def test_join_veth_fail(self, m_set, m_del_veth, m_veth, m_read, m_next_hops):
        """
        Test the join() processing when create_veth fails.
        """
        m_veth.side_effect = CalledProcessError(2, "testcmd")

        endpoint_json = {"Interfaces": [{"Address": "1.2.3.4", "ID": 0, "MacAddress": "EE:EE:EE:EE:EE:EE"}]}
        m_read.return_value = endpoint_json

        m_next_hops.return_value = {4: IPAddress("1.2.3.4"), 6: None}

        # Actually make the request to the plugin.
        rv = self.app.post(
            "/NetworkDriver.Join", data='{"EndpointID": "%s", "NetworkID": "%s"}' % (TEST_ENDPOINT_ID, TEST_NETWORK_ID)
        )
        m_read.assert_called_once_with(TEST_ENDPOINT_ID)

        # Check that the create_veth is called with this
        # endpoint.
        endpoint = Endpoint("hostname", "docker", "libnetwork", TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")
        endpoint.ipv4_gateway = IPAddress("1.2.3.4")
        endpoint.ipv4_nets.add(IPNetwork("1.2.3.4/32"))
        endpoint.profile_ids.append(TEST_NETWORK_ID)

        # Check that create veth is called with the expected endpoint, and
        # that set_endpoint is not (since create_veth is raising an exception).
        m_veth.assert_called_once_with(endpoint)
        assert_equal(m_set.call_count, 0)

        # Check that we delete the veth.
        m_del_veth.assert_called_once_with(endpoint)

        # Expect a 500 response.
        self.assertDictEqual(json.loads(rv.data), ERROR_RESPONSE_500)
    def test_join_set_fail(self, m_set, m_del_veth, m_veth, m_read, m_next_hops):
        """
        Test the join() processing when set_endpoint fails.
        """
        endpoint_json = {"Interfaces": [{"Address": "1.2.3.4", "ID": 0, "MacAddress": "EE:EE:EE:EE:EE:EE"}]}
        m_read.return_value = endpoint_json

        m_next_hops.return_value = {4: IPAddress("1.2.3.4"), 6: None}

        # Actually make the request to the plugin.
        rv = self.app.post(
            "/NetworkDriver.Join", data='{"EndpointID": "%s", "NetworkID": "%s"}' % (TEST_ENDPOINT_ID, TEST_NETWORK_ID)
        )
        m_read.assert_called_once_with(TEST_ENDPOINT_ID)

        # Check that the create_veth is called with this
        # endpoint.
        endpoint = Endpoint("hostname", "docker", "libnetwork", TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")
        endpoint.ipv4_gateway = IPAddress("1.2.3.4")
        endpoint.ipv4_nets.add(IPNetwork("1.2.3.4/32"))
        endpoint.profile_ids.append(TEST_NETWORK_ID)

        # Check that create veth and set_endpoint are called with the
        # endpoint.  The set throws a DataStoreError and so we clean up the
        # veth.
        m_veth.assert_called_once_with(endpoint)
        m_set.assert_called_once_with(endpoint)

        # Check that we delete the veth.
        m_del_veth.assert_called_once_with(endpoint)

        # Expect a 500 response.
        self.assertDictEqual(json.loads(rv.data), ERROR_RESPONSE_500)
예제 #3
0
def create_endpoint():
    json_data = request.get_json(force=True)
    app.logger.debug("CreateEndpoint JSON=%s", json_data)
    endpoint_id = json_data["EndpointID"]
    network_id = json_data["NetworkID"]
    interface = json_data["Interface"]

    app.logger.info("Creating endpoint %s", endpoint_id)

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

    # Get the addresses to use from the request JSON.
    address_ip4 = interface.get("Address")
    address_ip6 = interface.get("AddressIPv6")
    assert address_ip4 or address_ip6, "No address assigned for endpoint"

    # Create a Calico endpoint object.
    ep = Endpoint(hostname, ORCHESTRATOR_ID, CONTAINER_NAME, endpoint_id,
                  "active", FIXED_MAC)
    ep.profile_ids.append(network_id)

    # If either gateway indicates that we are using Calico IPAM driver then
    # our next hops are our host IPs.  Extract these from the datastore.
    # Note that we assume we cannot have a mixture of IPv4 and IPv6 using
    # different drivers.
    if (gateway_cidr4 and is_using_calico_ipam(gateway_cidr4)) or \
       (gateway_cidr6 and is_using_calico_ipam(gateway_cidr6)):
        app.logger.debug("Using Calico IPAM driver, get next hops")
        next_hops = client.get_default_next_hops(hostname=hostname)
        gateway_ip4 = next_hops.get(4)
        gateway_ip6 = next_hops.get(6)
    else:
        gateway_ip4 = gateway_cidr4.ip if gateway_cidr4 else None
        gateway_ip6 = gateway_cidr6.ip if gateway_cidr6 else None

    if address_ip4:
        ep.ipv4_nets.add(IPNetwork(address_ip4))
        ep.ipv4_gateway = gateway_ip4

    if address_ip6:
        ep.ipv6_nets.add(IPNetwork(address_ip6))
        ep.ipv6_gateway = gateway_ip6

    app.logger.debug("Saving Calico endpoint: %s", ep)
    client.set_endpoint(ep)

    json_response = {
        "Interface": {
            "MacAddress": FIXED_MAC,
        }
    }

    app.logger.debug("CreateEndpoint response JSON=%s", json_response)
    return jsonify(json_response)
예제 #4
0
def create_endpoint():
    json_data = request.get_json(force=True)
    app.logger.debug("CreateEndpoint JSON=%s", json_data)
    endpoint_id = json_data["EndpointID"]
    network_id = json_data["NetworkID"]
    interface = json_data["Interface"]

    app.logger.info("Creating endpoint %s", endpoint_id)

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

    # Get the addresses to use from the request JSON.
    address_ip4 = interface.get("Address")
    address_ip6 = interface.get("AddressIPv6")
    assert address_ip4 or address_ip6, "No address assigned for endpoint"

    # Create a Calico endpoint object.
    ep = Endpoint(hostname, ORCHESTRATOR_ID, CONTAINER_NAME, endpoint_id,
                  "active", FIXED_MAC)
    ep.profile_ids.append(network_id)

    # If either gateway indicates that we are using Calico IPAM driver then
    # our next hops are our host IPs.  Extract these from the datastore.
    # Note that we assume we cannot have a mixture of IPv4 and IPv6 using
    # different drivers.
    if (gateway_cidr4 and is_using_calico_ipam(gateway_cidr4)) or \
       (gateway_cidr6 and is_using_calico_ipam(gateway_cidr6)):
        app.logger.debug("Using Calico IPAM driver, get next hops")
        next_hops = client.get_default_next_hops(hostname=hostname)
        gateway_ip4 = next_hops.get(4)
        gateway_ip6 = next_hops.get(6)
    else:
        gateway_ip4 = gateway_cidr4.ip if gateway_cidr4 else None
        gateway_ip6 = gateway_cidr6.ip if gateway_cidr6 else None

    if address_ip4:
        ep.ipv4_nets.add(IPNetwork(address_ip4))
        ep.ipv4_gateway = gateway_ip4

    if address_ip6:
        ep.ipv6_nets.add(IPNetwork(address_ip6))
        ep.ipv6_gateway = gateway_ip6

    app.logger.debug("Saving Calico endpoint: %s", ep)
    client.set_endpoint(ep)

    json_response = {
        "Interface": {
            "MacAddress": FIXED_MAC,
        }
    }

    app.logger.debug("CreateEndpoint response JSON=%s", json_response)
    return jsonify(json_response)
예제 #5
0
    def test_create_endpoint(self, m_set, m_get_network):
        """
        Test the create_endpoint hook correctly writes the appropriate data
        to etcd based on IP assignment.
        """

        # Iterate using various different mixtures of IP assignments.
        # (IPv4 addr, IPv6 addr)
        parms = [(None, IPAddress("aa:bb::bb")),
                 (IPAddress("10.20.30.40"), None),
                 (IPAddress("10.20.30.40"), IPAddress("aa:bb::bb"))]

        m_get_network.return_value = {
            "NetworkID": TEST_NETWORK_ID,
            "IPv4Data": [{
                "Gateway": "6.5.4.3"
            }],
            "IPv6Data": [{
                "Gateway": "aa:bb::cc"
            }]
        }

        # Loop through different combinations of IP availability.
        for ipv4, ipv6 in parms:
            ipv4_json = ',"Address": "%s"' % ipv4 if ipv4 else ""
            ipv6_json = ',"AddressIPv6": "%s"' % ipv6 if ipv6 else ""

            # Invoke create endpoint.
            rv = self.app.post(
                '/NetworkDriver.CreateEndpoint',
                data='{"EndpointID": "%s",'
                '"NetworkID":  "%s",'
                '"Interface": {"MacAddress": "EE:EE:EE:EE:EE:EE"%s%s}}' %
                (TEST_ENDPOINT_ID, TEST_NETWORK_ID, ipv4_json, ipv6_json))

            # Assert expected data is written to etcd
            ep = Endpoint(hostname, "libnetwork", "libnetwork",
                          TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")

            ep.profile_ids.append(TEST_NETWORK_ID)

            if ipv4:
                ep.ipv4_nets.add(IPNetwork(ipv4))
                ep.ipv4_gateway = IPAddress("6.5.4.3")

            if ipv6:
                ep.ipv6_nets.add(IPNetwork(ipv6))
                ep.ipv6_gateway = IPAddress("aa:bb::cc")

            m_set.assert_called_once_with(ep)

            # Assert return value
            self.assertDictEqual(json.loads(rv.data), {})

            # Reset the Mocks before continuing.
            m_set.reset_mock()
    def test_create_endpoint(self, m_set, m_get_network):
        """
        Test the create_endpoint hook correctly writes the appropriate data
        to etcd based on IP assignment.
        """

        # Iterate using various different mixtures of IP assignments.
        # (IPv4 addr, IPv6 addr)
        parms = [
            (None, IPAddress("aa:bb::bb")),
            (IPAddress("10.20.30.40"), None),
            (IPAddress("10.20.30.40"), IPAddress("aa:bb::bb")),
        ]

        m_get_network.return_value = {
            "NetworkID": TEST_NETWORK_ID,
            "IPv4Data": [{"Gateway": "6.5.4.3"}],
            "IPv6Data": [{"Gateway": "aa:bb::cc"}],
        }

        # Loop through different combinations of IP availability.
        for ipv4, ipv6 in parms:
            ipv4_json = ',"Address": "%s"' % ipv4 if ipv4 else ""
            ipv6_json = ',"AddressIPv6": "%s"' % ipv6 if ipv6 else ""

            # Invoke create endpoint.
            rv = self.app.post(
                "/NetworkDriver.CreateEndpoint",
                data='{"EndpointID": "%s",'
                '"NetworkID":  "%s",'
                '"Interface": {"MacAddress": "EE:EE:EE:EE:EE:EE"%s%s}}'
                % (TEST_ENDPOINT_ID, TEST_NETWORK_ID, ipv4_json, ipv6_json),
            )

            # Assert expected data is written to etcd
            ep = Endpoint(hostname, "libnetwork", "libnetwork", TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")

            ep.profile_ids.append(TEST_NETWORK_ID)

            if ipv4:
                ep.ipv4_nets.add(IPNetwork(ipv4))
                ep.ipv4_gateway = IPAddress("6.5.4.3")

            if ipv6:
                ep.ipv6_nets.add(IPNetwork(ipv6))
                ep.ipv6_gateway = IPAddress("aa:bb::cc")

            m_set.assert_called_once_with(ep)

            # Assert return value
            self.assertDictEqual(json.loads(rv.data), {})

            # Reset the Mocks before continuing.
            m_set.reset_mock()
    def test_join(self):
        endpoint_mock = Mock()
        endpoint = Endpoint("hostname",
                            "docker",
                            "undefined",
                            TEST_ID,
                            "active",
                            "mac")
        endpoint.ipv4_gateway = IPAddress("1.2.3.4")
        endpoint.ipv6_gateway = IPAddress("FE80::0202:B3FF:FE1E:8329")
        endpoint.ipv4_nets.add(IPNetwork("1.2.3.4/24"))
        endpoint.ipv6_nets.add(IPNetwork("FE80::0202:B3FF:FE1E:8329/128"))
        endpoint_mock.return_value = endpoint
        docker_plugin.client.get_endpoint = endpoint_mock

        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s"}' % TEST_ID)
        endpoint_mock.assert_called_once_with(hostname=ANY,
                                              orchestrator_id="docker",
                                              workload_id="libnetwork",
                                              endpoint_id=TEST_ID)

        expected_response = """{
  "Gateway": "1.2.3.4",
  "GatewayIPv6": "fe80::202:b3ff:fe1e:8329",
  "InterfaceNames": [
    {
      "DstPrefix": "cali",
      "SrcName": "tmpTEST_ID"
    }
  ],
  "StaticRoutes": [
    {
      "Destination": "1.2.3.4/32",
      "InterfaceID": 0,
      "NextHop": "",
      "RouteType": 1
    },
    {
      "Destination": "fe80::202:b3ff:fe1e:8329/128",
      "InterfaceID": 0,
      "NextHop": "",
      "RouteType": 1
    }
  ]
}"""
        assert_dict_equal(json.loads(rv.data),
                          json.loads(expected_response))
예제 #8
0
    def test_leave_delete_failed(self, m_remove, m_get, m_veth):
        """
        Test the leave processing when these is no endpoint.
        """
        endpoint = Endpoint("hostname",
                            "docker",
                            "libnetwork",
                            TEST_ENDPOINT_ID,
                            "active",
                            "EE:EE:EE:EE:EE:EE")
        m_get.return_value = endpoint
        m_remove.side_effect = DataStoreError

        # Send the leave request.
        rv = self.app.post('/NetworkDriver.Leave',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        self.assertDictEqual(json.loads(rv.data), {})

        # Check parameters
        m_get.assert_called_once_with(hostname=ANY,
                                      orchestrator_id="docker",
                                      workload_id="libnetwork",
                                      endpoint_id=TEST_ENDPOINT_ID)
        m_remove.assert_called_once_with(endpoint)
        m_veth.assert_called_once_with(endpoint)
예제 #9
0
    def test_join_veth_fail(self, m_set, m_del_veth, m_veth, m_read, m_next_hops):
        """
        Test the join() processing when create_veth fails.
        """
        m_veth.side_effect = CalledProcessError(2, "testcmd")

        endpoint_json = {"Interfaces":
                          [
                            {"Address": "1.2.3.4",
                             "ID": 0,
                             "MacAddress": "EE:EE:EE:EE:EE:EE"}
                          ]
                        }
        m_read.return_value = endpoint_json

        m_next_hops.return_value = {4: IPAddress("1.2.3.4"),
                                    6: None}

        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s", "NetworkID": "%s"}' %
                                (TEST_ENDPOINT_ID, TEST_NETWORK_ID))
        m_read.assert_called_once_with(TEST_ENDPOINT_ID)

        # Check that the create_veth is called with this
        # endpoint.
        endpoint = Endpoint("hostname",
                            "docker",
                            "libnetwork",
                            TEST_ENDPOINT_ID,
                            "active",
                            "EE:EE:EE:EE:EE:EE")
        endpoint.ipv4_gateway = IPAddress("1.2.3.4")
        endpoint.ipv4_nets.add(IPNetwork("1.2.3.4/32"))
        endpoint.profile_ids.append(TEST_NETWORK_ID)

        # Check that create veth is called with the expected endpoint, and
        # that set_endpoint is not (since create_veth is raising an exception).
        m_veth.assert_called_once_with(endpoint)
        assert_equal(m_set.call_count, 0)

        # Check that we delete the veth.
        m_del_veth.assert_called_once_with(endpoint)

        # Expect a 500 response.
        self.assertDictEqual(json.loads(rv.data), ERROR_RESPONSE_500)
예제 #10
0
    def test_join_set_fail(self, m_set, m_del_veth, m_veth, m_read, m_next_hops):
        """
        Test the join() processing when set_endpoint fails.
        """
        endpoint_json = {"Interfaces":
                          [
                            {"Address": "1.2.3.4",
                             "ID": 0,
                             "MacAddress": "EE:EE:EE:EE:EE:EE"}
                          ]
                        }
        m_read.return_value = endpoint_json

        m_next_hops.return_value = {4: IPAddress("1.2.3.4"),
                                    6: None}

        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s", "NetworkID": "%s"}' %
                                (TEST_ENDPOINT_ID, TEST_NETWORK_ID))
        m_read.assert_called_once_with(TEST_ENDPOINT_ID)

        # Check that the create_veth is called with this
        # endpoint.
        endpoint = Endpoint("hostname",
                            "docker",
                            "libnetwork",
                            TEST_ENDPOINT_ID,
                            "active",
                            "EE:EE:EE:EE:EE:EE")
        endpoint.ipv4_gateway = IPAddress("1.2.3.4")
        endpoint.ipv4_nets.add(IPNetwork("1.2.3.4/32"))
        endpoint.profile_ids.append(TEST_NETWORK_ID)

        # Check that create veth and set_endpoint are called with the
        # endpoint.  The set throws a DataStoreError and so we clean up the
        # veth.
        m_veth.assert_called_once_with(endpoint)
        m_set.assert_called_once_with(endpoint)

        # Check that we delete the veth.
        m_del_veth.assert_called_once_with(endpoint)

        # Expect a 500 response.
        self.assertDictEqual(json.loads(rv.data), ERROR_RESPONSE_500)
예제 #11
0
    def get_endpoints(self, hostname=None, orchestrator_id=None, workload_id=None, endpoint_id=None):
        """
        Optimized function to get endpoint(s).

        Constructs a etcd-path that it as specific as possible given the
        provided criteria, in order to return the smallest etcd tree as
        possible. After querying with the ep_path, it will then compare the
        returned endpoints to the provided criteria, and return all matches.

        :param endpoint_id: The ID of the endpoint
        :param hostname: The hostname that the endpoint lives on.
        :param workload_id: The workload that the endpoint belongs to.
        :param orchestrator_id: The workload that the endpoint belongs to.
        :return: A list of Endpoint Objects which match the criteria, or an
        empty list if none match
        """
        # First build the query string as specific as possible. Note, we want
        # the query to be as specific as possible, so we proceed any variables
        # with known constants e.g. we add '/workload' after the hostname
        # variable.
        if not hostname:
            ep_path = HOSTS_PATH
        elif not orchestrator_id:
            ep_path = HOST_PATH % {"hostname": hostname}
        elif not workload_id:
            ep_path = ORCHESTRATOR_PATH % {"hostname": hostname, "orchestrator_id": orchestrator_id}
        elif not endpoint_id:
            ep_path = WORKLOAD_PATH % {
                "hostname": hostname,
                "orchestrator_id": orchestrator_id,
                "workload_id": workload_id,
            }
        else:
            ep_path = ENDPOINT_PATH % {
                "hostname": hostname,
                "orchestrator_id": orchestrator_id,
                "workload_id": workload_id,
                "endpoint_id": endpoint_id,
            }
        try:
            # Search etcd
            leaves = self.etcd_client.read(ep_path, recursive=True).leaves
        except EtcdKeyNotFound:
            return []

        # Filter through result
        matches = []
        for leaf in leaves:
            endpoint = Endpoint.from_json(leaf.key, leaf.value)

            # If its an endpoint, compare it to search criteria
            if endpoint and endpoint.matches(
                hostname=hostname, orchestrator_id=orchestrator_id, workload_id=workload_id, endpoint_id=endpoint_id
            ):
                matches.append(endpoint)
        return matches
예제 #12
0
    def test_join_calico_ipam(self, m_create_veth, m_set_mac, m_get_network,
                              m_get_endpoint, m_get_link_local, m_intf_up):
        """
        Test the join() processing with Calico IPAM.
        """
        m_get_network.return_value = {
            "NetworkID": TEST_NETWORK_ID,
            "IPv4Data": [{
                "Gateway": "0.0.0.0/0",
                "Pool": "0.0.0.0/0"
            }],
            "IPv6Data": [{
                "Gateway": "::/0",
                "Pool": "::/0"
            }]
        }
        m_get_endpoint.return_value = Endpoint(hostname, "libnetwork",
                                               "docker", TEST_ENDPOINT_ID,
                                               None, None)

        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s", "NetworkID": "%s"}' %
                           (TEST_ENDPOINT_ID, TEST_NETWORK_ID))

        host_interface_name = generate_cali_interface_name(
            IF_PREFIX, TEST_ENDPOINT_ID)
        temp_interface_name = generate_cali_interface_name(
            "tmp", TEST_ENDPOINT_ID)

        m_create_veth.assert_called_once_with(host_interface_name,
                                              temp_interface_name)
        m_set_mac.assert_called_once_with(temp_interface_name,
                                          "EE:EE:EE:EE:EE:EE")

        expected_data = {
            "Gateway":
            "169.254.1.1",
            "GatewayIPv6":
            "fe80::1/128",
            "InterfaceName": {
                "DstPrefix": "cali",
                "SrcName": "tmpTEST_ENDPOI"
            },
            "StaticRoutes": [{
                "Destination": "169.254.1.1/32",
                "RouteType": 1,
                "NextHop": ""
            }, {
                "Destination": "fe80::1/128",
                "RouteType": 1,
                "NextHop": ""
            }]
        }
        self.maxDiff = None
        self.assertDictEqual(json.loads(rv.data), expected_data)
def create_endpoint():
    json_data = request.get_json(force=True)
    app.logger.debug("CreateEndpoint JSON=%s", json_data)
    endpoint_id = json_data["EndpointID"]
    network_id = json_data["NetworkID"]
    interface = json_data["Interface"]

    app.logger.info("Creating endpoint %s", endpoint_id)

    # Get the addresses to use from the request JSON.
    address_ip4 = interface.get("Address")
    address_ip6 = interface.get("AddressIPv6")
    assert address_ip4 or address_ip6

    # Get the gateway from the data passed in during CreateNetwork
    network_data = client.get_network(network_id)

    if not network_data:
        error_message = "CreateEndpoint called but network doesn't exist" \
                        " Endpoint ID: %s Network ID: %s" % \
                        (endpoint_id, network_id)
        app.logger.error(error_message)
        raise Exception(error_message)

    # Create a Calico endpoint object.
    ep = Endpoint(hostname, ORCHESTRATOR_ID, CONTAINER_NAME, endpoint_id,
                  "active", FIXED_MAC)
    ep.profile_ids.append(network_id)

    if address_ip4:
        ep.ipv4_nets.add(IPNetwork(address_ip4))
        gateway_net = IPNetwork(network_data['IPv4Data'][0]['Gateway'])
        ep.ipv4_gateway = gateway_net.ip

    if address_ip6:
        ep.ipv6_nets.add(IPNetwork(address_ip6))
        gateway_net = IPNetwork(network_data['IPv6Data'][0]['Gateway'])
        ep.ipv6_gateway = gateway_net.ip

    client.set_endpoint(ep)

    app.logger.debug("CreateEndpoint response JSON=%s", {})
    return jsonify({})
    def test_remove_veth_fail(self, m_remove):
        """
        Test remove_veth calls through to netns to remove the veth.
        Fail with a CalledProcessError to write the log.
        """
        endpoint = Endpoint("hostname", "docker", "libnetwork",
                            TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")

        docker_plugin.remove_veth(endpoint)
        m_remove.assert_called_once_with(endpoint.name)
 def test_delete_endpoint_fail(self, m_remove):
     """
     Test delete_endpoint() deletes the endpoint and backout IP assignment.
     """
     rv = self.app.post('/NetworkDriver.DeleteEndpoint',
                        data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
     m_remove.assert_called_once_with(
         Endpoint(hostname, "libnetwork", "docker", TEST_ENDPOINT_ID, None,
                  None))
     self.assertDictEqual(json.loads(rv.data), {u'Err': u''})
예제 #16
0
def create_endpoint():
    json_data = request.get_json(force=True)
    app.logger.debug("CreateEndpoint JSON=%s", json_data)
    endpoint_id = json_data["EndpointID"]
    network_id = json_data["NetworkID"]
    interface = json_data["Interface"]

    app.logger.info("Creating endpoint %s", endpoint_id)

    # Get the addresses to use from the request JSON.
    address_ip4 = interface.get("Address")
    address_ip6 = interface.get("AddressIPv6")
    assert address_ip4 or address_ip6

    # Get the gateway from the data passed in during CreateNetwork
    network_data = client.get_network(network_id)

    if not network_data:
        error_message = "CreateEndpoint called but network doesn't exist" \
                        " Endpoint ID: %s Network ID: %s" % \
                        (endpoint_id, network_id)
        app.logger.error(error_message)
        raise Exception(error_message)

    # Create a Calico endpoint object.
    ep = Endpoint(hostname, ORCHESTRATOR_ID, CONTAINER_NAME, endpoint_id,
                  "active", FIXED_MAC)
    ep.profile_ids.append(network_id)

    if address_ip4:
        ep.ipv4_nets.add(IPNetwork(address_ip4))
        gateway_net = IPNetwork(network_data['IPv4Data'][0]['Gateway'])
        ep.ipv4_gateway = gateway_net.ip

    if address_ip6:
        ep.ipv6_nets.add(IPNetwork(address_ip6))
        gateway_net = IPNetwork(network_data['IPv6Data'][0]['Gateway'])
        ep.ipv6_gateway = gateway_net.ip

    client.set_endpoint(ep)

    app.logger.debug("CreateEndpoint response JSON=%s", {})
    return jsonify({})
예제 #17
0
    def get_endpoints(self, hostname=None, orchestrator_id=None,
                      workload_id=None, endpoint_id=None):
        """
        Optimized function to get endpoint(s).

        Constructs a etcd-path that it as specific as possible given the
        provided criteria, in order to return the smallest etcd tree as
        possible. After querying with the ep_path, it will then compare the
        returned endpoints to the provided criteria, and return all matches.

        :param endpoint_id: The ID of the endpoint
        :param hostname: The hostname that the endpoint lives on.
        :param workload_id: The workload that the endpoint belongs to.
        :param orchestrator_id: The workload that the endpoint belongs to.
        :return: A list of Endpoint Objects which match the criteria, or an
        empty list if none match
        """
        # First build the query string as specific as possible. Note, we want
        # the query to be as specific as possible, so we proceed any variables
        # with known constants e.g. we add '/workload' after the hostname
        # variable.
        if not hostname:
            ep_path = HOSTS_PATH
        elif not orchestrator_id:
            ep_path = HOST_PATH % {"hostname": hostname}
        elif not workload_id:
            ep_path = ORCHESTRATOR_PATH % {"hostname": hostname,
                                           "orchestrator_id": orchestrator_id}
        elif not endpoint_id:
            ep_path = WORKLOAD_PATH % {"hostname": hostname,
                                       "orchestrator_id": orchestrator_id,
                                       "workload_id": workload_id}
        else:
            ep_path = ENDPOINT_PATH % {"hostname": hostname,
                                       "orchestrator_id": orchestrator_id,
                                       "workload_id": workload_id,
                                       "endpoint_id": endpoint_id}
        try:
            # Search etcd
            leaves = self.etcd_client.read(ep_path, recursive=True).leaves
        except EtcdKeyNotFound:
            return []

        # Filter through result
        matches = []
        for leaf in leaves:
            endpoint = Endpoint.from_json(leaf.key, leaf.value)

            # If its an endpoint, compare it to search criteria
            if endpoint and endpoint.matches(hostname=hostname,
                                             orchestrator_id=orchestrator_id,
                                             workload_id=workload_id,
                                             endpoint_id=endpoint_id):
                matches.append(endpoint)
        return matches
예제 #18
0
    def create_endpoint(self,
                        hostname,
                        orchestrator_id,
                        workload_id,
                        ip_list,
                        mac=None):
        """
        Create a single new endpoint object.

        Note, the endpoint will not be stored in ETCD until set_endpoint
        or update_endpoint is called.

        :param hostname: The hostname that the endpoint lives on.
        :param orchestrator_id: The workload that the endpoint belongs to.
        :param workload_id: The workload that the endpoint belongs to.
        :param ip_list: A list of ip addresses that the endpoint belongs to
        :param mac: The mac address that the endpoint belongs to
        :return: An Endpoint Object
        """
        ep = Endpoint(hostname=hostname,
                      orchestrator_id=orchestrator_id,
                      workload_id=workload_id,
                      endpoint_id=uuid.uuid1().hex,
                      state="active",
                      mac=mac)
        next_hops = self.get_default_next_hops(hostname)

        for ip in ip_list:
            try:
                next_hop = next_hops[ip.version]
            except KeyError:
                raise AddrFormatError(
                    "This node is not configured for IPv%d." % ip.version)
            network = IPNetwork(ip)
            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

        return ep
    def test_status_no_ip(self, m_print):
        """Test Pod Status Hook: No IP on Endpoint

        Test for sys exit when endpoint ipv4_nets is empty.
        """
        # Set up args
        namespace = "ns"
        pod_name = "pod1"
        docker_id = 123456789101112

        # Call method under test
        endpoint = Endpoint(TEST_HOST, TEST_ORCH_ID, "1234", "5678", "active", "mac")
        endpoint.ipv4_nets = None
        endpoint.ipv6_nets = None
        self.m_datastore_client.get_endpoint.return_value = endpoint

        assert_raises(SystemExit, self.plugin.status, namespace, pod_name, docker_id)

        # Prints are read externally. We don't want to print in a fail state.
        assert_false(m_print.called)
예제 #20
0
def delete_endpoint():
    json_data = request.get_json(force=True)
    app.logger.debug("DeleteEndpoint JSON=%s", json_data)
    endpoint_id = json_data["EndpointID"]
    app.logger.info("Removing endpoint %s", endpoint_id)

    client.remove_endpoint(Endpoint(hostname, ORCHESTRATOR_ID, CONTAINER_NAME,
                                    endpoint_id, None, None))

    app.logger.debug("DeleteEndpoint response JSON=%s", "{}")
    return jsonify({})
    def test_create_veth(self, m_create, m_set):
        """
        Test create_veth calls through to netns to create the veth and
        set the MAC.
        """
        endpoint = Endpoint("hostname", "docker", "libnetwork",
                            TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")

        docker_plugin.create_veth(endpoint)
        m_create.assert_called_once_with(endpoint.name,
                                         endpoint.temp_interface_name)
        m_set.assert_called_once_with(endpoint.temp_interface_name,
                                      endpoint.mac)
예제 #22
0
    def create_endpoint(self, hostname, orchestrator_id, workload_id,
                        ip_list, mac=None):
        """
        Create a single new endpoint object.

        Note, the endpoint will not be stored in ETCD until set_endpoint
        or update_endpoint is called.

        :param hostname: The hostname that the endpoint lives on.
        :param orchestrator_id: The workload that the endpoint belongs to.
        :param workload_id: The workload that the endpoint belongs to.
        :param ip_list: A list of ip addresses that the endpoint belongs to
        :param mac: The mac address that the endpoint belongs to
        :return: An Endpoint Object
        """
        ep = Endpoint(hostname=hostname,
                      orchestrator_id=orchestrator_id,
                      workload_id=workload_id,
                      endpoint_id=uuid.uuid1().hex,
                      state="active",
                      mac=mac)
        next_hops = self.get_default_next_hops(hostname)

        for ip in ip_list:
            try:
                next_hop = next_hops[ip.version]
            except KeyError:
                raise AddrFormatError("This node is not configured for IPv%d."
                                       % ip.version)
            network = IPNetwork(ip)
            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

        return ep
예제 #23
0
    def test__cleanup(self, m_datastore):
        ep = Endpoint("test_host",
                      "mesos",
                      "test_workload",
                      "test_endpoint",
                      "active",
                      "aa:bb:cc:dd:ee:ff")
        ipv4_addrs = {IPAddress(ip) for ip in ["192.168.1.1", "192.168.5.4"]}
        ipv6_addrs = {IPAddress("2001:4567::1:1")}
        ep.ipv4_nets = {IPNetwork(ip) for ip in ipv4_addrs}
        ep.ipv6_nets = {IPNetwork(ip) for ip in ipv6_addrs}

        m_datastore.get_endpoint.return_value = ep

        calico_mesos._cleanup("test_host", "test_workload")

        m_datastore.release_ips.assert_called_once_with(ipv4_addrs |
                                                        ipv6_addrs)
        m_datastore.remove_endpoint.assert_called_once_with(ep)
        m_datastore.remove_workload.assert_called_once_with(
            hostname=ANY,
            orchestrator_id=ANY,
            workload_id="test_workload")
예제 #24
0
    def test__cleanup(self, m_datastore):
        ep = Endpoint("test_host",
                      "mesos",
                      "test_workload",
                      "test_endpoint",
                      "active",
                      "aa:bb:cc:dd:ee:ff")
        ipv4_addrs = {IPAddress(ip) for ip in ["192.168.1.1", "192.168.5.4"]}
        ipv6_addrs = {IPAddress("2001:4567::1:1")}
        ep.ipv4_nets = {IPNetwork(ip) for ip in ipv4_addrs}
        ep.ipv6_nets = {IPNetwork(ip) for ip in ipv6_addrs}

        m_datastore.get_endpoint.return_value = ep

        calico_mesos._cleanup("test_host", "test_workload")

        m_datastore.release_ips.assert_called_once_with(ipv4_addrs |
                                                        ipv6_addrs)
        m_datastore.remove_endpoint.assert_called_once_with(ep)
        m_datastore.remove_workload.assert_called_once_with(
            hostname=ANY,
            orchestrator_id=ANY,
            workload_id="test_workload")
    def test_container_add(self):
        with patch_object(self.plugin, "_validate_container_state", autospec=True) as m_validate_container_state, patch(
            "calico_kubernetes.calico_kubernetes.netns.PidNamespace", autospec=True
        ) as m_pid_ns, patch_object(self.plugin, "_assign_container_ip", autospec=True) as m_assign_ip:
            # Set up mock objs
            self.m_datastore_client.get_endpoint.side_effect = KeyError
            endpoint = Endpoint(TEST_HOST, TEST_ORCH_ID, "1234", "5678", "active", "mac")
            endpoint.provision_veth = Mock()
            endpoint.provision_veth.return_value = "new_mac"
            self.m_datastore_client.create_endpoint.return_value = endpoint

            # Set up arguments
            container_name = "container_name"
            self.plugin.docker_id = container_name
            pid = "pid"
            ip = IPAddress("1.1.1.1")
            interface = "eth0"

            m_assign_ip.return_value = ip

            # Call method under test
            return_value = self.plugin._container_add(pid, interface)

            # Assert call parameters
            self.m_datastore_client.get_endpoint.assert_called_once_with(
                hostname=TEST_HOST, orchestrator_id=TEST_ORCH_ID, workload_id=self.plugin.docker_id
            )
            m_validate_container_state.assert_called_once_with(container_name)
            self.m_datastore_client.create_endpoint.assert_called_once_with(
                TEST_HOST, TEST_ORCH_ID, self.plugin.docker_id, [ip]
            )
            self.m_datastore_client.set_endpoint.assert_called_once_with(endpoint)
            endpoint.provision_veth.assert_called_once_with(m_pid_ns(pid), interface)

            # Verify method output
            assert_equal(endpoint.mac, "new_mac")
            assert_equal(return_value, endpoint)
예제 #26
0
def create_endpoint():
    json_data = request.get_json(force=True)
    app.logger.debug("CreateEndpoint JSON=%s", json_data)
    endpoint_id = json_data["EndpointID"]
    network_id = json_data["NetworkID"]
    interface = json_data["Interface"]

    app.logger.info("Creating endpoint %s", endpoint_id)

    # Get the addresses to use from the request JSON.
    address_ip4 = interface.get("Address")
    address_ip6 = interface.get("AddressIPv6")
    assert address_ip4 or address_ip6, "No address assigned for endpoint"

    # Create a Calico endpoint object.
    ep = Endpoint(hostname, ORCHESTRATOR_ID, CONTAINER_NAME, endpoint_id,
                  "active", FIXED_MAC)
    ep.profile_ids.append(network_id)

    if address_ip4:
        ep.ipv4_nets.add(IPNetwork(address_ip4))

    if address_ip6:
        ep.ipv6_nets.add(IPNetwork(address_ip6))

    app.logger.debug("Saving Calico endpoint: %s", ep)
    client.set_endpoint(ep)

    json_response = {
        "Interface": {
            "MacAddress": FIXED_MAC,
        }
    }

    app.logger.debug("CreateEndpoint response JSON=%s", json_response)
    return jsonify(json_response)
예제 #27
0
def join():
    json_data = request.get_json(force=True)
    app.logger.debug("Join JSON=%s", json_data)
    ep_id = json_data["EndpointID"]
    net_id = json_data["NetworkID"]
    app.logger.info("Joining endpoint %s", ep_id)

    # Get CNM endpoint ID from datastore so we can find the IP addresses
    # assigned to it.
    cnm_ep = client.read_cnm_endpoint(ep_id)

    # Read the next hops from etcd
    next_hops = client.get_default_next_hops(hostname)

    # Create a Calico endpoint object.
    #TODO - set the CONTAINER_NAME to something better (the sandbox key?)
    ep = Endpoint(hostname, "docker", CONTAINER_NAME, ep_id, "active",
                  FIXED_MAC)
    ep.profile_ids.append(net_id)

    #TODO - this assumes there are still IPv6 gateways configured (could
    # have been deleted in the interim)
    address_ip4 = cnm_ep['Interfaces'][0].get('Address')
    if address_ip4:
        ep.ipv4_nets.add(IPNetwork(address_ip4))
        ep.ipv4_gateway = next_hops[4]

    address_ip6 = cnm_ep['Interfaces'][0].get('AddressIPv6')
    if address_ip6:
        ep.ipv6_nets.add(IPNetwork(address_ip6))
        ep.ipv6_gateway = next_hops[6]

    try:
        # Next, create the veth.
        create_veth(ep)

        # Finally, write the endpoint to the datastore.
        client.set_endpoint(ep)
    except (CalledProcessError, DataStoreError) as e:
        # Failed to create or configure the veth, or failed to write the
        # endpoint to the datastore. In both cases, ensure veth is removed.
        app.logger.exception(e)
        remove_veth(ep)
        abort(500)

    ret_json = {
        "InterfaceNames": [{
            "SrcName": ep.temp_interface_name,
            "DstPrefix": IF_PREFIX
        }],
        "Gateway": str(ep.ipv4_gateway),
        "StaticRoutes": [{
            "Destination": "%s/32" % ep.ipv4_gateway,
            "RouteType": 1,  # 1 = CONNECTED
            "NextHop": "",
            "InterfaceID": 0
            }]
    }
    if ep.ipv6_gateway:
        ret_json["GatewayIPv6"] = str(ep.ipv6_gateway)
        ret_json["StaticRoutes"].append({
            "Destination": "%s/128" % ep.ipv6_gateway,
            "RouteType": 1,  # 1 = CONNECTED
            "NextHop": "",
            "InterfaceID": 0
            })

    return jsonify(ret_json)
def create_endpoint():
    json_data = request.get_json(force=True)
    ep_id = json_data["EndpointID"]
    net_id = json_data["NetworkID"]

    # Create a calico endpoint object which we can populate and return to
    # libnetwork at the end of this method.
    ep = Endpoint(hostname, "docker", CONTAINER_NAME, ep_id, "active",
                  FIXED_MAC)
    ep.profile_ids.append(net_id)

    # This method is split into three phases that have side effects.
    # 1) Assigning IP addresses
    # 2) Creating VETHs
    # 3) Writing the endpoint to the datastore.
    #
    # A failure in a later phase attempts to roll back the effects of
    # the earlier phases.

    # First up is IP assignment. By default we assign both IPv4 and IPv6
    # addresses.
    # IPv4 failures may abort the request if the address couldn't be assigned.
    ipv4_and_gateway(ep)
    # IPv6 is currently best effort and won't abort the request.
    ipv6_and_gateway(ep)

    # Next, create the veth.
    try:
        create_veth(ep)
    except CalledProcessError as e:
        # Failed to create or configure the veth.
        # Back out the IP assignments and the veth creation.
        app.logger.exception(e)
        backout_ip_assignments(ep)
        remove_veth(ep)
        abort(500)

    # Finally, write the endpoint to the datastore.
    try:
        client.set_endpoint(ep)
    except DataStoreError as e:
        # We've failed to write the endpoint to the datastore.
        # Back out the IP assignments and the veth creation.
        app.logger.exception(e)
        backout_ip_assignments(ep)
        remove_veth(ep)
        abort(500)

    # Everything worked, create the JSON and return it to libnetwork.
    assert len(ep.ipv4_nets) == 1
    assert len(ep.ipv6_nets) <= 1
    iface_json = {
        "ID": 0,
        "Address": str(list(ep.ipv4_nets)[0]),
        "MacAddress": ep.mac
    }

    if ep.ipv6_nets:
        iface_json["AddressIPv6"] = str(list(ep.ipv6_nets)[0])

    return jsonify({"Interfaces": [iface_json]})
    def test_create_endpoint(self, m_set, m_get_network, m_get_next_hops):
        """
        Test the create_endpoint hook correctly writes the appropriate data
        to etcd based on IP assignment and pool selection.
        """

        # Iterate using various different mixtures of IP assignments and
        # gateway CIDRs.
        #
        # (IPv4 addr, IPv6 addr, IPv4 gway, IPv6 gway, calico_ipam)
        #
        # calico_ipam indicates whether the gateway indicates Calico IPAM or
        # not which changes the gateway selected in the endpoint.
        parms = [(None, "aa:bb::bb", None, "cc:dd::00/23", False),
                 ("10.20.30.40", None, "1.2.3.4/32", "aa:bb:cc::/24", False),
                 ("20.20.30.40", "ab:bb::bb", "1.2.3.4/32", "aa:bb:cc::/25",
                  False), (None, "ac:bb::bb", None, "00::00/0", True),
                 ("40.20.30.40", None, "0.0.0.0/0", "::/0", True),
                 ("50.20.30.40", "ad:bb::bb", "0.0.0.0/0", "00::/0", True)]

        next_hop_4 = IPAddress("11.22.33.44")
        next_hop_6 = IPAddress("a0:b0::f0")
        m_get_next_hops.return_value = {4: next_hop_4, 6: next_hop_6}

        # Loop through different combinations of IP availability.
        for ipv4, ipv6, gwv4, gwv6, calico_ipam in parms:
            m_get_network.return_value = {
                "NetworkID": TEST_NETWORK_ID,
                "IPv4Data": [{
                    "Gateway": gwv4,
                    "Pool": gwv4
                }],
                "IPv6Data": [{
                    "Gateway": gwv6,
                    "Pool": gwv6
                }]
            }
            ipv4_json = ',"Address": "%s"' % ipv4 if ipv4 else ""
            ipv6_json = ',"AddressIPv6": "%s"' % ipv6 if ipv6 else ""

            # Invoke create endpoint.
            rv = self.app.post(
                '/NetworkDriver.CreateEndpoint',
                data='{"EndpointID": "%s",'
                '"NetworkID":  "%s",'
                '"Interface": {"MacAddress": "EE:EE:EE:EE:EE:EE"%s%s}}' %
                (TEST_ENDPOINT_ID, TEST_NETWORK_ID, ipv4_json, ipv6_json))

            # Assert return value
            self.assertDictEqual(json.loads(
                rv.data), {"Interface": {
                    "MacAddress": "EE:EE:EE:EE:EE:EE"
                }})

            # Assert expected data is written to etcd
            ep = Endpoint(hostname, "libnetwork", "libnetwork",
                          TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")

            ep.profile_ids.append(TEST_NETWORK_ID)

            if ipv4:
                ep.ipv4_nets.add(IPNetwork(ipv4))
                if calico_ipam:
                    ep.ipv4_gateway = next_hop_4
                else:
                    ep.ipv4_gateway = IPNetwork(gwv4).ip

            if ipv6:
                ep.ipv6_nets.add(IPNetwork(ipv6))
                if calico_ipam:
                    ep.ipv6_gateway = next_hop_6
                else:
                    ep.ipv6_gateway = IPNetwork(gwv6).ip

            m_set.assert_called_once_with(ep)

            # Reset the Mocks before continuing.
            m_set.reset_mock()
예제 #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()

    # 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
예제 #31
0
파일: netns.py 프로젝트: L-MA/calico-docker
def set_up_endpoint(
    ip, hostname, orchestrator_id, workload_id, cpid, next_hop_ips, veth_name=VETH_NAME, proc_alias=PROC_ALIAS, mac=None
):
    """
    Set up an endpoint (veth) in the network namespace identified by the PID.

    :param ip: The IP address to assign to the endpoint (veth) as Netaddr
    IPAddress.
    :param hostname: The host that this endpoint's workload resides on.
    :param orchestrator_id: The orchestrator_id that this endpoint was created on.
    :param workload_id: The workload_id that this endpoint resides on.
    :param cpid: The PID of a process currently running in the namespace.
    :param next_hop_ips: Dict of {version: IPAddress} for the next hops of the
    default routes namespace, as opposed to the root namespace.  If so, this
    method also moves the other end of the veth into the root namespace.
    :param veth_name: The name of the interface inside the container namespace,
    e.g. eth1
    :param proc_alias: The location of the /proc filesystem on the host.
    :param mac: The interface MAC to use.  Set to None to auto assign a MAC.
    :return: An Endpoint describing the veth just created.
    """
    assert isinstance(ip, IPAddress)

    # Generate a new endpoint ID.
    ep_id = uuid.uuid1().hex

    iface = IF_PREFIX + ep_id[:11]
    iface_tmp = "tmp" + ep_id[:11]

    # Provision the networking.  We create a temporary link from the proc
    # alias to the /var/run/netns to provide a named namespace.  If we don't
    # do this, when run from the calico-node container the PID of the
    # container process is not recognised by `ip link set <if> netns <pid>`
    # command because that uses /proc rather than the proc alias to
    # dereference the PID.
    with NamedNamespace(cpid, proc=proc_alias) as ns:
        # Create the veth pair and move one end into container:
        check_call("ip link add %s type veth peer name %s" % (iface, iface_tmp), shell=True)
        check_call("ip link set %s up" % iface, shell=True)
        check_call("ip link set %s netns %s" % (iface_tmp, ns.name), shell=True)
        _log.debug(check_output("ip link", shell=True))

        if mac:
            ns.check_call("ip link set dev %s name %s address %s" % (iface_tmp, veth_name, str(mac)), shell=True)
        else:
            ns.check_call("ip link set dev %s name %s" % (iface_tmp, veth_name), shell=True)
        ns.check_call("ip link set %s up" % veth_name, shell=True)

    # Add an IP address.
    add_ip_to_interface(cpid, ip, veth_name, proc_alias=proc_alias)

    with NamedNamespace(cpid, proc=proc_alias) as ns:
        # Connected route to next hop & default route.
        next_hop = next_hop_ips[ip.version]
        ns.check_call(
            "ip -%(version)s route replace"
            " %(next_hop)s dev %(device)s" % {"version": ip.version, "device": veth_name, "next_hop": next_hop},
            shell=True,
        )
        ns.check_call(
            "ip -%(version)s route replace"
            " default via %(next_hop)s dev %(device)s"
            % {"version": ip.version, "device": veth_name, "next_hop": next_hop},
            shell=True,
        )

        # Get the MAC address.
        mac = ns.check_output("ip link show %s | grep ether | awk '{print $2}'" % (veth_name), shell=True).strip()

    # Return an Endpoint.
    network = IPNetwork(IPAddress(ip))
    ep = Endpoint(
        hostname=hostname,
        orchestrator_id=orchestrator_id,
        workload_id=workload_id,
        endpoint_id=ep_id,
        state="active",
        mac=mac,
    )
    ep.if_name = veth_name
    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
    return ep
예제 #32
0
def set_up_endpoint(ip,
                    hostname,
                    orchestrator_id,
                    workload_id,
                    cpid,
                    next_hop_ips,
                    veth_name=VETH_NAME,
                    proc_alias=PROC_ALIAS,
                    mac=None):
    """
    Set up an endpoint (veth) in the network namespace identified by the PID.

    :param ip: The IP address to assign to the endpoint (veth) as Netaddr
    IPAddress.
    :param hostname: The host that this endpoint's workload resides on.
    :param orchestrator_id: The orchestrator_id that this endpoint was created on.
    :param workload_id: The workload_id that this endpoint resides on.
    :param cpid: The PID of a process currently running in the namespace.
    :param next_hop_ips: Dict of {version: IPAddress} for the next hops of the
    default routes namespace, as opposed to the root namespace.  If so, this
    method also moves the other end of the veth into the root namespace.
    :param veth_name: The name of the interface inside the container namespace,
    e.g. eth1
    :param proc_alias: The location of the /proc filesystem on the host.
    :param mac: The interface MAC to use.  Set to None to auto assign a MAC.
    :return: An Endpoint describing the veth just created.
    """
    assert isinstance(ip, IPAddress)

    # Generate a new endpoint ID.
    ep_id = uuid.uuid1().hex

    iface = IF_PREFIX + ep_id[:11]
    iface_tmp = "tmp" + ep_id[:11]

    # Provision the networking.  We create a temporary link from the proc
    # alias to the /var/run/netns to provide a named namespace.  If we don't
    # do this, when run from the calico-node container the PID of the
    # container process is not recognised by `ip link set <if> netns <pid>`
    # command because that uses /proc rather than the proc alias to
    # dereference the PID.
    with NamedNamespace(cpid, proc=proc_alias) as ns:
        # Create the veth pair and move one end into container:
        check_call("ip link add %s type veth peer name %s" %
                   (iface, iface_tmp),
                   shell=True)
        check_call("ip link set %s up" % iface, shell=True)
        check_call("ip link set %s netns %s" % (iface_tmp, ns.name),
                   shell=True)
        _log.debug(check_output("ip link", shell=True))

        if mac:
            ns.check_call("ip link set dev %s name %s address %s" %
                          (iface_tmp, veth_name, str(mac)),
                          shell=True)
        else:
            ns.check_call("ip link set dev %s name %s" %
                          (iface_tmp, veth_name),
                          shell=True)
        ns.check_call("ip link set %s up" % veth_name, shell=True)

    # Add an IP address.
    add_ip_to_interface(cpid, ip, veth_name, proc_alias=proc_alias)

    with NamedNamespace(cpid, proc=proc_alias) as ns:
        # Connected route to next hop & default route.
        next_hop = next_hop_ips[ip.version]
        ns.check_call("ip -%(version)s route replace"
                      " %(next_hop)s dev %(device)s" % {
                          "version": ip.version,
                          "device": veth_name,
                          "next_hop": next_hop
                      },
                      shell=True)
        ns.check_call("ip -%(version)s route replace"
                      " default via %(next_hop)s dev %(device)s" % {
                          "version": ip.version,
                          "device": veth_name,
                          "next_hop": next_hop
                      },
                      shell=True)

        # Get the MAC address.
        mac = ns.check_output(
            "ip link show %s | grep ether | awk '{print $2}'" % (veth_name),
            shell=True).strip()

    # Return an Endpoint.
    network = IPNetwork(IPAddress(ip))
    ep = Endpoint(hostname=hostname,
                  orchestrator_id=orchestrator_id,
                  workload_id=workload_id,
                  endpoint_id=ep_id,
                  state="active",
                  mac=mac)
    ep.if_name = veth_name
    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
    return ep
예제 #33
0
def join():
    json_data = request.get_json(force=True)
    app.logger.debug("Join JSON=%s", json_data)
    ep_id = json_data["EndpointID"]
    net_id = json_data["NetworkID"]
    app.logger.info("Joining endpoint %s", ep_id)

    # Get CNM endpoint ID from datastore so we can find the IP addresses
    # assigned to it.
    cnm_ep = client.read_cnm_endpoint(ep_id)

    # Read the next hops from etcd
    next_hops = client.get_default_next_hops(hostname)

    # Create a Calico endpoint object.
    #TODO - set the CONTAINER_NAME to something better (the sandbox key?)
    ep = Endpoint(hostname, "docker", CONTAINER_NAME, ep_id, "active",
                  FIXED_MAC)
    ep.profile_ids.append(net_id)

    #TODO - this assumes there are still IPv6 gateways configured (could
    # have been deleted in the interim)
    address_ip4 = cnm_ep['Interfaces'][0].get('Address')
    if address_ip4:
        ep.ipv4_nets.add(IPNetwork(address_ip4))
        ep.ipv4_gateway = next_hops[4]

    address_ip6 = cnm_ep['Interfaces'][0].get('AddressIPv6')
    if address_ip6:
        ep.ipv6_nets.add(IPNetwork(address_ip6))
        ep.ipv6_gateway = next_hops[6]

    try:
        # Next, create the veth.
        create_veth(ep)

        # Finally, write the endpoint to the datastore.
        client.set_endpoint(ep)
    except (CalledProcessError, DataStoreError) as e:
        # Failed to create or configure the veth, or failed to write the
        # endpoint to the datastore. In both cases, ensure veth is removed.
        app.logger.exception(e)
        remove_veth(ep)
        abort(500)

    ret_json = {
        "InterfaceNames": [{
            "SrcName": ep.temp_interface_name,
            "DstPrefix": IF_PREFIX
        }],
        "Gateway":
        str(ep.ipv4_gateway),
        "StaticRoutes": [{
            "Destination": "%s/32" % ep.ipv4_gateway,
            "RouteType": 1,  # 1 = CONNECTED
            "NextHop": "",
            "InterfaceID": 0
        }]
    }
    if ep.ipv6_gateway:
        ret_json["GatewayIPv6"] = str(ep.ipv6_gateway)
        ret_json["StaticRoutes"].append({
            "Destination": "%s/128" % ep.ipv6_gateway,
            "RouteType": 1,  # 1 = CONNECTED
            "NextHop": "",
            "InterfaceID": 0
        })

    return jsonify(ret_json)
예제 #34
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 test_create_endpoint(self, m_set, m_get_network, m_get_next_hops):
        """
        Test the create_endpoint hook correctly writes the appropriate data
        to etcd based on IP assignment and pool selection.
        """

        # Iterate using various different mixtures of IP assignments and
        # gateway CIDRs.
        #
        # (IPv4 addr, IPv6 addr, IPv4 gway, IPv6 gway, calico_ipam)
        #
        # calico_ipam indicates whether the gateway indicates Calico IPAM or
        # not which changes the gateway selected in the endpoint.
        parms = [(None, "aa:bb::bb", None, "cc:dd::00/23", False),
                 ("10.20.30.40", None, "1.2.3.4/32", "aa:bb:cc::/24", False),
                 ("20.20.30.40", "ab:bb::bb", "1.2.3.4/32", "aa:bb:cc::/25", False),
                 (None, "ac:bb::bb", None, "00::00/0", True),
                 ("40.20.30.40", None, "0.0.0.0/0", "::/0", True),
                 ("50.20.30.40", "ad:bb::bb", "0.0.0.0/0", "00::/0", True)]

        next_hop_4 = IPAddress("11.22.33.44")
        next_hop_6 = IPAddress("a0:b0::f0")
        m_get_next_hops.return_value = {4: next_hop_4,
                                        6: next_hop_6}

        # Loop through different combinations of IP availability.
        for ipv4, ipv6, gwv4, gwv6, calico_ipam in parms:
            m_get_network.return_value = {
                "NetworkID": TEST_NETWORK_ID,
                "IPv4Data":[{"Gateway": gwv4, "Pool": gwv4}],
                "IPv6Data":[{"Gateway": gwv6, "Pool": gwv6}]
            }
            ipv4_json = ',"Address": "%s"' % ipv4 if ipv4 else ""
            ipv6_json = ',"AddressIPv6": "%s"' % ipv6 if ipv6 else ""

            # Invoke create endpoint.
            rv = self.app.post('/NetworkDriver.CreateEndpoint',
                               data='{"EndpointID": "%s",'
                                     '"NetworkID":  "%s",'
                                     '"Interface": {"MacAddress": "EE:EE:EE:EE:EE:EE"%s%s}}' %
                                    (TEST_ENDPOINT_ID, TEST_NETWORK_ID, ipv4_json, ipv6_json))

            # Assert return value
            self.assertDictEqual(json.loads(rv.data), {
                "Interface": {
                    "MacAddress": "EE:EE:EE:EE:EE:EE"
                }
            })

            # Assert expected data is written to etcd
            ep = Endpoint(hostname, "libnetwork", "libnetwork",
                          TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")

            ep.profile_ids.append(TEST_NETWORK_ID)

            if ipv4:
                ep.ipv4_nets.add(IPNetwork(ipv4))
                if calico_ipam:
                    ep.ipv4_gateway = next_hop_4
                else:
                    ep.ipv4_gateway = IPNetwork(gwv4).ip

            if ipv6:
                ep.ipv6_nets.add(IPNetwork(ipv6))
                if calico_ipam:
                    ep.ipv6_gateway = next_hop_6
                else:
                    ep.ipv6_gateway = IPNetwork(gwv6).ip

            m_set.assert_called_once_with(ep)

            # Reset the Mocks before continuing.
            m_set.reset_mock()
예제 #36
0
    def test_join(self, m_set, m_veth, m_read, m_next_hops):
        """
        Test the join() processing correctly creates the veth and the Endpoint.
        """
        endpoint_json = {"Interface":
                            {"Address": "1.2.3.4",
                             "AddressIPv6": "FE80::0202:B3FF:FE1E:8329",
                             "MacAddress": "EE:EE:EE:EE:EE:EE"}
                        }
        m_read.return_value = endpoint_json

        m_next_hops.return_value = {4: IPAddress("1.2.3.4"),
                                    6: IPAddress("fe80::202:b3ff:fe1e:8329")}

        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s", "NetworkID": "%s"}' %
                                (TEST_ENDPOINT_ID, TEST_NETWORK_ID))
        m_read.assert_called_once_with(TEST_ENDPOINT_ID)

        # Check that the create_veth and set_endpoint are called with this
        # endpoint.
        endpoint = Endpoint("hostname",
                            "docker",
                            "libnetwork",
                            TEST_ENDPOINT_ID,
                            "active",
                            "EE:EE:EE:EE:EE:EE")
        endpoint.ipv4_gateway = IPAddress("1.2.3.4")
        endpoint.ipv6_gateway = IPAddress("FE80::0202:B3FF:FE1E:8329")
        endpoint.ipv4_nets.add(IPNetwork("1.2.3.4/32"))
        endpoint.ipv6_nets.add(IPNetwork("FE80::0202:B3FF:FE1E:8329/128"))
        endpoint.profile_ids.append(TEST_NETWORK_ID)

        m_veth.assert_called_once_with(endpoint)
        m_set.assert_called_once_with(endpoint)

        expected_response = """{
  "Gateway": "1.2.3.4",
  "GatewayIPv6": "fe80::202:b3ff:fe1e:8329",
  "InterfaceName":
    {
      "DstPrefix": "cali",
      "SrcName": "tmpTEST_ENDPOI"
    }
  ,
  "StaticRoutes": [
    {
      "Destination": "1.2.3.4/32",
      "NextHop": "",
      "RouteType": 1
    },
    {
      "Destination": "fe80::202:b3ff:fe1e:8329/128",
      "NextHop": "",
      "RouteType": 1
    }
  ]
}"""
        self.maxDiff=None
        self.assertDictEqual(json.loads(rv.data),
                             json.loads(expected_response))
    def test_join(self, m_set, m_veth, m_read, m_next_hops):
        """
        Test the join() processing correctly creates the veth and the Endpoint.
        """
        endpoint_json = {
            "Interfaces": [{
                "Address": "1.2.3.4",
                "AddressIPv6": "FE80::0202:B3FF:FE1E:8329",
                "ID": 0,
                "MacAddress": "EE:EE:EE:EE:EE:EE"
            }]
        }
        m_read.return_value = endpoint_json

        m_next_hops.return_value = {
            4: IPAddress("1.2.3.4"),
            6: IPAddress("fe80::202:b3ff:fe1e:8329")
        }

        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s", "NetworkID": "%s"}' %
                           (TEST_ENDPOINT_ID, TEST_NETWORK_ID))
        m_read.assert_called_once_with(TEST_ENDPOINT_ID)

        # Check that the create_veth and set_endpoint are called with this
        # endpoint.
        endpoint = Endpoint("hostname", "docker", "libnetwork",
                            TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")
        endpoint.ipv4_gateway = IPAddress("1.2.3.4")
        endpoint.ipv6_gateway = IPAddress("FE80::0202:B3FF:FE1E:8329")
        endpoint.ipv4_nets.add(IPNetwork("1.2.3.4/32"))
        endpoint.ipv6_nets.add(IPNetwork("FE80::0202:B3FF:FE1E:8329/128"))
        endpoint.profile_ids.append(TEST_NETWORK_ID)

        m_veth.assert_called_once_with(endpoint)
        m_set.assert_called_once_with(endpoint)

        expected_response = """{
  "Gateway": "1.2.3.4",
  "GatewayIPv6": "fe80::202:b3ff:fe1e:8329",
  "InterfaceNames": [
    {
      "DstPrefix": "cali",
      "SrcName": "tmpTEST_ENDPOI"
    }
  ],
  "StaticRoutes": [
    {
      "Destination": "1.2.3.4/32",
      "InterfaceID": 0,
      "NextHop": "",
      "RouteType": 1
    },
    {
      "Destination": "fe80::202:b3ff:fe1e:8329/128",
      "InterfaceID": 0,
      "NextHop": "",
      "RouteType": 1
    }
  ]
}"""
        self.maxDiff = None
        self.assertDictEqual(json.loads(rv.data),
                             json.loads(expected_response))
예제 #38
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()

    # 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
예제 #39
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.
    :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