Exemple #1
0
def check_system(fix=False, quit_if_error=False):
    """
    Checks that the system is setup correctly. fix==True, this command will
    attempt to fix any issues it encounters. If any fixes fail, it will
    exit(1). Fix will automatically be set to True if the user specifies --fix
    at the command line.

    :param fix: if True, try to fix any system dependency issues that are
    detected.
    :param quit_if_error: if True, quit with error code 1 if any issues are
    detected, or if any fixes are unsuccesful.
    :return: True if all system dependencies are in the proper state, False if
    they are not. This function will sys.exit(1) instead of returning false if
    quit_if_error == True
    """
    # modprobe and sysctl require root privileges.
    enforce_root()

    system_ok = (_check_kernel_modules(fix) and
                 _check_ip_forwarding(fix) and
                 _check_docker_version())

    if quit_if_error and not system_ok:
        sys.exit(1)

    return system_ok
def check_system(quit_if_error=False,
                 libnetwork=False,
                 check_docker=True,
                 check_modules=True,
                 check_etcd=True,
                 check_kernel=True):
    """
    Checks that the system is setup correctly.

    :param check_etcd: Whether to perform etcd checks.
    :param check_docker: Whether to perform docker checks.
    :param check_modules: Whether to perform module checks.
    :param quit_if_error: if True, quit with error code 1 if any issues are
    detected.
    :param libnetwork: If True, check for Docker version >= v1.21 to support libnetwork
    :return: a tuple containing the results of the checks

    This function will sys.exit(1) instead of returning false if
    quit_if_error == True
    """
    enforce_root()
    modules_ok = _check_modules() if check_modules else True
    docker_ok = _check_docker_version(libnetwork) if check_docker else True
    etcd_ok = check_etcd_version() if check_etcd else True
    kernel_ok = _check_kernel_version() if check_kernel else True

    system_ok = modules_ok and docker_ok and etcd_ok and kernel_ok

    if quit_if_error and not system_ok:
        sys.exit(1)

    return (modules_ok, docker_ok, etcd_ok)
def check_system(quit_if_error=False, libnetwork=False, check_docker=True,
                 check_modules=True, check_etcd=True, check_kernel=True):
    """
    Checks that the system is setup correctly.

    :param check_etcd: Whether to perform etcd checks.
    :param check_docker: Whether to perform docker checks.
    :param check_modules: Whether to perform module checks.
    :param quit_if_error: if True, quit with error code 1 if any issues are
    detected.
    :param libnetwork: If True, check for Docker version >= v1.21 to support libnetwork
    :return: a tuple containing the results of the checks

    This function will sys.exit(1) instead of returning false if
    quit_if_error == True
    """
    enforce_root()
    modules_ok = _check_modules() if check_modules else True
    docker_ok = _check_docker_version(libnetwork) if check_docker else True
    etcd_ok = _check_etcd_version() if check_etcd else True
    kernel_ok = _check_kernel_version() if check_kernel else True

    system_ok = modules_ok and docker_ok and etcd_ok and kernel_ok

    if quit_if_error and not system_ok:
        sys.exit(1)

    return (modules_ok, docker_ok, etcd_ok)
def status(arguments):
    """
    Main dispatcher for status commands. Calls the corresponding helper
    function.

    :param arguments: A dictionary of arguments already processed through
    this file's docstring with docopt
    :return: None
    """
    # Check runtime.
    runtime = arguments.get("--runtime")
    if not runtime in ["docker", "rkt"]:
        print "Invalid runtime specified: '%s'" % runtime
        sys.exit(1)

    # Check backend
    backend = arguments.get("--backend")
    if not backend in [None, "bird", "gobgp", "none"]:
        print "Invalid backend specified: '%s'" % backend
        sys.exit(1)

    # Start by locating the calico-node container and querying the package
    # summary file.
    if runtime == "rkt":
        enforce_root()
        check_container_status_rkt()
    else:
        check_container_status_docker()

    # Now query the host BGP details.  If the AS number is not specified on the
    # host then it must be inheriting the default.
    try:
        bgp_ipv4, bgp_ipv6 = client.get_host_bgp_ips(hostname)
        bgp_as = client.get_host_as(hostname)
        if bgp_as is None:
            bgp_as = client.get_default_node_as()
            bgp_as += " (inherited)"
    except DataStoreError:
        print "Error connecting to etcd.  Ensure ETCD_ENDPOINTS or ETCD_AUTHORITY is set properly."
        bgp_ipv4 = bgp_ipv6 = "unknown"
        bgp_as = "unknown"

    # TODO: Add additional information to the BIRD section:
    # TODO: - Include AS numbers of peers
    # TODO: - Include host name of peers when the peer is a calico-node
    # TODO: - Include details of peers configured multiple times

    print "\nIPv4 BGP status"
    if bgp_ipv4:
        print "IP: %s    AS Number: %s" % (bgp_ipv4, bgp_as)
        pprint_bgp_protocols(4, backend)
    else:
        print "No IPv4 address configured.\n"

    print "IPv6 BGP status"
    if bgp_ipv6:
        print "IP: %s    AS Number: %s" % (bgp_ipv6, bgp_as)
        pprint_bgp_protocols(6, backend)
    else:
        print "No IPv6 address configured.\n"
Exemple #5
0
def status(arguments):
    """
    Main dispatcher for status commands. Calls the corresponding helper
    function.

    :param arguments: A dictionary of arguments already processed through
    this file's docstring with docopt
    :return: None
    """
    # Check runtime.
    runtime = arguments.get("--runtime")
    if not runtime in ["docker", "rkt"]:
        print "Invalid runtime specified: '%s'" % runtime
        sys.exit(1)

    # Check backend
    backend = arguments.get("--backend")
    if not backend in [None, "bird", "gobgp", "none"]:
        print "Invalid backend specified: '%s'" % backend
        sys.exit(1)

    # Start by locating the calico-node container and querying the package
    # summary file.
    if runtime == "rkt":
        enforce_root()
        check_container_status_rkt()
    else:
        check_container_status_docker()

    # Now query the host BGP details.  If the AS number is not specified on the
    # host then it must be inheriting the default.
    try:
        bgp_ipv4, bgp_ipv6 = client.get_host_bgp_ips(hostname)
        bgp_as = client.get_host_as(hostname)
        if bgp_as is None:
            bgp_as = client.get_default_node_as()
            bgp_as += " (inherited)"
    except DataStoreError:
        print "Error connecting to etcd.  Ensure ETCD_ENDPOINTS or ETCD_AUTHORITY is set properly."
        bgp_ipv4 = bgp_ipv6 = "unknown"
        bgp_as = "unknown"

    # TODO: Add additional information to the BIRD section:
    # TODO: - Include AS numbers of peers
    # TODO: - Include host name of peers when the peer is a calico-node
    # TODO: - Include details of peers configured multiple times

    print "\nIPv4 BGP status"
    if bgp_ipv4:
        print "IP: %s    AS Number: %s" % (bgp_ipv4, bgp_as)
        pprint_bgp_protocols(4, backend)
    else:
        print "No IPv4 address configured.\n"

    print "IPv6 BGP status"
    if bgp_ipv6:
        print "IP: %s    AS Number: %s" % (bgp_ipv6, bgp_as)
        pprint_bgp_protocols(6, backend)
    else:
        print "No IPv6 address configured.\n"
def check_system(fix=False, quit_if_error=False):
    """
    Checks that the system is setup correctly. fix==True, this command will
    attempt to fix any issues it encounters. If any fixes fail, it will
    exit(1). Fix will automatically be set to True if the user specifies --fix
    at the command line.

    :param fix: if True, try to fix any system dependency issues that are
    detected.
    :param quit_if_error: if True, quit with error code 1 if any issues are
    detected, or if any fixes are unsuccesful.
    :return: True if all system dependencies are in the proper state, False if
    they are not. This function will sys.exit(1) instead of returning false if
    quit_if_error == True
    """
    # modprobe and sysctl require root privileges.
    enforce_root()

    system_ok = (_check_kernel_modules(fix) and
                 _check_ip_forwarding(fix) and
                 _check_docker_version())

    if quit_if_error and not system_ok:
        sys.exit(1)

    return system_ok
Exemple #7
0
def container_ip_remove(container_name, ip, interface):
    """
    Add an IP address to an existing Calico networked container.

    :param container_name: The name of the container.
    :param ip: The IP to add
    :param interface: The name of the interface in the container.

    :return: None
    """
    address = IPAddress(ip)

    # The netns manipulations must be done as root.
    enforce_root()

    pool = get_pool_or_exit(address)

    info = get_container_info_or_exit(container_name)
    container_id = info["Id"]

    # Check the container is actually running.
    if not info["State"]["Running"]:
        print "%s is not currently running." % container_name
        sys.exit(1)

    # Check that the container is already networked
    try:
        endpoint = client.get_endpoint(hostname=hostname, orchestrator_id=ORCHESTRATOR_ID, workload_id=container_id)
        if address.version == 4:
            nets = endpoint.ipv4_nets
        else:
            nets = endpoint.ipv6_nets

        if not IPNetwork(address) in nets:
            print "IP address is not assigned to container. Aborting."
            sys.exit(1)

    except KeyError:
        print "Container is unknown to Calico."
        sys.exit(1)

    try:
        nets.remove(IPNetwork(address))
        client.update_endpoint(endpoint)
    except (KeyError, ValueError):
        print "Error updating datastore. Aborting."
        sys.exit(1)

    try:
        container_pid = info["State"]["Pid"]
        netns.remove_ip_from_ns_veth(container_pid, address, interface)

    except CalledProcessError:
        print "Error updating networking in container. Aborting."
        sys.exit(1)

    client.unassign_address(pool, ip)

    print "IP %s removed from %s" % (ip, container_name)
