Add support to match expressions in Network Policies
This commit adds support to match expressions and redesigns how Security Groups are fetched and applied to pods that matches a Network Policy. Change-Id: I5d2831b09e24a2adc3c26aa29f1e7b131e9314ba
This commit is contained in:
parent
791d8dc29d
commit
8f42cd84d1
|
@ -72,3 +72,8 @@ DEFAULT_IFNAME = 'eth0'
|
||||||
|
|
||||||
ADDITIONAL_IFNAME_PREFIX = 'eth'
|
ADDITIONAL_IFNAME_PREFIX = 'eth'
|
||||||
K8S_NPWG_SRIOV_PREFIX = "intel.com/sriov"
|
K8S_NPWG_SRIOV_PREFIX = "intel.com/sriov"
|
||||||
|
|
||||||
|
K8S_OPERATOR_IN = 'in'
|
||||||
|
K8S_OPERATOR_NOT_IN = 'notin'
|
||||||
|
K8S_OPERATOR_DOES_NOT_EXIST = 'doesnotexist'
|
||||||
|
K8S_OPERATOR_EXISTS = 'exists'
|
||||||
|
|
|
@ -22,6 +22,7 @@ from neutronclient.common import exceptions as n_exc
|
||||||
from kuryr_kubernetes import clients
|
from kuryr_kubernetes import clients
|
||||||
from kuryr_kubernetes import constants
|
from kuryr_kubernetes import constants
|
||||||
from kuryr_kubernetes.controller.drivers import base
|
from kuryr_kubernetes.controller.drivers import base
|
||||||
|
from kuryr_kubernetes.controller.drivers import utils
|
||||||
from kuryr_kubernetes import exceptions
|
from kuryr_kubernetes import exceptions
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -120,10 +121,7 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver):
|
||||||
'egressSgRules': e_rules,
|
'egressSgRules': e_rules,
|
||||||
'podSelector': pod_selector,
|
'podSelector': pod_selector,
|
||||||
'networkpolicy_spec': policy['spec']})
|
'networkpolicy_spec': policy['spec']})
|
||||||
# TODO(ltomasbo): allow patching both spec and metadata in the
|
|
||||||
# same call
|
|
||||||
self.kubernetes.patch('metadata', crd['metadata']['selfLink'],
|
|
||||||
{'labels': pod_selector.get('matchLabels')})
|
|
||||||
except exceptions.K8sClientException:
|
except exceptions.K8sClientException:
|
||||||
LOG.exception('Error updating kuryrnetpolicy CRD %s', crd_name)
|
LOG.exception('Error updating kuryrnetpolicy CRD %s', crd_name)
|
||||||
raise
|
raise
|
||||||
|
@ -443,13 +441,6 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver):
|
||||||
'networkpolicy_spec': policy['spec']
|
'networkpolicy_spec': policy['spec']
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if pod_selector:
|
|
||||||
try:
|
|
||||||
netpolicy_crd['metadata']['labels'] = pod_selector[
|
|
||||||
'matchLabels']
|
|
||||||
except KeyError:
|
|
||||||
# NOTE(ltomasbo): Only supporting matchLabels for now
|
|
||||||
LOG.info("Pod Selector only allowed with matchLabels")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
LOG.debug("Creating KuryrNetPolicy CRD %s" % netpolicy_crd)
|
LOG.debug("Creating KuryrNetPolicy CRD %s" % netpolicy_crd)
|
||||||
|
@ -482,18 +473,8 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver):
|
||||||
else:
|
else:
|
||||||
pod_selector = policy['spec'].get('podSelector')
|
pod_selector = policy['spec'].get('podSelector')
|
||||||
if pod_selector:
|
if pod_selector:
|
||||||
pod_label = pod_selector['matchLabels']
|
policy_namespace = policy['metadata']['namespace']
|
||||||
pod_namespace = policy['metadata']['namespace']
|
pods = utils.get_pods(pod_selector, policy_namespace)
|
||||||
# Removing pod-template-hash as pods will not have it and
|
|
||||||
# otherwise there will be no match
|
|
||||||
pod_label.pop('pod-template-hash', None)
|
|
||||||
pod_label = urlencode(pod_label)
|
|
||||||
# NOTE(ltomasbo): K8s API does not accept &, so we need to AND
|
|
||||||
# the matchLabels with ',' or '%2C' instead
|
|
||||||
pod_label = pod_label.replace('&', ',')
|
|
||||||
pods = self.kubernetes.get(
|
|
||||||
'{}/namespaces/{}/pods?labelSelector={}'.format(
|
|
||||||
constants.K8S_API_BASE, pod_namespace, pod_label))
|
|
||||||
return pods.get('items')
|
return pods.get('items')
|
||||||
else:
|
else:
|
||||||
# NOTE(ltomasbo): It affects all the pods on the namespace
|
# NOTE(ltomasbo): It affects all the pods on the namespace
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from six.moves.urllib.parse import urlencode
|
|
||||||
|
|
||||||
from kuryr_kubernetes import clients
|
from kuryr_kubernetes import clients
|
||||||
from kuryr_kubernetes import config
|
from kuryr_kubernetes import config
|
||||||
|
@ -26,25 +25,19 @@ from oslo_log import log as logging
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _get_kuryrnetpolicy_crds(labels=None, namespace='default'):
|
OPERATORS_WITH_VALUES = [constants.K8S_OPERATOR_IN,
|
||||||
|
constants.K8S_OPERATOR_NOT_IN]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_kuryrnetpolicy_crds(namespace='default'):
|
||||||
kubernetes = clients.get_kubernetes_client()
|
kubernetes = clients.get_kubernetes_client()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if labels:
|
knp_path = '{}/{}/kuryrnetpolicies'.format(
|
||||||
LOG.debug("Using labels %s", labels)
|
constants.K8S_API_CRD_NAMESPACES, namespace)
|
||||||
labels.pop('pod-template-hash', None)
|
|
||||||
# removing pod-template-hash is necessary to fetch the proper list
|
|
||||||
labels = urlencode(labels)
|
|
||||||
# NOTE(maysams): K8s API does not accept &, so we need to replace
|
|
||||||
# it with ',' or '%2C' instead
|
|
||||||
labels = labels.replace('&', ',')
|
|
||||||
knp_path = '{}/{}/kuryrnetpolicies?labelSelector={}'.format(
|
|
||||||
constants.K8S_API_CRD_NAMESPACES, namespace, labels)
|
|
||||||
LOG.debug("K8s API Query %s", knp_path)
|
LOG.debug("K8s API Query %s", knp_path)
|
||||||
knps = kubernetes.get(knp_path)
|
knps = kubernetes.get(knp_path)
|
||||||
LOG.debug("Return Kuryr Network Policies with label %s", knps)
|
LOG.debug("Return Kuryr Network Policies with label %s", knps)
|
||||||
else:
|
|
||||||
knps = kubernetes.get('{}/{}/kuryrnetpolicies'.format(
|
|
||||||
constants.K8S_API_CRD_NAMESPACES, namespace))
|
|
||||||
except exceptions.K8sResourceNotFound:
|
except exceptions.K8sResourceNotFound:
|
||||||
LOG.exception("KuryrNetPolicy CRD not found")
|
LOG.exception("KuryrNetPolicy CRD not found")
|
||||||
raise
|
raise
|
||||||
|
@ -54,29 +47,76 @@ def _get_kuryrnetpolicy_crds(labels=None, namespace='default'):
|
||||||
return knps
|
return knps
|
||||||
|
|
||||||
|
|
||||||
|
def _match_expressions(expressions, pod_labels):
|
||||||
|
for exp in expressions:
|
||||||
|
exp_op = exp['operator'].lower()
|
||||||
|
if pod_labels:
|
||||||
|
if exp_op in OPERATORS_WITH_VALUES:
|
||||||
|
exp_values = exp['values']
|
||||||
|
pod_value = pod_labels.get(str(exp['key']), None)
|
||||||
|
if exp_op == constants.K8S_OPERATOR_IN:
|
||||||
|
if pod_value is None or pod_value not in exp_values:
|
||||||
|
return False
|
||||||
|
elif exp_op == constants.K8S_OPERATOR_NOT_IN:
|
||||||
|
if pod_value in exp_values:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if exp_op == constants.K8S_OPERATOR_EXISTS:
|
||||||
|
exists = pod_labels.get(str(exp['key']), None)
|
||||||
|
if exists is None:
|
||||||
|
return False
|
||||||
|
elif exp_op == constants.K8S_OPERATOR_DOES_NOT_EXIST:
|
||||||
|
exists = pod_labels.get(str(exp['key']), None)
|
||||||
|
if exists is not None:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if exp_op in (constants.K8S_OPERATOR_IN,
|
||||||
|
constants.K8S_OPERATOR_EXISTS):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _match_labels(crd_labels, pod_labels):
|
||||||
|
for label_key, label_value in crd_labels.items():
|
||||||
|
pod_value = pod_labels.get(label_key, None)
|
||||||
|
if not pod_value or label_value != pod_value:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class NetworkPolicySecurityGroupsDriver(base.PodSecurityGroupsDriver):
|
class NetworkPolicySecurityGroupsDriver(base.PodSecurityGroupsDriver):
|
||||||
"""Provides security groups for pods based on network policies"""
|
"""Provides security groups for pods based on network policies"""
|
||||||
|
|
||||||
def get_security_groups(self, pod, project_id):
|
def get_security_groups(self, pod, project_id):
|
||||||
sg_list = []
|
sg_list = []
|
||||||
pod_namespace = pod['metadata']['namespace']
|
|
||||||
pod_labels = pod['metadata'].get('labels')
|
pod_labels = pod['metadata'].get('labels')
|
||||||
LOG.debug("Using labels %s", pod_labels)
|
pod_namespace = pod['metadata']['namespace']
|
||||||
|
|
||||||
if pod_labels:
|
knp_crds = _get_kuryrnetpolicy_crds(namespace=pod_namespace)
|
||||||
knp_crds = _get_kuryrnetpolicy_crds(pod_labels,
|
|
||||||
namespace=pod_namespace)
|
|
||||||
for crd in knp_crds.get('items'):
|
for crd in knp_crds.get('items'):
|
||||||
|
pod_selector = crd['spec'].get('podSelector')
|
||||||
|
if pod_selector:
|
||||||
|
crd_labels = pod_selector.get('matchLabels', None)
|
||||||
|
crd_expressions = pod_selector.get('matchExpressions', None)
|
||||||
|
|
||||||
|
match_exp = match_lb = True
|
||||||
|
if crd_expressions:
|
||||||
|
match_exp = _match_expressions(crd_expressions,
|
||||||
|
pod_labels)
|
||||||
|
if crd_labels and pod_labels:
|
||||||
|
match_lb = _match_labels(crd_labels, pod_labels)
|
||||||
|
if match_exp and match_lb:
|
||||||
|
LOG.debug("Appending %s",
|
||||||
|
str(crd['spec']['securityGroupId']))
|
||||||
|
sg_list.append(str(crd['spec']['securityGroupId']))
|
||||||
|
else:
|
||||||
LOG.debug("Appending %s", str(crd['spec']['securityGroupId']))
|
LOG.debug("Appending %s", str(crd['spec']['securityGroupId']))
|
||||||
sg_list.append(str(crd['spec']['securityGroupId']))
|
sg_list.append(str(crd['spec']['securityGroupId']))
|
||||||
|
|
||||||
knp_namespace_crds = _get_kuryrnetpolicy_crds(namespace=pod_namespace)
|
# NOTE(maysams) Pods that are not selected by any Networkpolicy
|
||||||
for crd in knp_namespace_crds.get('items'):
|
# are fully accessible. Thus, the default security group is associated.
|
||||||
if not crd['metadata'].get('labels'):
|
if not sg_list:
|
||||||
LOG.debug("Appending %s", str(crd['spec']['securityGroupId']))
|
|
||||||
sg_list.append(str(crd['spec']['securityGroupId']))
|
|
||||||
|
|
||||||
if not knp_namespace_crds.get('items') and not sg_list:
|
|
||||||
sg_list = config.CONF.neutron_defaults.pod_security_groups
|
sg_list = config.CONF.neutron_defaults.pod_security_groups
|
||||||
if not sg_list:
|
if not sg_list:
|
||||||
raise cfg.RequiredOptError('pod_security_groups',
|
raise cfg.RequiredOptError('pod_security_groups',
|
||||||
|
@ -104,20 +144,30 @@ class NetworkPolicyServiceSecurityGroupsDriver(
|
||||||
svc_labels = service['metadata'].get('labels')
|
svc_labels = service['metadata'].get('labels')
|
||||||
LOG.debug("Using labels %s", svc_labels)
|
LOG.debug("Using labels %s", svc_labels)
|
||||||
|
|
||||||
if svc_labels:
|
knp_crds = _get_kuryrnetpolicy_crds(namespace=svc_namespace)
|
||||||
knp_crds = _get_kuryrnetpolicy_crds(svc_labels,
|
|
||||||
namespace=svc_namespace)
|
|
||||||
for crd in knp_crds.get('items'):
|
for crd in knp_crds.get('items'):
|
||||||
|
pod_selector = crd['spec'].get('podSelector')
|
||||||
|
if pod_selector:
|
||||||
|
crd_labels = pod_selector.get('matchLabels', None)
|
||||||
|
crd_expressions = pod_selector.get('matchExpressions', None)
|
||||||
|
|
||||||
|
match_exp = match_lb = True
|
||||||
|
if crd_expressions:
|
||||||
|
match_exp = _match_expressions(crd_expressions,
|
||||||
|
svc_labels)
|
||||||
|
if crd_labels and svc_labels:
|
||||||
|
match_lb = _match_labels(crd_labels, svc_labels)
|
||||||
|
if match_exp and match_lb:
|
||||||
|
LOG.debug("Appending %s",
|
||||||
|
str(crd['spec']['securityGroupId']))
|
||||||
|
sg_list.append(str(crd['spec']['securityGroupId']))
|
||||||
|
else:
|
||||||
LOG.debug("Appending %s", str(crd['spec']['securityGroupId']))
|
LOG.debug("Appending %s", str(crd['spec']['securityGroupId']))
|
||||||
sg_list.append(str(crd['spec']['securityGroupId']))
|
sg_list.append(str(crd['spec']['securityGroupId']))
|
||||||
|
|
||||||
knp_namespace_crds = _get_kuryrnetpolicy_crds(namespace=svc_namespace)
|
# NOTE(maysams) Pods that are not selected by any Networkpolicy
|
||||||
for crd in knp_namespace_crds.get('items'):
|
# are fully accessible. Thus, the default security group is associated.
|
||||||
if not crd['metadata'].get('labels'):
|
if not sg_list:
|
||||||
LOG.debug("Appending %s", str(crd['spec']['securityGroupId']))
|
|
||||||
sg_list.append(str(crd['spec']['securityGroupId']))
|
|
||||||
|
|
||||||
if not knp_namespace_crds.get('items') and not sg_list:
|
|
||||||
sg_list = config.CONF.neutron_defaults.pod_security_groups
|
sg_list = config.CONF.neutron_defaults.pod_security_groups
|
||||||
if not sg_list:
|
if not sg_list:
|
||||||
raise cfg.RequiredOptError('pod_security_groups',
|
raise cfg.RequiredOptError('pod_security_groups',
|
||||||
|
|
|
@ -14,12 +14,17 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
|
from six.moves.urllib import parse
|
||||||
|
|
||||||
|
from kuryr_kubernetes import clients
|
||||||
from kuryr_kubernetes import constants
|
from kuryr_kubernetes import constants
|
||||||
from kuryr_kubernetes import exceptions as k_exc
|
from kuryr_kubernetes import exceptions as k_exc
|
||||||
from kuryr_kubernetes import os_vif_util as ovu
|
from kuryr_kubernetes import os_vif_util as ovu
|
||||||
from kuryr_kubernetes import utils
|
from kuryr_kubernetes import utils
|
||||||
|
|
||||||
|
OPERATORS_WITH_VALUES = [constants.K8S_OPERATOR_IN,
|
||||||
|
constants.K8S_OPERATOR_NOT_IN]
|
||||||
|
|
||||||
|
|
||||||
def get_network_id(subnets):
|
def get_network_id(subnets):
|
||||||
ids = ovu.osvif_to_neutron_network_ids(subnets)
|
ids = ovu.osvif_to_neutron_network_ids(subnets)
|
||||||
|
@ -58,3 +63,51 @@ def get_pod_state(pod):
|
||||||
|
|
||||||
def is_host_network(pod):
|
def is_host_network(pod):
|
||||||
return pod['spec'].get('hostNetwork', False)
|
return pod['spec'].get('hostNetwork', False)
|
||||||
|
|
||||||
|
|
||||||
|
def get_pods(selector, namespace):
|
||||||
|
kubernetes = clients.get_kubernetes_client()
|
||||||
|
labels = selector.get('matchLabels', None)
|
||||||
|
if labels:
|
||||||
|
# Removing pod-template-hash as pods will not have it and
|
||||||
|
# otherwise there will be no match
|
||||||
|
labels.pop('pod-template-hash', None)
|
||||||
|
labels = replace_encoded_characters(labels)
|
||||||
|
|
||||||
|
exps = selector.get('matchExpressions', None)
|
||||||
|
if exps:
|
||||||
|
exps = ', '.join(format_expression(exp) for exp in exps)
|
||||||
|
if labels:
|
||||||
|
expressions = parse.quote("," + exps)
|
||||||
|
labels += expressions
|
||||||
|
else:
|
||||||
|
labels = parse.quote(exps)
|
||||||
|
|
||||||
|
pods = kubernetes.get(
|
||||||
|
'{}/namespaces/{}/pods?labelSelector={}'.format(
|
||||||
|
constants.K8S_API_BASE, namespace, labels))
|
||||||
|
|
||||||
|
return pods
|
||||||
|
|
||||||
|
|
||||||
|
def format_expression(expression):
|
||||||
|
key = expression['key']
|
||||||
|
operator = expression['operator'].lower()
|
||||||
|
if operator in OPERATORS_WITH_VALUES:
|
||||||
|
values = expression['values']
|
||||||
|
values = str(', '.join(values))
|
||||||
|
values = "(%s)" % values
|
||||||
|
return "%s %s %s" % (key, operator, values)
|
||||||
|
else:
|
||||||
|
if operator == constants.K8S_OPERATOR_DOES_NOT_EXIST:
|
||||||
|
return "!%s" % key
|
||||||
|
else:
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
def replace_encoded_characters(labels):
|
||||||
|
labels = parse.urlencode(labels)
|
||||||
|
# NOTE(ltomasbo): K8s API does not accept &, so we need to AND
|
||||||
|
# the matchLabels with ',' or '%2C' instead
|
||||||
|
labels = labels.replace('&', ',')
|
||||||
|
return labels
|
||||||
|
|
|
@ -61,12 +61,6 @@ class NetworkPolicyHandler(k8s_base.ResourceEventHandler):
|
||||||
project_id = self._drv_project.get_project(policy)
|
project_id = self._drv_project.get_project(policy)
|
||||||
pods_to_update = []
|
pods_to_update = []
|
||||||
|
|
||||||
knps_on_namespace = self._drv_policy.knps_on_namespace(
|
|
||||||
policy['metadata']['namespace'])
|
|
||||||
if not knps_on_namespace:
|
|
||||||
namespace_pods = self._drv_policy.namespaced_pods(policy)
|
|
||||||
pods_to_update.extend(namespace_pods)
|
|
||||||
|
|
||||||
modified_pods = self._drv_policy.ensure_network_policy(policy,
|
modified_pods = self._drv_policy.ensure_network_policy(policy,
|
||||||
project_id)
|
project_id)
|
||||||
if modified_pods:
|
if modified_pods:
|
||||||
|
@ -89,18 +83,15 @@ class NetworkPolicyHandler(k8s_base.ResourceEventHandler):
|
||||||
pod_sgs = self._drv_pod_sg.get_security_groups(pod, project_id)
|
pod_sgs = self._drv_pod_sg.get_security_groups(pod, project_id)
|
||||||
if crd_sg in pod_sgs:
|
if crd_sg in pod_sgs:
|
||||||
pod_sgs.remove(crd_sg)
|
pod_sgs.remove(crd_sg)
|
||||||
|
if not pod_sgs:
|
||||||
|
pod_sgs = oslo_cfg.CONF.neutron_defaults.pod_security_groups
|
||||||
|
if not pod_sgs:
|
||||||
|
raise oslo_cfg.RequiredOptError('pod_security_groups',
|
||||||
|
oslo_cfg.OptGroup(
|
||||||
|
'neutron_defaults'))
|
||||||
self._drv_vif_pool.update_vif_sgs(pod, pod_sgs)
|
self._drv_vif_pool.update_vif_sgs(pod, pod_sgs)
|
||||||
|
|
||||||
self._drv_policy.release_network_policy(netpolicy_crd)
|
self._drv_policy.release_network_policy(netpolicy_crd)
|
||||||
# re-apply original security groups for the namespace
|
|
||||||
knps_on_namespace = self._drv_policy.knps_on_namespace(
|
|
||||||
policy['metadata']['namespace'])
|
|
||||||
if not knps_on_namespace:
|
|
||||||
namespace_pods = self._drv_policy.namespaced_pods(policy)
|
|
||||||
for pod in namespace_pods:
|
|
||||||
pod_sgs = self._drv_pod_sg.get_security_groups(pod,
|
|
||||||
project_id)
|
|
||||||
self._drv_vif_pool.update_vif_sgs(pod, pod_sgs)
|
|
||||||
|
|
||||||
@MEMOIZE
|
@MEMOIZE
|
||||||
def is_ready(self, quota):
|
def is_ready(self, quota):
|
||||||
|
|
|
@ -25,9 +25,9 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestNetworkPolicySecurityGroupsDriver, self).setUp()
|
super(TestNetworkPolicySecurityGroupsDriver, self).setUp()
|
||||||
self._labels = mock.sentinel.labels
|
|
||||||
self._project_id = mock.sentinel.project_id
|
self._project_id = mock.sentinel.project_id
|
||||||
self._sg_id = mock.sentinel.sg_id
|
self._sg_id = mock.sentinel.sg_id
|
||||||
|
self._sg_id2 = mock.sentinel._sg_id2
|
||||||
self._namespace = 'default'
|
self._namespace = 'default'
|
||||||
self._crd = {
|
self._crd = {
|
||||||
'metadata': {'name': mock.sentinel.name,
|
'metadata': {'name': mock.sentinel.name,
|
||||||
|
@ -55,9 +55,38 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase):
|
||||||
'security_group_id': self._sg_id,
|
'security_group_id': self._sg_id,
|
||||||
'id': mock.sentinel.id
|
'id': mock.sentinel.id
|
||||||
}}],
|
}}],
|
||||||
|
'podSelector': {
|
||||||
|
'matchExpressions': [
|
||||||
|
{
|
||||||
|
'key': 'environment',
|
||||||
|
'operator': 'In',
|
||||||
|
'values': [
|
||||||
|
'production']}],
|
||||||
|
'matchLabels': {
|
||||||
|
'run': 'demo'
|
||||||
|
}},
|
||||||
'securityGroupId': self._sg_id,
|
'securityGroupId': self._sg_id,
|
||||||
'securityGroupName': mock.sentinel.sg_name}}
|
'securityGroupName': mock.sentinel.sg_name}}
|
||||||
|
|
||||||
|
self._crd2 = {
|
||||||
|
'metadata': {'name': mock.sentinel.name3,
|
||||||
|
'selfLink': mock.sentinel.selfLink},
|
||||||
|
'spec': {
|
||||||
|
'ingressSgRules': [
|
||||||
|
{'security_group_rule':
|
||||||
|
{'description': 'Kuryr-Kubernetes NetPolicy SG rule',
|
||||||
|
'direction': 'ingress',
|
||||||
|
'ethertype': 'IPv4',
|
||||||
|
'port_range_max': 8080,
|
||||||
|
'port_range_min': 8080,
|
||||||
|
'protocol': 'tcp',
|
||||||
|
'security_group_id': self._sg_id2,
|
||||||
|
'id': mock.sentinel.id
|
||||||
|
}}],
|
||||||
|
'podSelector': {},
|
||||||
|
'securityGroupId': self._sg_id2,
|
||||||
|
'securityGroupName': mock.sentinel.sg_name}}
|
||||||
|
|
||||||
self._crds = {
|
self._crds = {
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"items": [self._crd],
|
"items": [self._crd],
|
||||||
|
@ -66,6 +95,14 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase):
|
||||||
"resourceVersion": "",
|
"resourceVersion": "",
|
||||||
"selfLink": mock.sentinel.selfLink}}
|
"selfLink": mock.sentinel.selfLink}}
|
||||||
|
|
||||||
|
self._multiple_crds = {
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"items": [self._crd, self._crd2],
|
||||||
|
"kind": "List",
|
||||||
|
"metadata": {
|
||||||
|
"resourceVersion": "",
|
||||||
|
"selfLink": mock.sentinel.selfLink}}
|
||||||
|
|
||||||
self._empty_crds = {
|
self._empty_crds = {
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"items": [],
|
"items": [],
|
||||||
|
@ -81,7 +118,39 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase):
|
||||||
'name': mock.sentinel.pod_name,
|
'name': mock.sentinel.pod_name,
|
||||||
'namespace': self._namespace,
|
'namespace': self._namespace,
|
||||||
'labels': {
|
'labels': {
|
||||||
'run': 'demo'}},
|
'run': 'demo',
|
||||||
|
'environment': 'production'}},
|
||||||
|
'spec': {
|
||||||
|
'containers': [{
|
||||||
|
'image': 'kuryr/demo',
|
||||||
|
'imagePullPolicy': 'Always',
|
||||||
|
'name': mock.sentinel.pod_name
|
||||||
|
}]
|
||||||
|
}}
|
||||||
|
|
||||||
|
self._pod2 = {
|
||||||
|
'apiVersion': 'v1',
|
||||||
|
'kind': 'Pod',
|
||||||
|
'metadata': {
|
||||||
|
'name': mock.sentinel.pod_name,
|
||||||
|
'namespace': self._namespace,
|
||||||
|
'labels': {
|
||||||
|
'run': 'demo',
|
||||||
|
'environment': 'development'}},
|
||||||
|
'spec': {
|
||||||
|
'containers': [{
|
||||||
|
'image': 'kuryr/demo',
|
||||||
|
'imagePullPolicy': 'Always',
|
||||||
|
'name': mock.sentinel.pod_name
|
||||||
|
}]
|
||||||
|
}}
|
||||||
|
|
||||||
|
self._pod_without_label = {
|
||||||
|
'apiVersion': 'v1',
|
||||||
|
'kind': 'Pod',
|
||||||
|
'metadata': {
|
||||||
|
'name': mock.sentinel.pod_name,
|
||||||
|
'namespace': self._namespace},
|
||||||
'spec': {
|
'spec': {
|
||||||
'containers': [{
|
'containers': [{
|
||||||
'image': 'kuryr/demo',
|
'image': 'kuryr/demo',
|
||||||
|
@ -94,35 +163,92 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase):
|
||||||
self._driver = (
|
self._driver = (
|
||||||
network_policy_security_groups.NetworkPolicySecurityGroupsDriver())
|
network_policy_security_groups.NetworkPolicySecurityGroupsDriver())
|
||||||
|
|
||||||
|
@mock.patch('kuryr_kubernetes.config.CONF')
|
||||||
@mock.patch.object(network_policy_security_groups,
|
@mock.patch.object(network_policy_security_groups,
|
||||||
'_get_kuryrnetpolicy_crds')
|
'_get_kuryrnetpolicy_crds')
|
||||||
def test_get_security_groups(self, m_get_crds):
|
def test_get_sgs_for_pod_without_label(self, m_get_crds, m_cfg):
|
||||||
m_get_crds.return_value = self._crds
|
m_get_crds.return_value = self._crds
|
||||||
self._driver.get_security_groups(self._pod, self._project_id)
|
sg_list = [str(mock.sentinel.sg_id)]
|
||||||
calls = [mock.call(self._pod['metadata']['labels'],
|
m_cfg.neutron_defaults.pod_security_groups = sg_list
|
||||||
namespace=self._namespace),
|
|
||||||
mock.call(namespace=self._namespace)]
|
sgs = self._driver.get_security_groups(self._pod_without_label,
|
||||||
m_get_crds.assert_has_calls(calls)
|
self._project_id)
|
||||||
|
|
||||||
@mock.patch.object(network_policy_security_groups,
|
|
||||||
'_get_kuryrnetpolicy_crds')
|
|
||||||
def test_get_security_groups_without_label(self, m_get_crds):
|
|
||||||
pod = self._pod.copy()
|
|
||||||
del pod['metadata']['labels']
|
|
||||||
labels = {'run': 'demo'}
|
|
||||||
self._crds['items'][0]['metadata']['labels'] = labels
|
|
||||||
m_get_crds.return_value = self._crds
|
|
||||||
self._driver.get_security_groups(pod, self._project_id)
|
|
||||||
m_get_crds.assert_called_once_with(namespace=self._namespace)
|
m_get_crds.assert_called_once_with(namespace=self._namespace)
|
||||||
|
self.assertEqual(sg_list, sgs)
|
||||||
|
|
||||||
|
@mock.patch.object(network_policy_security_groups,
|
||||||
|
'_match_expressions')
|
||||||
|
@mock.patch.object(network_policy_security_groups,
|
||||||
|
'_match_labels')
|
||||||
|
@mock.patch.object(network_policy_security_groups,
|
||||||
|
'_get_kuryrnetpolicy_crds')
|
||||||
|
def test_get_sgs_for_pod_with_label(self, m_get_crds, m_match_labels,
|
||||||
|
m_match_expressions):
|
||||||
|
m_get_crds.return_value = self._crds
|
||||||
|
m_match_expressions.return_value = True
|
||||||
|
m_match_labels.return_value = True
|
||||||
|
pod_labels = self._pod['metadata']['labels']
|
||||||
|
resp = self._driver.get_security_groups(self._pod, self._project_id)
|
||||||
|
|
||||||
|
m_get_crds.assert_called_once_with(namespace=self._namespace)
|
||||||
|
m_match_expressions.assert_called_once_with(
|
||||||
|
self._crd['spec']['podSelector']['matchExpressions'], pod_labels)
|
||||||
|
m_match_labels.assert_called_once_with(
|
||||||
|
self._crd['spec']['podSelector']['matchLabels'], pod_labels)
|
||||||
|
self.assertEqual(resp, [str(self._sg_id)])
|
||||||
|
|
||||||
|
@mock.patch('kuryr_kubernetes.config.CONF')
|
||||||
|
@mock.patch.object(network_policy_security_groups,
|
||||||
|
'_match_expressions')
|
||||||
|
@mock.patch.object(network_policy_security_groups,
|
||||||
|
'_match_labels')
|
||||||
|
@mock.patch.object(network_policy_security_groups,
|
||||||
|
'_get_kuryrnetpolicy_crds')
|
||||||
|
def test_get_sgs_for_pod_with_label_no_match(self, m_get_crds,
|
||||||
|
m_match_labels,
|
||||||
|
m_match_expressions, m_cfg):
|
||||||
|
m_get_crds.return_value = self._crds
|
||||||
|
m_match_expressions.return_value = False
|
||||||
|
m_match_labels.return_value = True
|
||||||
|
sg_list = [mock.sentinel.sg_id]
|
||||||
|
m_cfg.neutron_defaults.pod_security_groups = sg_list
|
||||||
|
pod_labels = self._pod2['metadata']['labels']
|
||||||
|
|
||||||
|
sgs = self._driver.get_security_groups(self._pod2, self._project_id)
|
||||||
|
|
||||||
|
m_get_crds.assert_called_once_with(namespace=self._namespace)
|
||||||
|
m_match_expressions.assert_called_once_with(
|
||||||
|
self._crd['spec']['podSelector']['matchExpressions'], pod_labels)
|
||||||
|
m_match_labels.assert_called_once_with(
|
||||||
|
self._crd['spec']['podSelector']['matchLabels'], pod_labels)
|
||||||
|
self.assertEqual(sg_list, sgs)
|
||||||
|
|
||||||
@mock.patch.object(network_policy_security_groups,
|
@mock.patch.object(network_policy_security_groups,
|
||||||
'_get_kuryrnetpolicy_crds')
|
'_get_kuryrnetpolicy_crds')
|
||||||
def test_get_security_groups_no_crds(self, m_get_crds):
|
def test_get_sgs_no_crds(self, m_get_crds):
|
||||||
m_get_crds.return_value = self._empty_crds
|
m_get_crds.return_value = self._empty_crds
|
||||||
|
cfg.CONF.set_override('pod_security_groups', [],
|
||||||
|
group='neutron_defaults')
|
||||||
|
|
||||||
self.assertRaises(cfg.RequiredOptError,
|
self.assertRaises(cfg.RequiredOptError,
|
||||||
self._driver.get_security_groups, self._pod,
|
self._driver.get_security_groups, self._pod,
|
||||||
self._project_id)
|
self._project_id)
|
||||||
calls = [mock.call(self._pod['metadata']['labels'],
|
m_get_crds.assert_called_with(namespace=self._namespace)
|
||||||
namespace=self._namespace),
|
|
||||||
mock.call(namespace=self._namespace)]
|
@mock.patch.object(network_policy_security_groups,
|
||||||
m_get_crds.assert_has_calls(calls)
|
'_match_expressions')
|
||||||
|
@mock.patch.object(network_policy_security_groups,
|
||||||
|
'_match_labels')
|
||||||
|
@mock.patch.object(network_policy_security_groups,
|
||||||
|
'_get_kuryrnetpolicy_crds')
|
||||||
|
def test_get_sgs_multiple_crds(self, m_get_crds, m_match_labels,
|
||||||
|
m_match_expressions):
|
||||||
|
m_match_expressions.return_value = True
|
||||||
|
m_match_labels.return_value = True
|
||||||
|
m_get_crds.return_value = self._multiple_crds
|
||||||
|
|
||||||
|
resp = self._driver.get_security_groups(self._pod, self._project_id)
|
||||||
|
|
||||||
|
m_get_crds.assert_called_once_with(namespace=self._namespace)
|
||||||
|
self.assertEqual([str(self._sg_id), str(self._sg_id2)], resp)
|
||||||
|
|
|
@ -139,34 +139,25 @@ class TestPolicyHandler(test_base.TestCase):
|
||||||
def test_on_present_without_knps_on_namespace(self):
|
def test_on_present_without_knps_on_namespace(self):
|
||||||
modified_pod = mock.sentinel.modified_pod
|
modified_pod = mock.sentinel.modified_pod
|
||||||
match_pod = mock.sentinel.match_pod
|
match_pod = mock.sentinel.match_pod
|
||||||
namespace_pod = mock.sentinel.namespace_pod
|
|
||||||
|
|
||||||
knp_on_ns = self._handler._drv_policy.knps_on_namespace
|
|
||||||
knp_on_ns.return_value = False
|
|
||||||
namespaced_pods = self._handler._drv_policy.namespaced_pods
|
|
||||||
namespaced_pods.return_value = [namespace_pod]
|
|
||||||
ensure_nw_policy = self._handler._drv_policy.ensure_network_policy
|
ensure_nw_policy = self._handler._drv_policy.ensure_network_policy
|
||||||
ensure_nw_policy.return_value = [modified_pod]
|
ensure_nw_policy.return_value = [modified_pod]
|
||||||
affected_pods = self._handler._drv_policy.affected_pods
|
affected_pods = self._handler._drv_policy.affected_pods
|
||||||
affected_pods.return_value = [match_pod]
|
affected_pods.return_value = [match_pod]
|
||||||
sg1 = [mock.sentinel.sg1]
|
|
||||||
sg2 = [mock.sentinel.sg2]
|
sg2 = [mock.sentinel.sg2]
|
||||||
sg3 = [mock.sentinel.sg3]
|
sg3 = [mock.sentinel.sg3]
|
||||||
self._get_security_groups.side_effect = [sg1, sg2, sg3]
|
self._get_security_groups.side_effect = [sg2, sg3]
|
||||||
|
|
||||||
policy.NetworkPolicyHandler.on_present(self._handler, self._policy)
|
policy.NetworkPolicyHandler.on_present(self._handler, self._policy)
|
||||||
namespaced_pods.assert_called_once_with(self._policy)
|
|
||||||
ensure_nw_policy.assert_called_once_with(self._policy,
|
ensure_nw_policy.assert_called_once_with(self._policy,
|
||||||
self._project_id)
|
self._project_id)
|
||||||
affected_pods.assert_called_once_with(self._policy)
|
affected_pods.assert_called_once_with(self._policy)
|
||||||
|
|
||||||
calls = [mock.call(namespace_pod, self._project_id),
|
calls = [mock.call(modified_pod, self._project_id),
|
||||||
mock.call(modified_pod, self._project_id),
|
|
||||||
mock.call(match_pod, self._project_id)]
|
mock.call(match_pod, self._project_id)]
|
||||||
self._get_security_groups.assert_has_calls(calls)
|
self._get_security_groups.assert_has_calls(calls)
|
||||||
|
|
||||||
calls = [mock.call(namespace_pod, sg1),
|
calls = [mock.call(modified_pod, sg2),
|
||||||
mock.call(modified_pod, sg2),
|
|
||||||
mock.call(match_pod, sg3)]
|
mock.call(match_pod, sg3)]
|
||||||
self._update_vif_sgs.assert_has_calls(calls)
|
self._update_vif_sgs.assert_has_calls(calls)
|
||||||
|
|
||||||
|
@ -189,8 +180,6 @@ class TestPolicyHandler(test_base.TestCase):
|
||||||
|
|
||||||
policy.NetworkPolicyHandler.on_deleted(self._handler, self._policy)
|
policy.NetworkPolicyHandler.on_deleted(self._handler, self._policy)
|
||||||
release_nw_policy.assert_called_once_with(knp_obj)
|
release_nw_policy.assert_called_once_with(knp_obj)
|
||||||
calls = [mock.call(match_pod, self._project_id),
|
self._get_security_groups.assert_called_once_with(match_pod,
|
||||||
mock.call(namespace_pod, self._project_id)]
|
self._project_id)
|
||||||
self._get_security_groups.assert_has_calls(calls)
|
self._update_vif_sgs.assert_called_once_with(match_pod, sg1)
|
||||||
calls = [mock.call(match_pod, sg1), mock.call(namespace_pod, sg2)]
|
|
||||||
self._update_vif_sgs.assert_has_calls(calls)
|
|
||||||
|
|
Loading…
Reference in New Issue