def provision_veth(self, namespace, veth_name_ns):
        """
        Create the veth, move into the container namespace, add the IP and
        set up the default routes.

        Note, the endpoint will not be updated in etcd. If desired, the user
        should update the endpoint mac with the mac address provided
        by the function and then call update_endpoint

        :param self: The endpoint object to provision the veth on
        :param namespace: The namespace to operate in
        :type namespace netns.Namespace
        :param veth_name_ns: The name of the interface in the namespace
        :return The mac address of the veth as a string
        """
        assert isinstance(namespace, netns.Namespace), \
            'Namespace object expected.'
        netns.create_veth(self.name, self.temp_interface_name)
        netns.move_veth_into_ns(namespace, self.temp_interface_name,
                                veth_name_ns)
        for ip_net in self.ipv4_nets | self.ipv6_nets:
            netns.add_ip_to_ns_veth(namespace, ip_net.ip, veth_name_ns)

        netns.add_ns_default_route(namespace, self.name, veth_name_ns)

        return netns.get_ns_veth_mac(namespace, veth_name_ns)
    def provision_veth(self, namespace, veth_name_ns):
        """
        Create the veth, move into the container namespace, add the IP and
        set up the default routes.

        Note, the endpoint will not be updated in etcd. If desired, the user
        should update the endpoint mac with the mac address provided
        by the function and then call update_endpoint

        :param self: The endpoint object to provision the veth on
        :param namespace: The namespace to operate in
        :type namespace netns.Namespace
        :param veth_name_ns: The name of the interface in the namespace
        :return The mac address of the veth as a string
        """
        assert isinstance(namespace, netns.Namespace), \
            'Namespace object expected.'
        netns.create_veth(self.name, self.temp_interface_name)
        netns.move_veth_into_ns(namespace, self.temp_interface_name,
                                veth_name_ns)
        for ip_net in self.ipv4_nets | self.ipv6_nets:
            netns.add_ip_to_ns_veth(namespace, ip_net.ip, veth_name_ns)

        netns.add_ns_default_route(namespace, self.name, veth_name_ns)

        return netns.get_ns_veth_mac(namespace, veth_name_ns)
 def test_create_veth(self, m_check_output):
     """
     Test creating a veth (pair).
     """
     create_veth("veth1", "temp_name")
     check_output_1 = call(
         ["ip", "link", "add", "veth1", "type", "veth", "peer", "name", "temp_name"], timeout=IP_CMD_TIMEOUT
     )
     check_output_2 = call(["ip", "link", "set", "veth1", "up"], timeout=IP_CMD_TIMEOUT)
     m_check_output.assert_has_calls([check_output_1, check_output_2])
def create_veth(endpoint):
    """
    Create the veth, and configure the MAC address (since libnetwork does not
    do this).
    :param endpoint: The Endpoint being configured.
    """
    # Create the veth pair.
    netns.create_veth(endpoint.name, endpoint.temp_interface_name)

    # Set the mac as libnetwork doesn't do this for us.
    netns.set_veth_mac(endpoint.temp_interface_name, endpoint.mac)
Exemple #5
0
 def test_create_veth(self, m_check_call):
     """
     Test creating a veth (pair).
     """
     create_veth("veth1", "temp_name")
     check_call_1 = call(['ip', 'link', 'add', "veth1", 'type',
                          'veth','peer', 'name', "temp_name"],
                         timeout=IP_CMD_TIMEOUT)
     check_call_2 = call(['ip', 'link', 'set', "veth1", 'up'],
                         timeout=IP_CMD_TIMEOUT)
     m_check_call.assert_has_calls([check_call_1, check_call_2])
def create_veth(endpoint):
    """
    Create the veth, and configure the MAC address (since libnetwork does not
    do this).
    :param endpoint: The Endpoint being configured.
    """
    # Create the veth pair.
    netns.create_veth(endpoint.name, endpoint.temp_interface_name)

    # Set the mac as libnetwork doesn't do this for us.
    netns.set_veth_mac(endpoint.temp_interface_name, endpoint.mac)