def container_remove(container_id):
    """
    Remove a container (on this host) from Calico networking.

    The container may be left in a state without any working networking.
    If there is a network adaptor in the host namespace used by the container
    then it is removed.

    :param container_id: The namespace path or the ID of the container.
    """
    # The netns manipulations must be done as root.
    enforce_root()

    # Resolve the name to ID.
    if container_id.startswith("/") and os.path.exists(container_id):
        # The ID is a path. Don't do any docker lookups
        orchestrator_id = NAMESPACE_ORCHESTRATOR_ID
        endpoints = client.get_endpoints(hostname=hostname,
                                         orchestrator_id=orchestrator_id,
                                         workload_id=escape_etcd(container_id))
    else:
        # We know we're using "docker" as the orchestrator. If we have a direct
        # hit on the container id then we can proceed. Otherwise, ask docker to
        # try converting the name/id fragment into a full ID.
        orchestrator_id = DOCKER_ORCHESTRATOR_ID
        endpoints = client.get_endpoints(hostname=hostname,
                                         orchestrator_id=orchestrator_id,
                                         workload_id=container_id)

        if not endpoints:
            container_id = get_workload_id(container_id)
            endpoints = client.get_endpoints(hostname=hostname,
                                             orchestrator_id=orchestrator_id,
                                             workload_id=container_id)

    for endpoint in endpoints:
        # Remove any IP address assignments that this endpoint has
        client.release_ips(
            set(map(IPAddress, endpoint.ipv4_nets | endpoint.ipv6_nets)))

        try:
            # Remove the interface if it exists
            netns.remove_veth(endpoint.name)
        except CalledProcessError:
            print "Could not remove Calico interface %s" % endpoint.name

    # Always try to remove the workload, even if we didn't find any
    # endpoints.
    try:
        client.remove_workload(hostname, orchestrator_id, container_id)
        print "Removed Calico from %s" % container_id
    except KeyError:
        print "Failed find Calico data for %s" % container_id
Exemple #9
0
def diags(arguments):
    """
    Main dispatcher for diags commands. Calls the corresponding helper function.

    :param arguments: A dictionary of arguments already processed through
    this file's docstring with docopt
    :return: None
    """
    enforce_root()
    print("Collecting diags")
    save_diags(arguments["--log-dir"], arguments["--upload"])
    sys.exit(0)
Exemple #10
0
def container_remove(container_id):
    """
    Remove a container (on this host) from Calico networking.

    The container may be left in a state without any working networking.
    If there is a network adaptor in the host namespace used by the container
    then it is removed.

    :param container_id: The namespace path or the ID of the container.
    """
    # The netns manipulations must be done as root.
    enforce_root()

    # Resolve the name to ID.
    if container_id.startswith("/") and os.path.exists(container_id):
        # The ID is a path. Don't do any docker lookups
        workload_id = escape_etcd(container_id)
        orchestrator_id = NAMESPACE_ORCHESTRATOR_ID
    else:
        workload_id = get_workload_id(container_id)
        orchestrator_id = DOCKER_ORCHESTRATOR_ID

    # Find the endpoint ID. We need this to find any ACL rules
    try:
        endpoint = client.get_endpoint(hostname=hostname,
                                       orchestrator_id=orchestrator_id,
                                       workload_id=workload_id)
    except KeyError:
        print "Container %s doesn't contain any endpoints" % container_id
        sys.exit(1)

    # Remove any IP address assignments that this endpoint has
    for net in endpoint.ipv4_nets | endpoint.ipv6_nets:
        assert (net.size == 1)
        ip = net.ip
        pools = client.get_ip_pools(ip.version)
        for pool in pools:
            if ip in pool:
                # Ignore failure to unassign address, since we're not
                # enforcing assignments strictly in datastore.py.
                client.unassign_address(pool, ip)

    try:
        # Remove the interface if it exists
        netns.remove_veth(endpoint.name)
    except CalledProcessError:
        print "Could not remove Calico interface %s" % endpoint.name
        sys.exit(1)

    # Remove the container from the datastore.
    client.remove_workload(hostname, orchestrator_id, workload_id)

    print "Removed Calico interface from %s" % container_id
def container_remove(container_id):
    """
    Remove a container (on this host) from Calico networking.

    The container may be left in a state without any working networking.
    If there is a network adaptor in the host namespace used by the container
    then it is removed.

    :param container_id: The namespace path or the ID of the container.
    """
    # The netns manipulations must be done as root.
    enforce_root()

    # Resolve the name to ID.
    if container_id.startswith("/") and os.path.exists(container_id):
        # The ID is a path. Don't do any docker lookups
        orchestrator_id = NAMESPACE_ORCHESTRATOR_ID
        endpoints = client.get_endpoints(hostname=hostname,
                                         orchestrator_id=orchestrator_id,
                                         workload_id=escape_etcd(container_id))
    else:
        # We know we're using "docker" as the orchestrator. If we have a direct
        # hit on the container id then we can proceed. Otherwise, ask docker to
        # try converting the name/id fragment into a full ID.
        orchestrator_id = DOCKER_ORCHESTRATOR_ID
        endpoints = client.get_endpoints(hostname=hostname,
                                         orchestrator_id=orchestrator_id,
                                         workload_id=container_id)

        if not endpoints:
            container_id = get_workload_id(container_id)
            endpoints = client.get_endpoints(hostname=hostname,
                                             orchestrator_id=orchestrator_id,
                                             workload_id=container_id)

    for endpoint in endpoints:
        # Remove any IP address assignments that this endpoint has
        client.release_ips(set(map(IPAddress,
                                   endpoint.ipv4_nets | endpoint.ipv6_nets)))

        try:
            # Remove the interface if it exists
            netns.remove_veth(endpoint.name)
        except CalledProcessError:
            print "Could not remove Calico interface %s" % endpoint.name

    # Always try to remove the workload, even if we didn't find any
    # endpoints.
    try:
        client.remove_workload(hostname, orchestrator_id, container_id)
        print "Removed Calico from %s" % container_id
    except KeyError:
        print "Failed find Calico data for %s" % container_id
Exemple #12
0
def container_remove(container_id):
    """
    Remove a container (on this host) from Calico networking.

    The container may be left in a state without any working networking.
    If there is a network adaptor in the host namespace used by the container
    then it is removed.

    :param container_id: The namespace path or the ID of the container.
    """
    # The netns manipulations must be done as root.
    enforce_root()

    # Resolve the name to ID.
    if container_id.startswith("/") and os.path.exists(container_id):
        # The ID is a path. Don't do any docker lookups
        workload_id = escape_etcd(container_id)
        orchestrator_id = NAMESPACE_ORCHESTRATOR_ID
    else:
        workload_id = get_workload_id(container_id)
        orchestrator_id = DOCKER_ORCHESTRATOR_ID

    # Find the endpoint ID. We need this to find any ACL rules
    try:
        endpoint = client.get_endpoint(hostname=hostname,
                                       orchestrator_id=orchestrator_id,
                                       workload_id=workload_id)
    except KeyError:
        print "Container %s doesn't contain any endpoints" % container_id
        sys.exit(1)

    # Remove any IP address assignments that this endpoint has
    for net in endpoint.ipv4_nets | endpoint.ipv6_nets:
        assert(net.size == 1)
        ip = net.ip
        pools = client.get_ip_pools(ip.version)
        for pool in pools:
            if ip in pool:
                # Ignore failure to unassign address, since we're not
                # enforcing assignments strictly in datastore.py.
                client.unassign_address(pool, ip)

    try:
        # Remove the interface if it exists
        netns.remove_veth(endpoint.name)
    except CalledProcessError:
        print "Could not remove Calico interface %s" % endpoint.name
        sys.exit(1)

    # Remove the container from the datastore.
    client.remove_workload(hostname, orchestrator_id, workload_id)

    print "Removed Calico interface from %s" % container_id
def diags(arguments):
    """
    Main dispatcher for diags commands. Calls the corresponding helper function.

    :param arguments: A dictionary of arguments already processed through
    this file's docstring with docopt
    :return: None
    """
    # The command has to be run as root for ipset collections (and iptables)
    enforce_root()
    print("Collecting diagnostics")
    save_diags(arguments["--log-dir"])
    sys.exit(0)
