From 063c6b0982c52460d44c85fe298a26480663190f Mon Sep 17 00:00:00 2001 From: Dan Osborne Date: Fri, 27 May 2016 09:43:33 -0700 Subject: [PATCH] Move default rule into CNI plugin --- calico.py | 35 ++++++----------- calico_cni/constants.py | 16 +------- calico_cni/policy_drivers.py | 51 ++++++++++++++++++++++++- tests/unit/test_cni_plugin.py | 13 ------- tests/unit/test_policy_drivers.py | 62 +++++++++++++++++++++++++++++++ 5 files changed, 123 insertions(+), 54 deletions(-) diff --git a/calico.py b/calico.py index a67be7301..ce2880d2a 100755 --- a/calico.py +++ b/calico.py @@ -110,26 +110,6 @@ def __init__(self, network_config, env): Environment dictionary used when calling the IPAM plugin. """ - self.labels = {} - """ - Label data to assign to this endpoint. The origin of the labels is orchestrator - dependent. - """ - - self.args = network_config.get(ARGS_KEY, {}) - - # If there Mesos namespaced data, extract any labels that have been specified. - # Note that Mesos labels are a list of {"key": , "value": }, so pull - # the key and values and populate the labels dictionary. - if MESOS_NS_KEY in self.args: - _log.info("Extracting Mesos namespaced data") - labels_list = self.args[MESOS_NS_KEY]. \ - get(MESOS_NETWORK_INFO_KEY, {}). \ - get(MESOS_LABELS_OUTER_KEY, {}). \ - get(MESOS_LABELS_KEY, []) - for label in labels_list: - self.labels[label["key"]] = label["value"] - self.command = env[CNI_COMMAND_ENV] assert self.command in [CNI_CMD_DELETE, CNI_CMD_ADD], \ "Invalid CNI command %s" % self.command @@ -160,6 +140,17 @@ def __init__(self, network_config, env): Path in which to search for CNI plugins. """ + self.running_under_mesos = bool(self.network_config.get("args", {}). \ + get(MESOS_NS_KEY)) + """ + Flag indicating if this plugin is being executed by mesos. Mesos injects "args" + into the network config before passing it to the CNI plugin. Those args will + contain a mesos namespaced field. If that field exists, this must be mesos. + + Note: Mesos does not yet inject this information during network deletion. Fortunately, + we don't perform mesos-specific logic during deletion. + """ + self.running_under_k8s = self.k8s_namespace and self.k8s_pod_name if self.running_under_k8s: self.workload_id = "%s.%s" % (self.k8s_namespace, self.k8s_pod_name) @@ -588,10 +579,6 @@ def _create_endpoint(self, ip_list): print_cni_error(ERR_CODE_GENERIC, e.message) sys.exit(ERR_CODE_GENERIC) - # Assign labels to the new endpoint object - _log.debug("Setting Labels: %s", self.labels) - endpoint.labels = self.labels - _log.info("Created Calico endpoint with IP address(es) %s", ip_list) return endpoint diff --git a/calico_cni/constants.py b/calico_cni/constants.py index b66bdd1f5..a85cc6fab 100644 --- a/calico_cni/constants.py +++ b/calico_cni/constants.py @@ -71,21 +71,7 @@ LOG_DIR = "/var/log/calico/cni" LOG_FORMAT = '%(asctime)s %(process)d [%(identity)s] %(levelname)s %(message)s' -# Mesos namespaced data. Mesos CNI inserts the following additional data into -# the args field: -# "args" : { -# "org.apache.mesos" : { -# "network_info" : { -# "name" : "mynet", -# "labels" : { -# "labels" : [ -# { "key" : "app", "value" : "myapp" }, -# { "key" : "env", "value" : "prod" } -# ] -# } -# } -# } -# } +# Mesos namespaced data. MESOS_NS_KEY = "org.apache.mesos" MESOS_NETWORK_INFO_KEY = "network_info" MESOS_LABELS_OUTER_KEY = "labels" diff --git a/calico_cni/policy_drivers.py b/calico_cni/policy_drivers.py index 3c0790a64..7674cb685 100644 --- a/calico_cni/policy_drivers.py +++ b/calico_cni/policy_drivers.py @@ -131,13 +131,55 @@ def generate_tags(self): """ return [] +class MesosPolicyDriver(DefaultPolicyDriver): + """ + Implements default network policy for a Mesos container manager. + """ + def __init__(self, network_name, network_args): + """ + Extract any labels that have been specified in the mesos-namespaced labels field + of the network_args. Mesos CNI inserts the following additional data into + the args field: + "args" : { + "org.apache.mesos" : { + "network_info" : { + "name" : "mynet", + "labels" : { + "labels" : [ + { "key" : "app", "value" : "myapp" }, + { "key" : "env", "value" : "prod" } + ] + } + } + } + } + Note that Mesos labels are a list of {"key": , "value": }, so pull + the key and values and populate the labels dictionary. + """ + self.labels = {} + + labels_list = network_args[MESOS_NS_KEY]. \ + get(MESOS_NETWORK_INFO_KEY, {}). \ + get(MESOS_LABELS_OUTER_KEY, {}). \ + get(MESOS_LABELS_KEY, []) + + for label in labels_list: + self.labels[label["key"]] = label["value"] + + DefaultPolicyDriver.__init__(self, network_name) + + def apply_profile(self, endpoint): + endpoint.labels = self.labels + self._client.update_endpoint(endpoint) + DefaultPolicyDriver.apply_profile(self, endpoint) + class KubernetesNoPolicyDriver(DefaultPolicyDriver): """ Implements default network policy for a Kubernetes container manager. - The different between this an the DefaultPolicyDriver is that this - engine creates profiles which allow all incoming traffic. + Unlike the DefaultPolicyDriver, this engine creates profiles + which allow _all_ incoming traffic. """ def generate_rules(self): """Generates default rules for a Kubernetes container manager. @@ -399,6 +441,7 @@ def get_policy_driver(cni_plugin): # Extract policy config and network name. policy_config = cni_plugin.network_config.get(POLICY_KEY, {}) network_name = cni_plugin.network_config["name"] + network_args = cni_plugin.network_config.get(ARGS_KEY, {}) policy_type = policy_config.get("type") k8s_config = cni_plugin.network_config.get("kubernetes", {}) supported_policy_types = [None, @@ -452,6 +495,10 @@ def get_policy_driver(cni_plugin): _log.debug("Using Kubernetes Driver - no policy") driver_cls = KubernetesNoPolicyDriver driver_args = [network_name] + elif cni_plugin.running_under_mesos: + _log.debug("Using Mesos Policy Driver") + driver_cls = MesosPolicyDriver + driver_args = [network_name, network_args] else: _log.debug("Using default policy driver") driver_cls = DefaultPolicyDriver diff --git a/tests/unit/test_cni_plugin.py b/tests/unit/test_cni_plugin.py index 854c308ee..bab80cdaf 100644 --- a/tests/unit/test_cni_plugin.py +++ b/tests/unit/test_cni_plugin.py @@ -52,18 +52,6 @@ def setUp(self): "routes": [{"dst": "0.0.0.0/0"}], "range-start": "", "range-end": "" - }, - "args" : { - "org.apache.mesos" : { - "network_info" : { - "name" : "mynet", - "labels" : { - "labels" : [ - { "key" : "group", "value" : "production" }, - ] - }, - }, - }, } } self.env = { @@ -555,7 +543,6 @@ def test_create_endpoint_mainline(self): self.plugin._client.create_endpoint.assert_called_once_with(ANY, self.expected_orch_id, self.expected_workload_id, ip_list) assert_equal(ep, endpoint) - self.assertEqual(ep.labels, {"group": "production"}) def test_create_endpoint_error(self): # Mock. diff --git a/tests/unit/test_policy_drivers.py b/tests/unit/test_policy_drivers.py index 57c53d6ae..20ddc958e 100644 --- a/tests/unit/test_policy_drivers.py +++ b/tests/unit/test_policy_drivers.py @@ -26,6 +26,7 @@ get_policy_driver, PolicyException, DefaultPolicyDriver, + MesosPolicyDriver, KubernetesNoPolicyDriver, KubernetesAnnotationDriver, KubernetesPolicyDriver) @@ -99,6 +100,48 @@ def test_invalid_network_name(self, net_name): assert_raises(ValueError, DefaultPolicyDriver, net_name) +class MesosPolicyDriverTest(unittest.TestCase): + def setUp(self): + self.network_name = "test_net_name" + self.network_args = { + "org.apache.mesos" : { + "network_info" : { + "name" : "test_net_name", + "labels" : { + "labels" : [ + { "key" : "app", "value" : "myapp" }, + { "key" : "env", "value" : "prod" } + ] + } + } + } + } + + # The init function for Mesos Policy driver will convert these + # to a standard dictionary. + self.expected_labels = {"app":"myapp", "env":"prod"} + + self.driver = MesosPolicyDriver(self.network_name, self.network_args) + assert_equal(self.driver.profile_name, self.network_name) + assert_equal(self.driver.labels, self.expected_labels) + + # Mock the datastore client + self.client = MagicMock(spec=DatastoreClient) + self.driver._client = self.client + + @patch("calico_cni.policy_drivers.DefaultPolicyDriver.apply_profile") + def test_apply_profile(self, m_default_apply_profile): + endpoint = MagicMock(spec=Endpoint) + endpoint.endpoint_id = "12345" + + # Mock default profile call + self.driver.apply_profile(endpoint) + + m_default_apply_profile.assert_called_once() + self.assertEqual(endpoint.labels, self.expected_labels) + + + class KubernetesDefaultPolicyDriverTest(unittest.TestCase): """ Test class for KubernetesDefaultPolicyDriver class. @@ -122,6 +165,7 @@ def test_generate_rules(self): outbound_rules=[Rule(action="allow")]) assert_equal(rules, expected) + class KubernetesAnnotationDriverTest(unittest.TestCase): """ Test class for KubernetesAnnotationDriver class. @@ -422,6 +466,7 @@ def test_get_metadata_missing(self): # Should be None assert_equal(annotations, None) + class KubernetesPolicyDriverTest(unittest.TestCase): """ Test class for DefaultDenyInboundDriver class. @@ -467,6 +512,7 @@ def test_get_policy_driver_default_k8s(self): cni_plugin.k8s_pod_name = "podname" cni_plugin.k8s_namespace = "namespace" cni_plugin.running_under_k8s = True + cni_plugin.running_under_mesos = False driver = get_policy_driver(cni_plugin) assert_true(isinstance(driver, KubernetesNoPolicyDriver)) @@ -477,6 +523,7 @@ def test_get_policy_driver_k8s_annotations(self): cni_plugin.k8s_pod_name = "podname" cni_plugin.k8s_namespace = "namespace" cni_plugin.running_under_k8s = True + cni_plugin.running_under_mesos = False driver = get_policy_driver(cni_plugin) assert_true(isinstance(driver, KubernetesAnnotationDriver)) @@ -486,6 +533,7 @@ def test_get_policy_driver_k8s(self): cni_plugin.k8s_pod_name = "podname" cni_plugin.k8s_namespace = "namespace" cni_plugin.running_under_k8s = True + cni_plugin.running_under_mesos = False driver = get_policy_driver(cni_plugin) assert_true(isinstance(driver, KubernetesPolicyDriver)) @@ -503,6 +551,7 @@ def test_missing_cert(self): cni_plugin = Mock(spec=CniPlugin) cni_plugin.network_config = config cni_plugin.running_under_k8s = True + cni_plugin.running_under_mesos = False cni_plugin.k8s_pod_name = "podname" cni_plugin.k8s_namespace = "namespace" with assert_raises(SystemExit) as err: @@ -517,6 +566,7 @@ def test_get_policy_driver_value_error(self, m_driver): cni_plugin = Mock(spec=CniPlugin) cni_plugin.network_config = {"name": "testnetwork"} cni_plugin.running_under_k8s = False + cni_plugin.running_under_mesos = False # Call with assert_raises(SystemExit) as err: @@ -524,3 +574,15 @@ def test_get_policy_driver_value_error(self, m_driver): e = err.exception assert_equal(e.code, ERR_CODE_GENERIC) + def test_get_policy_driver_mesos(self): + cni_plugin = Mock(spec=CniPlugin) + cni_plugin.network_config = {"name": "testnetwork", + "policy":{"type": "k8s"}, + "args": { + "org.apache.mesos": {} + } + } + cni_plugin.running_under_k8s = False + cni_plugin.running_under_mesos = True + driver = get_policy_driver(cni_plugin) + assert_true(isinstance(driver, MesosPolicyDriver))