def join():
    json_data = request.get_json(force=True)
    app.logger.debug("Join JSON=%s", json_data)
    endpoint_id = json_data["EndpointID"]
    app.logger.info("Joining endpoint %s", endpoint_id)

    network_id = json_data["NetworkID"]
    network_data = client.get_network(network_id)
    ipv4_gateway = ""
    if network_data and "IPv4Data" in network_data.keys():
        ipv4_gateway = str(IPNetwork(network_data["IPv4Data"][0]["Gateway"]).ip)

    # The host interface name matches the name given when creating the endpoint
    # during CreateEndpoint
    host_interface_name = generate_cali_interface_name(IF_PREFIX, endpoint_id)

    # The temporary interface name is what gets passed to libnetwork.
    # Libnetwork renames the interface using the DstPrefix (e.g. cali0)
    temp_interface_name = generate_cali_interface_name("tmp", endpoint_id)

    try:
        # Create the veth pair.
        netns.create_veth(host_interface_name, temp_interface_name)

        # Set the mac as libnetwork doesn't do this for us (even if we return
        # it on the CreateNetwork)
        netns.set_veth_mac(temp_interface_name, FIXED_MAC)
    except CalledProcessError as e:
        # Failed to create or configure the veth, ensure veth is removed.
        remove_veth(host_interface_name)
        raise e

    return_json = {
        "InterfaceName": {"SrcName": temp_interface_name, "DstPrefix": IF_PREFIX},
        "Gateway": ipv4_gateway,  # Leave gateway empty to trigger auto-gateway behaviour
    }

    app.logger.debug("Join Response JSON=%s", return_json)
    return jsonify(return_json)
Exemple #8
0
def join():
    json_data = request.get_json(force=True)
    app.logger.debug("Join JSON=%s", json_data)
    endpoint_id = json_data["EndpointID"]
    app.logger.info("Joining endpoint %s", endpoint_id)

    # The host interface name matches the name given when creating the endpoint
    # during CreateEndpoint
    host_interface_name = generate_cali_interface_name(IF_PREFIX, endpoint_id)

    # The temporary interface name is what gets passed to libnetwork.
    # Libnetwork renames the interface using the DstPrefix (e.g. cali0)
    temp_interface_name = generate_cali_interface_name("tmp", endpoint_id)

    try:
        # Create the veth pair.
        netns.create_veth(host_interface_name, temp_interface_name)

        # Set the mac as libnetwork doesn't do this for us (even if we return
        # it on the CreateNetwork)
        netns.set_veth_mac(temp_interface_name, FIXED_MAC)
    except CalledProcessError as e:
        # Failed to create or configure the veth, ensure veth is removed.
        remove_veth(host_interface_name)
        raise e

    return_json = {
        "InterfaceName": {
            "SrcName": temp_interface_name,
            "DstPrefix": IF_PREFIX
        },
        "Gateway": "",  # Leave gateway empty to trigger auto-gateway behaviour
    }

    app.logger.debug("Join Response JSON=%s", return_json)
    return jsonify(return_json)
    def provision_veth(self, ns_pid, veth_name_ns):
        """
        Create the veth, move into the container namespace, add the IP and
        set up the default routes.

        Note, the endpoint will not be updated in etcd. If desired, the user
        should update the endpoint mac with the mac address provided
        by the function and then call update_endpoint

        :param self: The endpoint object to provision the veth on
        :param ns_pid: The PID of the namespace to operate in
        :param veth_name_ns: The name of the interface in the namespace
        :return The mac address of the veth as a string
        """
        netns.create_veth(self.name, self.temp_interface_name)
        netns.move_veth_into_ns(ns_pid, self.temp_interface_name, veth_name_ns)
        for ip_net in self.ipv4_nets | self.ipv6_nets:
            netns.add_ip_to_ns_veth(ns_pid, ip_net.ip, veth_name_ns)
            if ip_net.ip.version == 4:
                netns.add_ns_default_route(ns_pid, self.ipv4_gateway, veth_name_ns)
            else:
                netns.add_ns_default_route(ns_pid, self.ipv6_gateway, veth_name_ns)

        return netns.get_ns_veth_mac(ns_pid, veth_name_ns)