Exemple #14
0
def diags(arguments):
    """
    Main dispatcher for diags commands. Calls the corresponding helper function.

    :param arguments: A dictionary of arguments already processed through
    this file's docstring with docopt
    :return: None
    """
    # The command has to be run as root for ipset collections (and iptables)
    enforce_root()
    print("Collecting diagnostics")
    save_diags(arguments["--log-dir"])
    sys.exit(0)
Exemple #15
0
def container_remove(container_name):
    """
    Remove a container (on this host) from Calico networking.

    The container may be left in a state without any working networking.
    If there is a network adaptor in the host namespace used by the container
    then it is removed.

    :param container_name: The name or ID of the container.
    """
    # The netns manipulations must be done as root.
    enforce_root()

    # Resolve the name to ID.
    workload_id = get_container_id(container_name)

    # Find the endpoint ID. We need this to find any ACL rules
    try:
        endpoint = client.get_endpoint(hostname=hostname,
                                       orchestrator_id=ORCHESTRATOR_ID,
                                       workload_id=workload_id)
    except KeyError:
        print "Container %s doesn't contain any endpoints" % container_name
        sys.exit(1)

    # Remove any IP address assignments that this endpoint has
    for net in endpoint.ipv4_nets | endpoint.ipv6_nets:
        assert(net.size == 1)
        ip = net.ip
        pools = client.get_ip_pools("v%s" % ip.version)
        for pool in pools:
            if ip in pool:
                # Ignore failure to unassign address, since we're not
                # enforcing assignments strictly in datastore.py.
                client.unassign_address(pool, ip)

    # Remove the endpoint
    netns.remove_endpoint(endpoint.endpoint_id)

    # Remove the container from the datastore.
    client.remove_workload(hostname, ORCHESTRATOR_ID, workload_id)

    print "Removed Calico interface from %s" % container_name
Exemple #16
0
def container_remove(container_name):
    """
    Remove a container (on this host) from Calico networking.

    The container may be left in a state without any working networking.
    If there is a network adaptor in the host namespace used by the container
    then it is removed.

    :param container_name: The name or ID of the container.
    """
    # The netns manipulations must be done as root.
    enforce_root()

    # Resolve the name to ID.
    workload_id = get_container_id(container_name)

    # Find the endpoint ID. We need this to find any ACL rules
    try:
        endpoint = client.get_endpoint(hostname=hostname,
                                       orchestrator_id=ORCHESTRATOR_ID,
                                       workload_id=workload_id)
    except KeyError:
        print "Container %s doesn't contain any endpoints" % container_name
        sys.exit(1)

    # Remove any IP address assignments that this endpoint has
    for net in endpoint.ipv4_nets | endpoint.ipv6_nets:
        assert(net.size == 1)
        ip = net.ip
        pools = client.get_ip_pools(ip.version)
        for pool in pools:
            if ip in pool:
                # Ignore failure to unassign address, since we're not
                # enforcing assignments strictly in datastore.py.
                client.unassign_address(pool, ip)

    # Remove the endpoint
    netns.remove_veth(endpoint.name)

    # Remove the container from the datastore.
    client.remove_workload(hostname, ORCHESTRATOR_ID, workload_id)

    print "Removed Calico interface from %s" % container_name
Exemple #17
0
def node_stop(force):
    """
    Stop the Calico node.  This stops the containers (calico/node and
    calico/node-libnetwork) that are started by calicoctl node.
    """
    # The command has to be run as root to stop the calico-node service
    enforce_root()

    endpoints = len(client.get_endpoints(hostname=hostname))
    if endpoints:
        if not force:
            print_paragraph("Current host has active endpoints so can't be "
                            "stopped.  Force with --force")
            print_paragraph("Note that stopping the node while there are "
                            "active endpoints may make it difficult to clean "
                            "up the endpoints: for example, Docker containers "
                            "networked using libnetwork with Calico will not "
                            "invoke network cleanup during the normal "
                            "container lifecycle.")
            sys.exit(1)
        else:
            print_paragraph("Stopping node while host has active endpoints.  "
                            "If this in error, restart the node using the "
                            "'calicoctl node' command.")

    try:
        docker_client.stop("calico-node")
    except docker.errors.APIError as err:
        if err.response.status_code != 404:
            raise
    try:
        docker_client.stop("calico-libnetwork")
    except docker.errors.APIError as err:
        if err.response.status_code != 404:
            raise
    try:
        call(["systemctl", "stop", "calico-node.service"])
    except OSError:
        # systemctl not installed, ignore error.
        pass

    print "Node stopped"
Exemple #18
0
def node_stop(force):
    """
    Stop the Calico node.  This stops the containers (calico/node and
    calico/node-libnetwork) that are started by calicoctl node.
    """
    # The command has to be run as root to stop the calico-node service
    enforce_root()

    endpoints = len(client.get_endpoints(hostname=hostname))
    if endpoints:
        if not force:
            print_paragraph("Current host has active endpoints so can't be "
                "stopped.  Force with --force")
            print_paragraph("Note that stopping the node while there are "
                            "active endpoints may make it difficult to clean "
                            "up the endpoints: for example, Docker containers "
                            "networked using libnetwork with Calico will not "
                            "invoke network cleanup during the normal "
                            "container lifecycle.")
            sys.exit(1)
        else:
            print_paragraph("Stopping node while host has active endpoints.  "
                            "If this in error, restart the node using the "
                            "'calicoctl node' command.")

    try:
        docker_client.stop("calico-node")
    except docker.errors.APIError as err:
        if err.response.status_code != 404:
            raise
    try:
        docker_client.stop("calico-libnetwork")
    except docker.errors.APIError as err:
        if err.response.status_code != 404:
            raise
    try:
        call(["systemctl", "stop", "calico-node.service"])
    except OSError:
        # systemctl not installed, ignore error.
        pass

    print "Node stopped"
def check_system(quit_if_error=False, libnetwork=False):
    """
    Checks that the system is setup correctly.

    :param quit_if_error: if True, quit with error code 1 if any issues are
    detected.
    :param libnetwork: If True, check for Docker version >= v1.21 to support libnetwork
    :return: True if all system dependencies are in the proper state, False if
    they are not. This function will sys.exit(1) instead of returning false if
    quit_if_error == True
    """
    enforce_root()
    modules_ok = _check_modules()
    docker_ok = _check_docker_version(libnetwork)

    system_ok = modules_ok and docker_ok

    if quit_if_error and not system_ok:
        sys.exit(1)

    return system_ok
def check_system(quit_if_error=False, libnetwork=False):
    """
    Checks that the system is setup correctly.

    :param quit_if_error: if True, quit with error code 1 if any issues are
    detected.
    :param libnetwork: If True, check for Docker version >= v1.21 to support libnetwork
    :return: True if all system dependencies are in the proper state, False if
    they are not. This function will sys.exit(1) instead of returning false if
    quit_if_error == True
    """
    enforce_root()
    modules_ok = _check_modules()
    docker_ok = _check_docker_version(libnetwork)

    system_ok = modules_ok and docker_ok

    if quit_if_error and not system_ok:
        sys.exit(1)

    return system_ok
Exemple #21
0
def pprint_bird_protocols(version):
    """
    Pretty print the output from the BIRD "show protocols".  This parses the
    existing output and lays it out in pretty printed table.

    :param version:  The IP version (4 or 6).
    :return: None.
    """
    # This needs to be run as root to access the bird data in /var/run/calico
    enforce_root()

    # Based on the IP version, run the appropriate BIRD command, and select
    # the appropriate separator char for an IP address.
    if getattr(sys, 'frozen', False):
        # We're running under pyinstaller
        birdcl = sys._MEIPASS + "/birdcl"
    else:
        birdcl = "birdcl"
    try:
        if version == 4:
            results = subprocess32.check_output(
                   "echo show protocols | %s -s /var/run/calico/bird.ctl" % birdcl,
                   shell=True)
            ip_sep = "."
        else:
            results = subprocess32.check_output(
                  "echo show protocols | %s -s /var/run/calico/bird6.ctl" % birdcl,
                  shell=True)
            ip_sep = ":"
    except subprocess32.CalledProcessError:
        print "Couldn't connect to bird."
        return

    # Parse the output from BIRD to extract the values in the protocol status
    # table.  We'll further parse the name since that includes details about
    # the type of peer and the peer IP address.
    x = PrettyTable(["Peer address", "Peer type", "State",
                     "Since", "Info"])
    lines = results.split("\n")
    found_table = False
    for line in lines:
        # When BIRD displays its protocol table, it prints the bird> prompt and
        # then shifts the cursor to print back over the prompt.  However, this
        # means that we get rogue prompts when parsing the output.  For this
        # processing just remove the prompt if it is present.
        if line.startswith("bird>"):
            line = line[5:]

        # Skip blank lines.
        line = line.strip()
        if not line:
            continue

        # Split the line into columns based on whitespace separators.  We split
        # a maximum of 5 times because the 6th "info" column may contain a
        # string that itself includes whitespace that should be maintained.
        columns = re.split("\s+", line.strip(), 5)

        # Loop until we find the table heading.
        if columns == ["name", "proto", "table", "state", "since", "info"]:
            found_table = True
            continue
        elif not found_table:
            continue

        # We expect either 5 or 6 columns depending on whether there was a
        # value in the info column.  Anything else is not handled, so revert
        # to displaying the raw BIRD output.
        if not (5 <= len(columns) <= 6):
            found_table = False
            break

        # Parse the name, we name our BGP peers as "Mesh", "Node" or "Global"
        # followed by the IP address.  Extract the info so we can pretty
        # print it.
        combined = columns[0]
        if combined.startswith("Mesh_"):
            name = combined[5:].replace("_", ip_sep)
            ptype = "node-to-node mesh"
        elif combined.startswith("Node_"):
            name = combined[5:].replace("_", ip_sep)
            ptype = "node specific"
        elif combined.startswith("Global_"):
            name = combined[7:].replace("_", ip_sep)
            ptype = "global"
        else:
            # This is not a BGP Peer, so do not include in the output.
            continue

        x.add_row([name, ptype, columns[3], columns[4],
                   columns[5] if len(columns) == 6 else ""])

    # If we parsed the table then pretty print the table, otherwise just output
    # the BIRD output directly.  The first line of the BIRD output provides an
    # overall BIRD status.
    if found_table:
        print str(x) + "\n"
    else:
        print results + "\n"
