Merge "Ensure pod relabeling is supported by the Network Policy"

This commit is contained in:
Zuul 2018-12-05 14:20:58 +00:00 committed by Gerrit Code Review
commit 1b2b217981
5 changed files with 256 additions and 7 deletions

View File

@ -7,7 +7,7 @@ handlers at kuryr.conf (further info on how to do this can be found at
:doc:`./devstack/containerized`)::
[kubernetes]
enabled_handlers=vif,lb,lbaasspec,policy
enabled_handlers=vif,lb,lbaasspec,policy,pod_label
After that, enable also the security group drivers for policies::
@ -28,7 +28,7 @@ Same for containerized deployments::
For directly enabling the driver when deploying with devstack, you just need
to add the policy handler and drivers with::
KURYR_ENABLED_HANDLERS=vif,lb,lbaasspec,policy
KURYR_ENABLED_HANDLERS=vif,lb,lbaasspec,policy,pod_label
KURYR_SG_DRIVER=policy
Testing the network policy support functionality
@ -44,7 +44,7 @@ Testing the network policy support functionality
spec:
podSelector:
matchLabels:
role: db
project: default
policyTypes:
- Ingress
- Egress
@ -143,7 +143,7 @@ Testing the network policy support functionality
protocol: TCP
podSelector:
matchLabels:
role: db
project: default
policyTypes:
- Ingress
- Egress
@ -246,6 +246,9 @@ Testing the network policy support functionality
- namespaceSelector:
matchLabels:
project: default
podSelector:
matchLabels:
project: default
policyTypes:
- Ingress
- Egress
@ -264,10 +267,40 @@ Testing the network policy support functionality
demo-5558c7865d-fdkdv: HELLO! I AM ALIVE!!!
Note the ping will only work from pods (neutron ports) on a namespace that has
the label 'project: default' as stated on the policy namespaceSelector.
Note the curl only works from pods (neutron ports) on a namespace that has
the label `project: default` as stated on the policy namespaceSelector.
10. Confirm the teardown of the resources once the network policy is removed::
10. We can also create a single pod, without a label and check that there is
no connectivity to it, as it does not match the network policy
podSelector::
$ cat sample-pod.yml
apiVersion: v1
kind: Pod
metadata:
name: demo-pod
spec:
containers:
- image: kuryr/demo
imagePullPolicy: Always
name: demo-pod
$ kubectl apply -f sample-pod.yml
$ curl demo-pod-IP:8080
NO REPLY
11. If we add to the pod a label that match a network policy podSelector, in
this case 'project: default', the network policy will get applied on the
pod, and the traffic will be allowed::
$ kubectl label pod demo-pod project=default
$ curl demo-pod-IP:8080
demo-pod-XXX: HELLO! I AM ALIVE!!!
12. Confirm the teardown of the resources once the network policy is removed::
$ kubectl delete -f network_policy.yml
$ kubectl get kuryrnetpolicies

View File

@ -36,6 +36,7 @@ K8S_POD_STATUS_PENDING = 'Pending'
K8S_ANNOTATION_PREFIX = 'openstack.org/kuryr'
K8S_ANNOTATION_VIF = K8S_ANNOTATION_PREFIX + '-vif'
K8S_ANNOTATION_LABEL = K8S_ANNOTATION_PREFIX + '-pod-label'
K8S_ANNOTATION_LBAAS_SPEC = K8S_ANNOTATION_PREFIX + '-lbaas-spec'
K8S_ANNOTATION_LBAAS_STATE = K8S_ANNOTATION_PREFIX + '-lbaas-state'
K8S_ANNOTATION_NET_CRD = K8S_ANNOTATION_PREFIX + '-net-crd'

View File