Exemple #10
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
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 #13
0
def join():
    json_data = request.get_json(force=True)
    app.logger.debug("Join JSON=%s", json_data)
    network_id = json_data["NetworkID"]
    endpoint_id = json_data["EndpointID"]
    app.logger.info("Joining endpoint %s", endpoint_id)

    # The host interface name matches the name given when creating the endpoint
    # during CreateEndpoint
    host_interface_name = generate_cali_interface_name(IF_PREFIX, endpoint_id)

    # The temporary interface name is what gets passed to libnetwork.
    # Libnetwork renames the interface using the DstPrefix (e.g. cali0)
    temp_interface_name = generate_cali_interface_name("tmp", endpoint_id)

    try:
        # Create the veth pair.
        netns.create_veth(host_interface_name, temp_interface_name)

        # Set the mac as libnetwork doesn't do this for us (even if we return
        # it on the CreateNetwork)
        netns.set_veth_mac(temp_interface_name, FIXED_MAC)
    except CalledProcessError as e:
        # Failed to create or configure the veth, ensure veth is removed.
        remove_veth(host_interface_name)
        raise e

    # Initialise our response data.
    json_response = {
        "InterfaceName": {
            "SrcName": temp_interface_name,
            "DstPrefix": IF_PREFIX,
        }
    }

    # Extract relevant data from the Network data.
    network_data = get_network_data(network_id)
    gateway_ip4, _ = get_gateway_pool_from_network_data(network_data, 4)
    gateway_ip6, _ = get_gateway_pool_from_network_data(network_data, 6)

    if (gateway_ip4 and is_using_calico_ipam(gateway_ip4)) or \
       (gateway_ip6 and is_using_calico_ipam(gateway_ip6)):
        # One of the network gateway addresses indicate that we are using
        # Calico IPAM driver.  In this case we setup routes using the gateways
        # configured on the endpoint (which will be our host IPs).
        app.logger.debug("Using Calico IPAM driver, configure gateway and "
                         "static routes to the host")
        ep = client.get_endpoint(hostname=hostname,
                                 orchestrator_id=ORCHESTRATOR_ID,
                                 workload_id=CONTAINER_NAME,
                                 endpoint_id=endpoint_id)
        static_routes = []
        if ep.ipv4_gateway:
            json_response["Gateway"] = str(ep.ipv4_gateway)
            static_routes.append({
                "Destination": str(IPNetwork(ep.ipv4_gateway)),
                "RouteType": 1,  # 1 = CONNECTED
                "NextHop": ""
            })
        if ep.ipv6_gateway:
            json_response["GatewayIPv6"] = str(ep.ipv6_gateway)
            static_routes.append({
                "Destination": str(IPNetwork(ep.ipv6_gateway)),
                "RouteType": 1,  # 1 = CONNECTED
                "NextHop": ""
            })
        json_response["StaticRoutes"] = static_routes
    else:
        # We are not using Calico IPAM driver, so configure blank gateways to
        # set up auto-gateway behavior.
        app.logger.debug("Not using Calico IPAM driver")
        json_response["Gateway"] = ""
        json_response["GatewayIPv6"] = ""

    app.logger.debug("Join Response JSON=%s", json_response)
    return jsonify(json_response)