def container_ip_add(container_id, ip, interface):
    """
    Add an IP address to an existing Calico networked container.

    :param container_id: The namespace path or container_id of the container.
    :param ip: The IP to add
    :param interface: The name of the interface in the container.

    :return: None
    """

    # The netns manipulations must be done as root.
    enforce_root()

    if container_id.startswith("/") and os.path.exists(container_id):
        # The ID is a path. Don't do any docker lookups
        workload_id = escape_etcd(container_id)
        namespace = netns.Namespace(container_id)
        orchestrator_id = NAMESPACE_ORCHESTRATOR_ID
    else:
        info = get_container_info_or_exit(container_id)
        workload_id = info["Id"]
        namespace = netns.PidNamespace(info["State"]["Pid"])
        orchestrator_id = DOCKER_ORCHESTRATOR_ID

        # Check the container is actually running.
        if not info["State"]["Running"]:
            print "%s is not currently running." % container_id
            sys.exit(1)

    # Check that the container is already networked
    try:
        endpoint = client.get_endpoint(hostname=hostname,
                                       orchestrator_id=orchestrator_id,
                                       workload_id=workload_id)
    except KeyError:
        print "Failed to add IP address to container.\n"
        print_container_not_in_calico_msg(container_id)
        sys.exit(1)

    # From here, this method starts having side effects. If something
    # fails then at least try to leave the system in a clean state.
    address, pool = get_ip_and_pool(ip)

    try:
        if address.version == 4:
            endpoint.ipv4_nets.add(IPNetwork(address))
        else:
            endpoint.ipv6_nets.add(IPNetwork(address))
        client.update_endpoint(endpoint)
    except (KeyError, ValueError):
        client.release_ips({address})
        print "Error updating datastore. Aborting."
        sys.exit(1)

    if not netns.ns_veth_exists(namespace, interface):
        print "Interface provided does not exist in container. Aborting."
        sys.exit(1)

    try:
        netns.add_ip_to_ns_veth(namespace, address, interface)
    except CalledProcessError:
        print "Error updating networking in container. Aborting."
        if address.version == 4:
            endpoint.ipv4_nets.remove(IPNetwork(address))
        else:
            endpoint.ipv6_nets.remove(IPNetwork(address))
        client.update_endpoint(endpoint)
        client.release_ips({address})
        sys.exit(1)

    print "IP %s added to %s" % (str(address), container_id)
def container_ip_remove(container_id, ip, interface):
    """
    Add an IP address to an existing Calico networked container.

    :param container_id: The namespace path or container_id of the container.
    :param ip: The IP to add
    :param interface: The name of the interface in the container.

    :return: None
    """
    address = IPAddress(ip)

    # The netns manipulations must be done as root.
    enforce_root()

    pool = get_pool_or_exit(address)
    if container_id.startswith("/") and os.path.exists(container_id):
        # The ID is a path. Don't do any docker lookups
        workload_id = escape_etcd(container_id)
        namespace = netns.Namespace(container_id)
        orchestrator_id = NAMESPACE_ORCHESTRATOR_ID
    else:
        info = get_container_info_or_exit(container_id)
        workload_id = info["Id"]
        namespace = netns.PidNamespace(info["State"]["Pid"])
        orchestrator_id = DOCKER_ORCHESTRATOR_ID

        # Check the container is actually running.
        if not info["State"]["Running"]:
            print "%s is not currently running." % container_id
            sys.exit(1)

    # Check that the container is already networked
    try:
        endpoint = client.get_endpoint(hostname=hostname,
                                       orchestrator_id=orchestrator_id,
                                       workload_id=workload_id)
        if address.version == 4:
            nets = endpoint.ipv4_nets
        else:
            nets = endpoint.ipv6_nets

        if not IPNetwork(address) in nets:
            print "IP address is not assigned to container. Aborting."
            sys.exit(1)

    except KeyError:
        print "Container is unknown to Calico."
        sys.exit(1)

    try:
        nets.remove(IPNetwork(address))
        client.update_endpoint(endpoint)
    except (KeyError, ValueError):
        print "Error updating datastore. Aborting."
        sys.exit(1)

    try:
        netns.remove_ip_from_ns_veth(namespace, address, interface)

    except CalledProcessError:
        print "Error updating networking in container. Aborting."
        sys.exit(1)

    client.release_ips({address})

    print "IP %s removed from %s" % (ip, container_id)
def container_add(container_id, ip, interface):
    """
    Add a container (on this host) to Calico networking with the given IP.

    :param container_id: The namespace path or the docker name/ID of the container.
    :param ip: An IPAddress object with the desired IP to assign.
    :param interface: The name of the interface in the container.
    """
    # The netns manipulations must be done as root.
    enforce_root()

    # TODO: This section is redundant in container_add_ip and elsewhere
    if container_id.startswith("/") and os.path.exists(container_id):
        # The ID is a path. Don't do any docker lookups
        workload_id = escape_etcd(container_id)
        orchestrator_id = NAMESPACE_ORCHESTRATOR_ID
        namespace = netns.Namespace(container_id)
    else:
        info = get_container_info_or_exit(container_id)
        workload_id = info["Id"]
        orchestrator_id = DOCKER_ORCHESTRATOR_ID
        namespace = netns.PidNamespace(info["State"]["Pid"])

        # Check the container is actually running.
        if not info["State"]["Running"]:
            print_paragraph("%s is not currently running." % container_id)
            sys.exit(1)

        # We can't set up Calico if the container shares the host namespace.
        if info["HostConfig"]["NetworkMode"] == "host":
            print_paragraph("Can't add %s to Calico because it is "
                            "running NetworkMode = host." % container_id)
            sys.exit(1)

    # Check if the container already exists
    try:
        _ = client.get_endpoint(hostname=hostname,
                                orchestrator_id=orchestrator_id,
                                workload_id=workload_id)
    except KeyError:
        # Calico doesn't know about this container.  Continue.
        pass
    else:
        # Calico already set up networking for this container.  Since we got
        # called with an IP address, we shouldn't just silently exit, since
        # that would confuse the user: the container would not be reachable on
        # that IP address.
        print_paragraph("%s has already been configured with Calico "
                        "Networking." % container_id)
        sys.exit(1)

    ip, pool = get_ip_and_pool(ip)

    try:
        # The next hop IPs for this host are stored in etcd.
        next_hops = client.get_default_next_hops(hostname)
        next_hops[ip.version]
    except KeyError:
        print_paragraph("This node is not configured for IPv%d.  "
                        "Is calico-node running?" % ip.version)
        unallocated_ips = client.release_ips({ip})
        if unallocated_ips:
            print_paragraph("Error during cleanup. %s was already"
                            "unallocated." % ip)
        sys.exit(1)

    # Get the next hop for the IP address.
    next_hop = next_hops[ip.version]

    network = IPNetwork(IPAddress(ip))
    ep = Endpoint(hostname=hostname,
                  orchestrator_id=DOCKER_ORCHESTRATOR_ID,
                  workload_id=workload_id,
                  endpoint_id=uuid.uuid1().hex,
                  state="active",
                  mac=None)
    if network.version == 4:
        ep.ipv4_nets.add(network)
        ep.ipv4_gateway = next_hop
    else:
        ep.ipv6_nets.add(network)
        ep.ipv6_gateway = next_hop

    # Create the veth, move into the container namespace, add the IP and
    # set up the default routes.
    netns.increment_metrics(namespace)
    netns.create_veth(ep.name, ep.temp_interface_name)
    netns.move_veth_into_ns(namespace, ep.temp_interface_name, interface)
    netns.add_ip_to_ns_veth(namespace, ip, interface)
    netns.add_ns_default_route(namespace, next_hop, interface)

    # Grab the MAC assigned to the veth in the namespace.
    ep.mac = netns.get_ns_veth_mac(namespace, interface)

    # Register the endpoint with Felix.
    client.set_endpoint(ep)

    # Let the caller know what endpoint was created.
    print_paragraph("IP %s added to %s" % (str(ip), container_id))
    return ep
