Merge "Ensure pod relabeling is supported by the Network Policy"
This commit is contained in:
commit
1b2b217981
@ -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