def join():
    json_data = request.get_json(force=True)
    app.logger.debug("Join JSON=%s", json_data)
    network_id = json_data["NetworkID"]
    endpoint_id = json_data["EndpointID"]
    app.logger.info("Joining endpoint %s", endpoint_id)

    # The host interface name matches the name given when creating the endpoint
    # during CreateEndpoint
    host_interface_name = generate_cali_interface_name(IF_PREFIX, endpoint_id)

    # The temporary interface name is what gets passed to libnetwork.
    # Libnetwork renames the interface using the DstPrefix (e.g. cali0)
    temp_interface_name = generate_cali_interface_name("tmp", endpoint_id)

    try:
        # Create the veth pair.
        netns.create_veth(host_interface_name, temp_interface_name)

        # Set the mac as libnetwork doesn't do this for us (even if we return
        # it on the CreateNetwork)
        netns.set_veth_mac(temp_interface_name, FIXED_MAC)
    except CalledProcessError as e:
        # Failed to create or configure the veth, ensure veth is removed.
        remove_veth(host_interface_name)
        raise e

    # Initialise our response data.
    json_response = {
        "InterfaceName": {
            "SrcName": temp_interface_name,
            "DstPrefix": "eth",
        }
    }

    # Extract relevant data from the Network data.
    network_data = get_network_data(network_id)
    gateway_ip4, _ = get_gateway_pool_from_network_data(network_data, 4)
    gateway_ip6, _ = get_gateway_pool_from_network_data(network_data, 6)

    if (gateway_ip4 and is_using_calico_ipam(gateway_ip4)) or \
       (gateway_ip6 and is_using_calico_ipam(gateway_ip6)):
        # One of the network gateway addresses indicate that we are using
        # Calico IPAM driver.  In this case we setup routes using the gateways
        # configured on the endpoint (which will be our host IPs).
        app.logger.debug("Using Calico IPAM driver, configure gateway and "
                         "static routes to the host")
        static_routes = []
        if gateway_ip4:
            json_response["Gateway"] = DUMMY_IPV4_NEXTHOP
            static_routes.append({
                "Destination": DUMMY_IPV4_NEXTHOP + "/32",
                "RouteType": 1,  # 1 = CONNECTED
                "NextHop": ""
            })
        if gateway_ip6:
            # Here, we'll report the link local address of the host's cali interface to libnetwork
            # as our IPv6 gateway. IPv6 link local addresses are automatically assigned to interfaces
            # when they are brought up. Unfortunately, the container link must be up as well. So
            # bring it up now
            # TODO: create_veth should already bring up both links
            bring_up_interface(temp_interface_name)
            # Then extract the link local address that was just assigned to our host's interface
            next_hop_6 = get_next_hop_6(host_interface_name)
            json_response["GatewayIPv6"] = next_hop_6
            static_routes.append({
                "Destination": str(IPNetwork(next_hop_6)),
                "RouteType": 1,  # 1 = CONNECTED
                "NextHop": ""
            })
        json_response["StaticRoutes"] = static_routes
    else:
        # We are not using Calico IPAM driver, so configure blank gateways to
        # set up auto-gateway behavior.
        app.logger.debug("Not using Calico IPAM driver")
        json_response["Gateway"] = ""
        json_response["GatewayIPv6"] = ""

    app.logger.debug("Join Response JSON=%s", json_response)
    return jsonify(json_response)