Exemple #25
0
def node_start(node_image, runtime, log_dir, ip, ip6, as_num, detach,
               libnetwork_image, no_pull):
    """
    Create the calico-node container and establish Calico networking on this
    host.

    :param ip:  The IPv4 address of the host.
    :param node_image:  The calico-node image to use.
    :param ip6:  The IPv6 address of the host (or None if not configured)
    :param as_num:  The BGP AS Number to use for this node.  If not specified
    the global default value will be used.
    :param detach: True to run in Docker's "detached" mode, False to run
    attached.
    :param libnetwork_image: The name of the Calico libnetwork driver image to
    use.  None, if not using libnetwork.
    :param no_pull: Boolean, True to prevent function from pulling the Calico
    node Docker images.
    :return:  None.
    """
    # The command has to be run as root to access iptables and services
    enforce_root()

    # Normally, Felix will load the modules it needs, but when running inside a
    # container it might not be able to do so. Ensure the required modules are
    # loaded each time the node starts.
    # We only make a best effort attempt because the command may fail if the
    # modules are built in.
    # We'll warn during the check_system() if the modules are unavailable.
    if not running_in_container():
        try:
            call(["modprobe", "-a"] + REQUIRED_MODULES)
        except OSError:
            pass

        _setup_ip_forwarding()
        _set_nf_conntrack_max()

        # Print warnings for any known system issues before continuing
        if runtime == 'docker' and not running_in_container():
            using_docker = True
        else:
            using_docker = False

        (_, docker_ok, etcd_ok) = \
            check_system(quit_if_error=False, libnetwork=libnetwork_image,
                         check_docker=using_docker,
                         check_modules=not running_in_container())

        if not etcd_ok or (using_docker and not docker_ok):
            sys.exit(1)

    # Ensure log directory exists
    if not os.path.exists(log_dir):
        os.makedirs(log_dir)

    # The format of the authority and endpoints strings have already been
    # validated.
    etcd_authority = os.getenv(ETCD_AUTHORITY_ENV, ETCD_AUTHORITY_DEFAULT)
    etcd_endpoints = os.getenv(ETCD_ENDPOINTS_ENV)

    # Get etcd SSL environment variables if they exist
    etcd_scheme = os.getenv(ETCD_SCHEME_ENV, ETCD_SCHEME_DEFAULT)
    etcd_key_file = os.getenv(ETCD_KEY_FILE_ENV)
    etcd_cert_file = os.getenv(ETCD_CERT_FILE_ENV)
    etcd_ca_cert_file = os.getenv(ETCD_CA_CERT_FILE_ENV)

    etcd_volumes = []
    etcd_binds = {}
    etcd_envs = ["ETCD_AUTHORITY=%s" % etcd_authority,
                 "ETCD_SCHEME=%s" % etcd_scheme]

    if etcd_endpoints:
        etcd_envs.append("ETCD_ENDPOINTS=%s" % etcd_endpoints)

    if etcd_ca_cert_file and etcd_key_file and etcd_cert_file:
        etcd_volumes.append(ETCD_CA_CERT_NODE_FILE)
        etcd_binds[etcd_ca_cert_file] = {"bind": ETCD_CA_CERT_NODE_FILE,
                                         "ro": True}
        etcd_envs.append("ETCD_CA_CERT_FILE=%s" % ETCD_CA_CERT_NODE_FILE)

        etcd_volumes.append(ETCD_KEY_NODE_FILE)
        etcd_binds[etcd_key_file] = {"bind": ETCD_KEY_NODE_FILE,
                                     "ro": True}
        etcd_envs.append("ETCD_KEY_FILE=%s" % ETCD_KEY_NODE_FILE)

        etcd_volumes.append(ETCD_CERT_NODE_FILE)
        etcd_binds[etcd_cert_file] = {"bind": ETCD_CERT_NODE_FILE,
                                      "ro": True}
        etcd_envs.append("ETCD_CERT_FILE=%s" % ETCD_CERT_NODE_FILE)

    if runtime == 'docker':
        _start_node_container_docker(ip, ip6, as_num, log_dir, node_image, detach,
                                     etcd_envs, etcd_volumes, etcd_binds, no_pull)
        if libnetwork_image:
            _start_libnetwork_container(libnetwork_image, etcd_envs,
                                        etcd_volumes, etcd_binds, no_pull)
    if runtime == 'rkt':
        _start_node_container_rkt(ip, ip6, as_num, node_image, etcd_envs,
                                  etcd_volumes, etcd_binds)
Exemple #26
0
def check_system(fix=False, quit_if_error=False):
    """
    Checks that the system is setup correctly. fix==True, this command will
    attempt to fix any issues it encounters. If any fixes fail, it will
    exit(1). Fix will automatically be set to True if the user specifies --fix
    at the command line.

    :param fix: if True, try to fix any system dependency issues that are
    detected.
    :param quit_if_error: if True, quit with error code 1 if any issues are
    detected, or if any fixes are unsuccesful.
    :return: True if all system dependencies are in the proper state, False if
    they are not. This function will sys.exit(1) instead of returning false if
    quit_if_error == True
    """
    # modprobe and sysctl require root privileges.
    enforce_root()

    system_ok = True
    modprobe = sh.Command._create('modprobe')
    ip6tables = sh.Command._create('ip6tables')
    try:
        ip6tables("-L")
    except:
        if fix:
            try:
                modprobe('ip6_tables')
            except sh.ErrorReturnCode:
                print >> sys.stderr, "ERROR: Could not enable ip6_tables."
                system_ok = False
        else:
            print >> sys.stderr, "WARNING: Unable to detect the ip6_tables " \
                                 "module. Load with `modprobe ip6_tables`"
            system_ok = False

    if not module_loaded("xt_set"):
        if fix:
            try:
                modprobe('xt_set')
            except sh.ErrorReturnCode:
                print >> sys.stderr, "ERROR: Could not enable xt_set."
                system_ok = False
        else:
            print >> sys.stderr, "WARNING: Unable to detect the xt_set " \
                                 "module. Load with `modprobe xt_set`"
            system_ok = False

    # Enable IP forwarding since all compute hosts are vRouters.
    # IPv4 forwarding should be enabled already by docker.
    if "1" not in sysctl("net.ipv4.ip_forward"):
        if fix:
            if "1" not in sysctl("-w", "net.ipv4.ip_forward=1"):
                print >> sys.stderr, "ERROR: Could not enable ipv4 forwarding."
                system_ok = False
        else:
            print >> sys.stderr, "WARNING: ipv4 forwarding is not enabled."
            system_ok = False

    if "1" not in sysctl("net.ipv6.conf.all.forwarding"):
        if fix:
            if "1" not in sysctl("-w", "net.ipv6.conf.all.forwarding=1"):
                print >> sys.stderr, "ERROR: Could not enable ipv6 forwarding."
                system_ok = False
        else:
            print >> sys.stderr, "WARNING: ipv6 forwarding is not enabled."
            system_ok = False

    # Check docker version compatability
    try:
        info = docker_client.version()
    except docker.errors.APIError:
        print >> sys.stderr, "ERROR: Docker server must support Docker " \
                             "Remote API v%s or greater." % DOCKER_VERSION
        system_ok = False
    else:
        api_version = normalize_version(info['ApiVersion'])
        # Check that API Version is above the minimum supported version
        if cmp(api_version, normalize_version(DOCKER_VERSION)) < 0:
            print >> sys.stderr, "ERROR: Docker server must support Docker " \
                                 "Remote API v%s or greater." % DOCKER_VERSION
            system_ok = False

    if quit_if_error and not system_ok:
        sys.exit(1)

    return system_ok