@ -0,0 +1,94 @@
# Copyright 2018 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_log import log as logging
from oslo_serialization import jsonutils
from kuryr_kubernetes import clients
from kuryr_kubernetes import constants
from kuryr_kubernetes.controller.drivers import base as drivers
from kuryr_kubernetes import exceptions
from kuryr_kubernetes.handlers import k8s_base
LOG = logging.getLogger(__name__)
class PodLabelHandler(k8s_base.ResourceEventHandler):
"""Controller side of Pod Label process for Kubernetes pods.
`PodLabelHandler` runs on the Kuryr-Kubernetes controller and is
responsible for triggering the vif port updates upon pod labels changes.
"""
OBJECT_KIND = constants.K8S_OBJ_POD
OBJECT_WATCH_PATH = "%s/%s" % (constants.K8S_API_BASE, "pods")
def __init__(self):
super(PodLabelHandler, self).__init__()
self._drv_project = drivers.PodProjectDriver.get_instance()
self._drv_sg = drivers.PodSecurityGroupsDriver.get_instance()
self._drv_vif_pool = drivers.VIFPoolDriver.get_instance(
specific_driver='multi_pool')
self._drv_vif_pool.set_vif_driver()
def on_modified(self, pod):
if not self._has_pod_state(pod):
# NOTE(ltomasbo): Ensuring the event is retried and the right
# pod label annotation is added to the pod
raise exceptions.ResourceNotReady(pod)
current_pod_labels = pod['metadata'].get('labels')
previous_pod_labels = self._get_pod_labels(pod)
LOG.debug("Got previous pod labels from annotation: %r",
previous_pod_labels)
if current_pod_labels == previous_pod_labels:
return
project_id = self._drv_project.get_project(pod)
security_groups = self._drv_sg.get_security_groups(pod, project_id)
self._drv_vif_pool.update_vif_sgs(pod, security_groups)
self._set_pod_labels(pod, current_pod_labels)
def _get_pod_labels(self, pod):
try:
annotations = pod['metadata']['annotations']
pod_labels_annotation = annotations[constants.K8S_ANNOTATION_LABEL]
except KeyError:
return None
pod_labels = jsonutils.loads(pod_labels_annotation)
return pod_labels
def _set_pod_labels(self, pod, labels):
if not labels:
LOG.debug("Removing Label annotation: %r", labels)
annotation = None
else:
annotation = jsonutils.dumps(labels, sort_keys=True)
LOG.debug("Setting Labels annotation: %r", annotation)
k8s = clients.get_kubernetes_client()
k8s.annotate(pod['metadata']['selfLink'],
{constants.K8S_ANNOTATION_LABEL: annotation},
resource_version=pod['metadata']['resourceVersion'])
def _has_pod_state(self, pod):
try:
pod_state = pod['metadata']['annotations'][
constants.K8S_ANNOTATION_VIF]
LOG.debug("Pod state is: %s", pod_state)
except KeyError:
return False
return True

View File