def join():
    json_data = request.get_json(force=True)
    app.logger.debug("Join JSON=%s", json_data)
    network_id = json_data["NetworkID"]
    endpoint_id = json_data["EndpointID"]
    app.logger.info("Joining endpoint %s", endpoint_id)

    # The host interface name matches the name given when creating the endpoint
    # during CreateEndpoint
    host_interface_name = generate_cali_interface_name(IF_PREFIX, endpoint_id)

    # The temporary interface name is what gets passed to libnetwork.
    # Libnetwork renames the interface using the DstPrefix (e.g. cali0)
    temp_interface_name = generate_cali_interface_name("tmp", endpoint_id)

    try:
        # Create the veth pair.
        netns.create_veth(host_interface_name, temp_interface_name)

        # Set the mac as libnetwork doesn't do this for us (even if we return
        # it on the CreateNetwork)
        netns.set_veth_mac(temp_interface_name, FIXED_MAC)
    except CalledProcessError as e:
        # Failed to create or configure the veth, ensure veth is removed.
        remove_veth(host_interface_name)
        raise e

    # Initialise our response data.
    json_response = {
        "InterfaceName": {
            "SrcName": temp_interface_name,
            "DstPrefix": IF_PREFIX,
        }
    }

    # Extract relevant data from the Network data.
    network_data = get_network_data(network_id)
    gateway_ip4, _ = get_gateway_pool_from_network_data(network_data, 4)
    gateway_ip6, _ = get_gateway_pool_from_network_data(network_data, 6)

    if (gateway_ip4 and is_using_calico_ipam(gateway_ip4)) or \
       (gateway_ip6 and is_using_calico_ipam(gateway_ip6)):
        # One of the network gateway addresses indicate that we are using
        # Calico IPAM driver.  In this case we setup routes using the gateways
        # configured on the endpoint (which will be our host IPs).
        app.logger.debug("Using Calico IPAM driver, configure gateway and "
                         "static routes to the host")
        static_routes = []
        if gateway_ip4:
            json_response["Gateway"] = DUMMY_IPV4_NEXTHOP
            static_routes.append({
                "Destination": DUMMY_IPV4_NEXTHOP + "/32",
                "RouteType": 1,  # 1 = CONNECTED
                "NextHop": ""
            })
        if gateway_ip6:
            # Here, we'll report the link local address of the host's cali interface to libnetwork
            # as our IPv6 gateway. IPv6 link local addresses are automatically assigned to interfaces
            # when they are brought up. Unfortunately, the container link must be up as well. So
            # bring it up now
            # TODO: create_veth should already bring up both links
            bring_up_interface(temp_interface_name)
            # Then extract the link local address that was just assigned to our host's interface
            next_hop_6 = get_ipv6_link_local(host_interface_name)
            json_response["GatewayIPv6"] = next_hop_6
            static_routes.append({
                "Destination": str(IPNetwork(next_hop_6)),
                "RouteType": 1,  # 1 = CONNECTED
                "NextHop": ""
            })
        json_response["StaticRoutes"] = static_routes
    else:
        # We are not using Calico IPAM driver, so configure blank gateways to
        # set up auto-gateway behavior.
        app.logger.debug("Not using Calico IPAM driver")
        json_response["Gateway"] = ""
        json_response["GatewayIPv6"] = ""

    app.logger.debug("Join Response JSON=%s", json_response)
    return jsonify(json_response)
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
def join():
    json_data = request.get_json(force=True)
    app.logger.debug("Join JSON=%s", json_data)
    network_id = json_data["NetworkID"]
    endpoint_id = json_data["EndpointID"]
    app.logger.info("Joining endpoint %s", endpoint_id)

    # The host interface name matches the name given when creating the endpoint
    # during CreateEndpoint
    host_interface_name = generate_cali_interface_name(IF_PREFIX, endpoint_id)

    # The temporary interface name is what gets passed to libnetwork.
    # Libnetwork renames the interface using the DstPrefix (e.g. cali0)
    temp_interface_name = generate_cali_interface_name("tmp", endpoint_id)

    try:
        # Create the veth pair.
        netns.create_veth(host_interface_name, temp_interface_name)

        # Set the mac as libnetwork doesn't do this for us (even if we return
        # it on the CreateNetwork)
        netns.set_veth_mac(temp_interface_name, FIXED_MAC)
    except CalledProcessError as e:
        # Failed to create or configure the veth, ensure veth is removed.
        remove_veth(host_interface_name)
        raise e

    # Initialise our response data.
    json_response = {
        "InterfaceName": {
            "SrcName": temp_interface_name,
            "DstPrefix": IF_PREFIX,
        }
    }

    # Extract relevant data from the Network data.
    network_data = get_network_data(network_id)
    gateway_ip4, _ = get_gateway_pool_from_network_data(network_data, 4)
    gateway_ip6, _ = get_gateway_pool_from_network_data(network_data, 6)

    if (gateway_ip4 and is_using_calico_ipam(gateway_ip4)) or \
       (gateway_ip6 and is_using_calico_ipam(gateway_ip6)):
        # One of the network gateway addresses indicate that we are using
        # Calico IPAM driver.  In this case we setup routes using the gateways
        # configured on the endpoint (which will be our host IPs).
        app.logger.debug("Using Calico IPAM driver, configure gateway and "
                         "static routes to the host")
        ep = client.get_endpoint(hostname=hostname,
                                 orchestrator_id=ORCHESTRATOR_ID,
                                 workload_id=CONTAINER_NAME,
                                 endpoint_id=endpoint_id)
        static_routes = []
        if ep.ipv4_gateway:
            json_response["Gateway"] = str(ep.ipv4_gateway)
            static_routes.append({
                "Destination": str(IPNetwork(ep.ipv4_gateway)),
                "RouteType": 1,  # 1 = CONNECTED
                "NextHop": ""
            })
        if ep.ipv6_gateway:
            json_response["GatewayIPv6"] = str(ep.ipv6_gateway)
            static_routes.append({
                "Destination": str(IPNetwork(ep.ipv6_gateway)),
                "RouteType": 1,  # 1 = CONNECTED
                "NextHop": ""
            })
        json_response["StaticRoutes"] = static_routes
    else:
        # We are not using Calico IPAM driver, so configure blank gateways to
        # set up auto-gateway behavior.
        app.logger.debug("Not using Calico IPAM driver")
        json_response["Gateway"] = ""
        json_response["GatewayIPv6"] = ""

    app.logger.debug("Join Response JSON=%s", json_response)
    return jsonify(json_response)