def container_add(container_name, ip, interface):
    """
    Add a container (on this host) to Calico networking with the given IP.

    :param container_name: The name or ID of the container.
    :param ip: An IPAddress object with the desired IP to assign.
    :param interface: The name of the interface in the container.
    """
    # The netns manipulations must be done as root.
    enforce_root()
    info = get_container_info_or_exit(container_name)
    container_id = info["Id"]

    # Check if the container already exists
    try:
        _ = client.get_endpoint(hostname=hostname,
                                orchestrator_id=ORCHESTRATOR_ID,
                                workload_id=container_id)
    except KeyError:
        # Calico doesn't know about this container.  Continue.
        pass
    else:
        # Calico already set up networking for this container.  Since we got
        # called with an IP address, we shouldn't just silently exit, since
        # that would confuse the user: the container would not be reachable on
        # that IP address.
        print "%s has already been configured with Calico Networking." % \
              container_name
        sys.exit(1)

    # Check the container is actually running.
    if not info["State"]["Running"]:
        print "%s is not currently running." % container_name
        sys.exit(1)

    # We can't set up Calico if the container shares the host namespace.
    if info["HostConfig"]["NetworkMode"] == "host":
        print "Can't add %s to Calico because it is " \
              "running NetworkMode = host." % container_name
        sys.exit(1)

    # Check the IP is in the allocation pool.  If it isn't, BIRD won't export
    # it.
    ip = IPAddress(ip)
    pool = get_pool_or_exit(ip)

    # The next hop IPs for this host are stored in etcd.
    next_hops = client.get_default_next_hops(hostname)
    try:
        next_hops[ip.version]
    except KeyError:
        print "This node is not configured for IPv%d." % ip.version
        sys.exit(1)

    # Assign the IP
    if not client.assign_address(pool, ip):
        print "IP address is already assigned in pool %s " % pool
        sys.exit(1)

    # Get the next hop for the IP address.
    next_hop = next_hops[ip.version]

    network = IPNetwork(IPAddress(ip))
    ep = Endpoint(hostname=hostname,
                  orchestrator_id=ORCHESTRATOR_ID,
                  workload_id=container_id,
                  endpoint_id=uuid.uuid1().hex,
                  state="active",
                  mac=None)
    if network.version == 4:
        ep.ipv4_nets.add(network)
        ep.ipv4_gateway = next_hop
    else:
        ep.ipv6_nets.add(network)
        ep.ipv6_gateway = next_hop

    # Create the veth, move into the container namespace, add the IP and
    # set up the default routes.
    pid = info["State"]["Pid"]
    netns.create_veth(ep.name, ep.temp_interface_name)
    netns.move_veth_into_ns(pid, ep.temp_interface_name, interface)
    netns.add_ip_to_ns_veth(pid, ip, interface)
    netns.add_ns_default_route(pid, next_hop, interface)

    # Grab the MAC assigned to the veth in the namespace.
    ep.mac = netns.get_ns_veth_mac(pid, interface)

    # Register the endpoint with Felix.
    client.set_endpoint(ep)

    # Let the caller know what endpoint was created.
    return ep
Exemple #28
0
def container_ip_add(container_id, ip, interface):
    """
    Add an IP address to an existing Calico networked container.

    :param container_id: The namespace path or container_id of the container.
    :param ip: The IP to add
    :param interface: The name of the interface in the container.

    :return: None
    """
    address = IPAddress(ip)

    # The netns manipulations must be done as root.
    enforce_root()

    pool = get_pool_or_exit(address)

    if container_id.startswith("/") and os.path.exists(container_id):
        # The ID is a path. Don't do any docker lookups
        workload_id = escape_etcd(container_id)
        namespace = netns.Namespace(container_id)
        orchestrator_id = NAMESPACE_ORCHESTRATOR_ID
    else:
        info = get_container_info_or_exit(container_id)
        workload_id = info["Id"]
        namespace = netns.PidNamespace(info["State"]["Pid"])
        orchestrator_id = DOCKER_ORCHESTRATOR_ID

        # Check the container is actually running.
        if not info["State"]["Running"]:
            print "%s is not currently running." % container_id
            sys.exit(1)

    # Check that the container is already networked
    try:
        endpoint = client.get_endpoint(hostname=hostname,
                                       orchestrator_id=orchestrator_id,
                                       workload_id=workload_id)
    except KeyError:
        print "Failed to add IP address to container.\n"
        print_container_not_in_calico_msg(container_id)
        sys.exit(1)

    # From here, this method starts having side effects. If something
    # fails then at least try to leave the system in a clean state.
    if not client.assign_address(pool, address):
        print "IP address is already assigned in pool %s " % pool
        sys.exit(1)

    try:
        if address.version == 4:
            endpoint.ipv4_nets.add(IPNetwork(address))
        else:
            endpoint.ipv6_nets.add(IPNetwork(address))
        client.update_endpoint(endpoint)
    except (KeyError, ValueError):
        client.unassign_address(pool, ip)
        print "Error updating datastore. Aborting."
        sys.exit(1)

    try:
        netns.add_ip_to_ns_veth(namespace, address, interface)
    except CalledProcessError:
        print "Error updating networking in container. Aborting."
        if address.version == 4:
            endpoint.ipv4_nets.remove(IPNetwork(address))
        else:
            endpoint.ipv6_nets.remove(IPNetwork(address))
        client.update_endpoint(endpoint)
        client.unassign_address(pool, ip)
        sys.exit(1)

    print "IP %s added to %s" % (ip, container_id)
Exemple #29
0
def container_ip_remove(container_id, ip, interface):
    """
    Add an IP address to an existing Calico networked container.

    :param container_id: The namespace path or container_id of the container.
    :param ip: The IP to add
    :param interface: The name of the interface in the container.

    :return: None
    """
    address = IPAddress(ip)

    # The netns manipulations must be done as root.
    enforce_root()

    pool = get_pool_or_exit(address)
    if container_id.startswith("/") and os.path.exists(container_id):
        # The ID is a path. Don't do any docker lookups
        workload_id = escape_etcd(container_id)
        namespace = netns.Namespace(container_id)
        orchestrator_id = NAMESPACE_ORCHESTRATOR_ID
    else:
        info = get_container_info_or_exit(container_id)
        workload_id = info["Id"]
        namespace = netns.PidNamespace(info["State"]["Pid"])
        orchestrator_id = DOCKER_ORCHESTRATOR_ID

        # Check the container is actually running.
        if not info["State"]["Running"]:
            print "%s is not currently running." % container_id
            sys.exit(1)

    # Check that the container is already networked
    try:
        endpoint = client.get_endpoint(hostname=hostname,
                                       orchestrator_id=orchestrator_id,
                                       workload_id=workload_id)
        if address.version == 4:
            nets = endpoint.ipv4_nets
        else:
            nets = endpoint.ipv6_nets

        if not IPNetwork(address) in nets:
            print "IP address is not assigned to container. Aborting."
            sys.exit(1)

    except KeyError:
        print "Container is unknown to Calico."
        sys.exit(1)

    try:
        nets.remove(IPNetwork(address))
        client.update_endpoint(endpoint)
    except (KeyError, ValueError):
        print "Error updating datastore. Aborting."
        sys.exit(1)

    try:
        netns.remove_ip_from_ns_veth(namespace, address, interface)

    except CalledProcessError:
        print "Error updating networking in container. Aborting."
        sys.exit(1)

    client.unassign_address(pool, address)

    print "IP %s removed from %s" % (ip, container_id)
Exemple #30
0
def container_add(container_id, ip, interface):
    """
    Add a container (on this host) to Calico networking with the given IP.

    :param container_id: The namespace path or the docker name/ID of the container.
    :param ip: An IPAddress object with the desired IP to assign.
    :param interface: The name of the interface in the container.
    """
    # The netns manipulations must be done as root.
    enforce_root()

    if container_id.startswith("/") and os.path.exists(container_id):
        # The ID is a path. Don't do any docker lookups
        workload_id = escape_etcd(container_id)
        orchestrator_id = NAMESPACE_ORCHESTRATOR_ID
        namespace = netns.Namespace(container_id)
    else:
        info = get_container_info_or_exit(container_id)
        workload_id = info["Id"]
        orchestrator_id = DOCKER_ORCHESTRATOR_ID
        namespace = netns.PidNamespace(info["State"]["Pid"])

        # Check the container is actually running.
        if not info["State"]["Running"]:
            print "%s is not currently running." % container_id
            sys.exit(1)

        # We can't set up Calico if the container shares the host namespace.
        if info["HostConfig"]["NetworkMode"] == "host":
            print "Can't add %s to Calico because it is " \
                  "running NetworkMode = host." % container_id
            sys.exit(1)

    # Check if the container already exists
    try:
        _ = client.get_endpoint(hostname=hostname,
                                orchestrator_id=orchestrator_id,
                                workload_id=workload_id)
    except KeyError:
        # Calico doesn't know about this container.  Continue.
        pass
    else:
        # Calico already set up networking for this container.  Since we got
        # called with an IP address, we shouldn't just silently exit, since
        # that would confuse the user: the container would not be reachable on
        # that IP address.
        print "%s has already been configured with Calico Networking." % \
              container_id
        sys.exit(1)

    # Check the IP is in the allocation pool.  If it isn't, BIRD won't export
    # it.
    ip = IPAddress(ip)
    pool = get_pool_or_exit(ip)

    # The next hop IPs for this host are stored in etcd.
    next_hops = client.get_default_next_hops(hostname)
    try:
        next_hops[ip.version]
    except KeyError:
        print "This node is not configured for IPv%d." % ip.version
        sys.exit(1)

    # Assign the IP
    if not client.assign_address(pool, ip):
        print "IP address is already assigned in pool %s " % pool
        sys.exit(1)

    # Get the next hop for the IP address.
    next_hop = next_hops[ip.version]

    network = IPNetwork(IPAddress(ip))
    ep = Endpoint(hostname=hostname,
                  orchestrator_id=DOCKER_ORCHESTRATOR_ID,
                  workload_id=workload_id,
                  endpoint_id=uuid.uuid1().hex,
                  state="active",
                  mac=None)
    if network.version == 4:
        ep.ipv4_nets.add(network)
        ep.ipv4_gateway = next_hop
    else:
        ep.ipv6_nets.add(network)
        ep.ipv6_gateway = next_hop

    # Create the veth, move into the container namespace, add the IP and
    # set up the default routes.
    netns.create_veth(ep.name, ep.temp_interface_name)
    netns.move_veth_into_ns(namespace, ep.temp_interface_name, interface)
    netns.add_ip_to_ns_veth(namespace, ip, interface)
    netns.add_ns_default_route(namespace, next_hop, interface)

    # Grab the MAC assigned to the veth in the namespace.
    ep.mac = netns.get_ns_veth_mac(namespace, interface)

    # Register the endpoint with Felix.
    client.set_endpoint(ep)

    # Let the caller know what endpoint was created.
    return ep
