Ensure pod relabeling is supported by the Network Policy
This patch adds a new handler in charge of reacting to pod relabeling actions. It main purpose is to use it together with the network policy handler and drivers to ensure the right policy is applied upon pod label changes. Partially Implements: blueprint k8s-network-policies Change-Id: If026cefce847f77c54af09a0160eb35343f89f37
This commit is contained in:
parent
4f71019c94
commit
96e314b0a9
@ -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
|
||||
|
@ -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'
|
||||
|
94
kuryr_kubernetes/controller/handlers/pod_label.py
Normal file
94
kuryr_kubernetes/controller/handlers/pod_label.py
Normal 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
|
@ -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()
|
@ -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 =
|
||||
|
Loading…
x
Reference in New Issue
Block a user