@ -0,0 +1,120 @@
# Copyright 2018 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from kuryr_kubernetes import constants as k_const
from kuryr_kubernetes.controller.drivers import base as drivers
from kuryr_kubernetes.controller.handlers import pod_label as p_label
from kuryr_kubernetes import exceptions
from kuryr_kubernetes.tests import base as test_base
class TestPodLabelHandler(test_base.TestCase):
def setUp(self):
super(TestPodLabelHandler, self).setUp()
self._project_id = mock.sentinel.project_id
self._sg_id = mock.sentinel.sg_id
self._pod_version = mock.sentinel.pod_version
self._pod_link = mock.sentinel.pod_link
self._pod = {
'metadata': {'resourceVersion': self._pod_version,
'selfLink': self._pod_link},
'status': {'phase': k_const.K8S_POD_STATUS_PENDING},
'spec': {'hostNetwork': False,
'nodeName': 'hostname'}
}
self._handler = mock.MagicMock(spec=p_label.PodLabelHandler)
self._handler._drv_project = mock.Mock(spec=drivers.PodProjectDriver)
self._handler._drv_sg = mock.Mock(spec=drivers.PodSecurityGroupsDriver)
self._handler._drv_vif_pool = mock.MagicMock(
spec=drivers.VIFPoolDriver)
self._get_project = self._handler._drv_project.get_project
self._get_security_groups = self._handler._drv_sg.get_security_groups
self._set_vif_driver = self._handler._drv_vif_pool.set_vif_driver
self._get_pod_labels = self._handler._get_pod_labels
self._set_pod_labels = self._handler._set_pod_labels
self._has_pod_state = self._handler._has_pod_state
self._update_vif_sgs = self._handler._drv_vif_pool.update_vif_sgs
self._get_project.return_value = self._project_id
self._get_security_groups.return_value = [self._sg_id]
@mock.patch.object(drivers.VIFPoolDriver, 'get_instance')
@mock.patch.object(drivers.PodSecurityGroupsDriver, 'get_instance')
@mock.patch.object(drivers.PodProjectDriver, 'get_instance')
def test_init(self, m_get_project_driver, m_get_sg_driver,
m_get_vif_pool_driver):
project_driver = mock.sentinel.project_driver
sg_driver = mock.sentinel.sg_driver
vif_pool_driver = mock.Mock(spec=drivers.VIFPoolDriver)
m_get_project_driver.return_value = project_driver
m_get_sg_driver.return_value = sg_driver
m_get_vif_pool_driver.return_value = vif_pool_driver
handler = p_label.PodLabelHandler()
self.assertEqual(project_driver, handler._drv_project)
self.assertEqual(sg_driver, handler._drv_sg)
self.assertEqual(vif_pool_driver, handler._drv_vif_pool)
def test_on_modified(self):
self._has_pod_state.return_value = True
self._get_pod_labels.return_value = {'test1': 'test'}
p_label.PodLabelHandler.on_modified(self._handler, self._pod)
self._has_pod_state.assert_called_once_with(self._pod)
self._get_pod_labels.assert_called_once_with(self._pod)
self._get_project.assert_called_once()
self._get_security_groups.assert_called_once()
self._update_vif_sgs.assert_called_once_with(self._pod, [self._sg_id])
self._set_pod_labels.assert_called_once_with(self._pod, None)
def test_on_modified_no_state(self):
self._has_pod_state.return_value = False
self.assertRaises(exceptions.ResourceNotReady,
p_label.PodLabelHandler.on_modified, self._handler,
self._pod)
self._has_pod_state.assert_called_once_with(self._pod)
self._get_pod_labels.assert_not_called()
self._set_pod_labels.assert_not_called()
def test_on_modified_no_labels(self):
self._has_pod_state.return_value = True
self._get_pod_labels.return_value = None
p_label.PodLabelHandler.on_modified(self._handler, self._pod)
self._has_pod_state.assert_called_once_with(self._pod)
self._get_pod_labels.assert_called_once_with(self._pod)
self._set_pod_labels.assert_not_called()
def test_on_modified_no_changes(self):
self._has_pod_state.return_value = True
pod_with_label = self._pod.copy()
pod_with_label['metadata']['labels'] = {'test1': 'test'}
self._get_pod_labels.return_value = {'test1': 'test'}
p_label.PodLabelHandler.on_modified(self._handler, pod_with_label)
self._has_pod_state.assert_called_once_with(pod_with_label)
self._get_pod_labels.assert_called_once_with(pod_with_label)
self._set_pod_labels.assert_not_called()

View File

@ -102,6 +102,7 @@ kuryr_kubernetes.controller.handlers =
ingresslb = kuryr_kubernetes.controller.handlers.ingress_lbaas:IngressLoadBalancerHandler
ocproute = kuryr_kubernetes.platform.ocp.controller.handlers.route:OcpRouteHandler
policy = kuryr_kubernetes.controller.handlers.policy:NetworkPolicyHandler
pod_label = kuryr_kubernetes.controller.handlers.pod_label:PodLabelHandler
test_handler = kuryr_kubernetes.tests.unit.controller.handlers.test_fake_handler:TestHandler
kuryr_kubernetes.controller.drivers.multi_vif =