def container_add(container_id, ip, interface):
    """
    Add a container (on this host) to Calico networking with the given IP.

    :param container_id: The namespace path or the docker name/ID of the container.
    :param ip: An IPAddress object with the desired IP to assign.
    :param interface: The name of the interface in the container.
    """
    # The netns manipulations must be done as root.
    enforce_root()

    # TODO: This section is redundant in container_add_ip and elsewhere
    if container_id.startswith("/") and os.path.exists(container_id):
        # The ID is a path. Don't do any docker lookups
        workload_id = escape_etcd(container_id)
        orchestrator_id = NAMESPACE_ORCHESTRATOR_ID
        namespace = netns.Namespace(container_id)
    else:
        info = get_container_info_or_exit(container_id)
        workload_id = info["Id"]
        orchestrator_id = DOCKER_ORCHESTRATOR_ID
        namespace = netns.PidNamespace(info["State"]["Pid"])

        # Check the container is actually running.
        if not info["State"]["Running"]:
            print_paragraph("%s is not currently running." % container_id)
            sys.exit(1)

        # We can't set up Calico if the container shares the host namespace.
        if info["HostConfig"]["NetworkMode"] == "host":
            print_paragraph("Can't add %s to Calico because it is "
                            "running NetworkMode = host." % container_id)
            sys.exit(1)

    # Check if the container already exists
    try:
        _ = client.get_endpoint(hostname=hostname,
                                orchestrator_id=orchestrator_id,
                                workload_id=workload_id)
    except KeyError:
        # Calico doesn't know about this container.  Continue.
        pass
    else:
        # Calico already set up networking for this container.  Since we got
        # called with an IP address, we shouldn't just silently exit, since
        # that would confuse the user: the container would not be reachable on
        # that IP address.
        print_paragraph("%s has already been configured with Calico "
                        "Networking." % container_id)
        sys.exit(1)

    ep = Endpoint(hostname=hostname,
                  orchestrator_id=DOCKER_ORCHESTRATOR_ID,
                  workload_id=workload_id,
                  endpoint_id=uuid.uuid1().hex,
                  state="active",
                  mac=None)

    ip, _ = get_ip_and_pool(ip)

    network = IPNetwork(ip)
    if network.version == 4:
        ep.ipv4_nets.add(network)
    else:
        ep.ipv6_nets.add(network)

    # Create the veth, move into the container namespace, add the IP and
    # set up the default routes.
    netns.increment_metrics(namespace)
    netns.create_veth(ep.name, ep.temp_interface_name)
    netns.move_veth_into_ns(namespace, ep.temp_interface_name, interface)
    netns.add_ip_to_ns_veth(namespace, ip, interface)
    netns.add_ns_default_route(namespace, ep.name, interface)

    # Grab the MAC assigned to the veth in the namespace.
    ep.mac = netns.get_ns_veth_mac(namespace, interface)

    # Register the endpoint with Felix.
    client.set_endpoint(ep)

    # Let the caller know what endpoint was created.
    print_paragraph("IP %s added to %s" % (str(ip), container_id))
    return ep
Exemple #32
0
def pprint_bird_protocols(version):
    """
    Pretty print the output from the BIRD "show protocols".  This parses the
    existing output and lays it out in pretty printed table.

    :param version:  The IP version (4 or 6).
    :return: None.
    """
    # This needs to be run as root to access the bird data in /var/run/calico
    enforce_root()

    # Based on the IP version, run the appropriate BIRD command, and select
    # the appropriate separator char for an IP address.
    if getattr(sys, 'frozen', False):
        # We're running under pyinstaller
        birdcl = sys._MEIPASS + "/birdcl"
    else:
        birdcl = "birdcl"
    try:
        if version == 4:
            results = subprocess32.check_output(
                   "echo show protocols | %s -s /var/run/calico/bird.ctl" % birdcl,
                   shell=True)
            ip_sep = "."
        else:
            results = subprocess32.check_output(
                  "echo show protocols | %s -s /var/run/calico/bird6.ctl" % birdcl,
                  shell=True)
            ip_sep = ":"
    except subprocess32.CalledProcessError:
        print "Couldn't connect to bird."
        return

    # Parse the output from BIRD to extract the values in the protocol status
    # table.  We'll further parse the name since that includes details about
    # the type of peer and the peer IP address.
    x = PrettyTable(["Peer address", "Peer type", "State",
                     "Since", "Info"])
    lines = results.split("\n")
    found_table = False
    for line in lines:
        # When BIRD displays its protocol table, it prints the bird> prompt and
        # then shifts the cursor to print back over the prompt.  However, this
        # means that we get rogue prompts when parsing the output.  For this
        # processing just remove the prompt if it is present.
        if line.startswith("bird>"):
            line = line[5:]

        # Skip blank lines.
        line = line.strip()
        if not line:
            continue

        # Split the line into columns based on whitespace separators.  We split
        # a maximum of 5 times because the 6th "info" column may contain a
        # string that itself includes whitespace that should be maintained.
        columns = re.split("\s+", line.strip(), 5)

        # Loop until we find the table heading.
        if columns == ["name", "proto", "table", "state", "since", "info"]:
            found_table = True
            continue
        elif not found_table:
            continue

        # We expect either 5 or 6 columns depending on whether there was a
        # value in the info column.  Anything else is not handled, so revert
        # to displaying the raw BIRD output.
        if not (5 <= len(columns) <= 6):
            found_table = False
            break

        # Parse the name, we name our BGP peers as "Mesh", "Node" or "Global"
        # followed by the IP address.  Extract the info so we can pretty
        # print it.
        combined = columns[0]
        if combined.startswith("Mesh_"):
            name = combined[5:].replace("_", ip_sep)
            ptype = "node-to-node mesh"
        elif combined.startswith("Node_"):
            name = combined[5:].replace("_", ip_sep)
            ptype = "node specific"
        elif combined.startswith("Global_"):
            name = combined[7:].replace("_", ip_sep)
            ptype = "global"
        else:
            # This is not a BGP Peer, so do not include in the output.
            continue

        x.add_row([name, ptype, columns[3], columns[4],
                   columns[5] if len(columns) == 6 else ""])

    # If we parsed the table then pretty print the table, otherwise just output
    # the BIRD output directly.  The first line of the BIRD output provides an
    # overall BIRD status.
    if found_table:
        print str(x) + "\n"
    else:
        print results + "\n"
Exemple #33
0
def container_add(container_name, ip, interface):
    """
    Add a container (on this host) to Calico networking with the given IP.

    :param container_name: The name or ID of the container.
    :param ip: An IPAddress object with the desired IP to assign.
    """
    # The netns manipulations must be done as root.
    enforce_root()
    info = get_container_info_or_exit(container_name)
    container_id = info["Id"]

    # Check if the container already exists
    try:
        _ = client.get_endpoint(hostname=hostname,
                                orchestrator_id=ORCHESTRATOR_ID,
                                workload_id=container_id)
    except KeyError:
        # Calico doesn't know about this container.  Continue.
        pass
    else:
        # Calico already set up networking for this container.  Since we got
        # called with an IP address, we shouldn't just silently exit, since
        # that would confuse the user: the container would not be reachable on
        # that IP address.
        print "%s has already been configured with Calico Networking." % \
              container_name
        sys.exit(1)

    # Check the container is actually running.
    if not info["State"]["Running"]:
        print "%s is not currently running." % container_name
        sys.exit(1)

    # Check the IP is in the allocation pool.  If it isn't, BIRD won't export
    # it.
    ip = IPAddress(ip)
    pool = get_pool_or_exit(ip)

    # The next hop IPs for this host are stored in etcd.
    next_hops = client.get_default_next_hops(hostname)
    try:
        next_hops[ip.version]
    except KeyError:
        print "This node is not configured for IPv%d." % ip.version
        sys.exit(1)

    # Assign the IP
    if not client.assign_address(pool, ip):
        print "IP address is already assigned in pool %s " % pool
        sys.exit(1)

    # Actually configure the netns. Defaults to eth1 since eth0 could
    # already be in use (e.g. by the Docker bridge)
    pid = info["State"]["Pid"]
    endpoint = netns.set_up_endpoint(ip=ip,
                                     hostname=hostname,
                                     orchestrator_id=ORCHESTRATOR_ID,
                                     workload_id=container_id,
                                     cpid=pid,
                                     next_hop_ips=next_hops,
                                     veth_name=interface,
                                     proc_alias="/proc")

    # Register the endpoint
    client.set_endpoint(endpoint)

    print "IP %s added to %s" % (ip, container_name)
Exemple #34
0
def container_ip_remove(container_name, ip, version, interface):
    """
    Add an IP address to an existing Calico networked container.

    :param container_name: The name of the container.
    :param ip: The IP to add
    :param version: The IP version ("v4" or "v6")
    :param interface: The name of the interface in the container.

    :return: None
    """
    address = check_ip_version(ip, version, IPAddress)

    # The netns manipulations must be done as root.
    enforce_root()

    pool = get_pool_or_exit(address)

    info = get_container_info_or_exit(container_name)
    container_id = info["Id"]

    # Check the container is actually running.
    if not info["State"]["Running"]:
        print "%s is not currently running." % container_name
        sys.exit(1)

    # Check that the container is already networked
    try:
        endpoint = client.get_endpoint(hostname=hostname,
                                       orchestrator_id=ORCHESTRATOR_ID,
                                       workload_id=container_id)
        if address.version == 4:
            nets = endpoint.ipv4_nets
        else:
            nets = endpoint.ipv6_nets

        if not IPNetwork(address) in nets:
            print "IP address is not assigned to container. Aborting."
            sys.exit(1)

    except KeyError:
        print "Container is unknown to Calico."
        sys.exit(1)

    try:
        nets.remove(IPNetwork(address))
        client.update_endpoint(endpoint)
    except (KeyError, ValueError):
        print "Error updating datastore. Aborting."
        sys.exit(1)

    try:
        container_pid = info["State"]["Pid"]
        netns.remove_ip_from_interface(container_pid,
                                       address,
                                       interface,
                                       proc_alias="/proc")

    except CalledProcessError:
        print "Error updating networking in container. Aborting."
        sys.exit(1)

    client.unassign_address(pool, ip)

    print "IP %s removed from %s" % (ip, container_name)
Exemple #35
0
def container_ip_add(container_name, ip, version, interface):
    """
    Add an IP address to an existing Calico networked container.

    :param container_name: The name of the container.
    :param ip: The IP to add
    :param version: The IP version ("v4" or "v6")
    :param interface: The name of the interface in the container.

    :return: None
    """
    address = check_ip_version(ip, version, IPAddress)

    # The netns manipulations must be done as root.
    enforce_root()

    pool = get_pool_or_exit(address)

    info = get_container_info_or_exit(container_name)
    container_id = info["Id"]

    # Check the container is actually running.
    if not info["State"]["Running"]:
        print "%s is not currently running." % container_name
        sys.exit(1)

    # Check that the container is already networked
    try:
        endpoint = client.get_endpoint(hostname=hostname,
                                       orchestrator_id=ORCHESTRATOR_ID,
                                       workload_id=container_id)
    except KeyError:
        print "Failed to add IP address to container.\n"
        print_container_not_in_calico_msg(container_name)
        sys.exit(1)

    # From here, this method starts having side effects. If something
    # fails then at least try to leave the system in a clean state.
    if not client.assign_address(pool, ip):
        print "IP address is already assigned in pool %s " % pool
        sys.exit(1)

    try:
        if address.version == 4:
            endpoint.ipv4_nets.add(IPNetwork(address))
        else:
            endpoint.ipv6_nets.add(IPNetwork(address))
        client.update_endpoint(endpoint)
    except (KeyError, ValueError):
        client.unassign_address(pool, ip)
        print "Error updating datastore. Aborting."
        sys.exit(1)

    try:
        container_pid = info["State"]["Pid"]
        netns.add_ip_to_interface(container_pid,
                                  address,
                                  interface,
                                  proc_alias="/proc")
    except CalledProcessError:
        print "Error updating networking in container. Aborting."
        if address.version == 4:
            endpoint.ipv4_nets.remove(IPNetwork(address))
        else:
            endpoint.ipv6_nets.remove(IPNetwork(address))
        client.update_endpoint(endpoint)
        client.unassign_address(pool, ip)
        sys.exit(1)

    print "IP %s added to %s" % (ip, container_id)
Exemple #36
0
def node_start(node_image, runtime, log_dir, ip, ip6, as_num, detach,
               libnetwork_image, no_pull):
    """
    Create the calico-node container and establish Calico networking on this
    host.

    :param ip:  The IPv4 address of the host.
    :param node_image:  The calico-node image to use.
    :param ip6:  The IPv6 address of the host (or None if not configured)
    :param as_num:  The BGP AS Number to use for this node.  If not specified
    the global default value will be used.
    :param detach: True to run in Docker's "detached" mode, False to run
    attached.
    :param libnetwork_image: The name of the Calico libnetwork driver image to
    use.  None, if not using libnetwork.
    :param no_pull: Boolean, True to prevent function from pulling the Calico
    node Docker images.
    :return:  None.
    """
    # The command has to be run as root to access iptables and services
    enforce_root()

    # Normally, Felix will load the modules it needs, but when running inside a
    # container it might not be able to do so. Ensure the required modules are
    # loaded each time the node starts.
    # We only make a best effort attempt because the command may fail if the
    # modules are built in.
    # We'll warn during the check_system() if the modules are unavailable.
    if not running_in_container():
        try:
            call(["modprobe", "-a"] + REQUIRED_MODULES)
        except OSError:
            pass

        _setup_ip_forwarding()
        _set_nf_conntrack_max()

        # Print warnings for any known system issues before continuing
        if runtime == 'docker' and not running_in_container():
            using_docker = True
        else:
            using_docker = False

        (_, docker_ok, etcd_ok) = \
            check_system(quit_if_error=False, libnetwork=libnetwork_image,
                         check_docker=using_docker,
                         check_modules=not running_in_container())

        if not etcd_ok or (using_docker and not docker_ok):
            sys.exit(1)

    # Ensure log directory exists
    if not os.path.exists(log_dir):
        os.makedirs(log_dir)

    # The format of the authority and endpoints strings have already been
    # validated.
    etcd_authority = os.getenv(ETCD_AUTHORITY_ENV, ETCD_AUTHORITY_DEFAULT)
    etcd_endpoints = os.getenv(ETCD_ENDPOINTS_ENV)

    # Get etcd SSL environment variables if they exist
    etcd_scheme = os.getenv(ETCD_SCHEME_ENV, ETCD_SCHEME_DEFAULT)
    etcd_key_file = os.getenv(ETCD_KEY_FILE_ENV)
    etcd_cert_file = os.getenv(ETCD_CERT_FILE_ENV)
    etcd_ca_cert_file = os.getenv(ETCD_CA_CERT_FILE_ENV)

    etcd_volumes = []
    etcd_binds = {}
    etcd_envs = [
        "ETCD_AUTHORITY=%s" % etcd_authority,
        "ETCD_SCHEME=%s" % etcd_scheme
    ]

    if etcd_endpoints:
        etcd_envs.append("ETCD_ENDPOINTS=%s" % etcd_endpoints)

    if etcd_ca_cert_file and etcd_key_file and etcd_cert_file:
        etcd_volumes.append(ETCD_CA_CERT_NODE_FILE)
        etcd_binds[etcd_ca_cert_file] = {
            "bind": ETCD_CA_CERT_NODE_FILE,
            "ro": True
        }
        etcd_envs.append("ETCD_CA_CERT_FILE=%s" % ETCD_CA_CERT_NODE_FILE)

        etcd_volumes.append(ETCD_KEY_NODE_FILE)
        etcd_binds[etcd_key_file] = {"bind": ETCD_KEY_NODE_FILE, "ro": True}
        etcd_envs.append("ETCD_KEY_FILE=%s" % ETCD_KEY_NODE_FILE)

        etcd_volumes.append(ETCD_CERT_NODE_FILE)
        etcd_binds[etcd_cert_file] = {"bind": ETCD_CERT_NODE_FILE, "ro": True}
        etcd_envs.append("ETCD_CERT_FILE=%s" % ETCD_CERT_NODE_FILE)

    if runtime == 'docker':
        _start_node_container_docker(ip, ip6, as_num, log_dir, node_image,
                                     detach, etcd_envs, etcd_volumes,
                                     etcd_binds, no_pull)
        if libnetwork_image:
            _start_libnetwork_container(libnetwork_image, etcd_envs,
                                        etcd_volumes, etcd_binds, no_pull)
    if runtime == 'rkt':
        _start_node_container_rkt(ip, ip6, as_num, node_image, etcd_envs,
                                  etcd_volumes, etcd_binds)