Michał Dulko ce6fb744b5 Strip leading zeros from "funny" Service IPs
According to [1] we're supposed to support "funny" Service IPs, which
means IPs with leading zeros. Unlike unix in general we should not parse
them as octal values, but rather treat them like without leading zeros.
This commit makes sure that we strip the zeros from both ClusterIP and
LoadBalancerIP before we put them into KuryrLoadBalancer struct. This
means that later on Octavia and Neutron will get values that they
support and will proceed with LB or FIP creation normally.

[1] https://github.com/kubernetes/kubernetes/blob/v1.24.1/test/e2e/network/funny_ips.go

Change-Id: I0aa8d7326dbf40459f73037ae54d2afc01ea9bb6
Closes-Bug: 1978112
2022-06-09 13:47:26 +02:00

449 lines
15 KiB
Python

# Copyright (c) 2016 Mirantis, Inc.
# All Rights Reserved.
#
# 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 unittest import mock
import os_vif.objects.network as osv_network
import os_vif.objects.subnet as osv_subnet
from kuryr_kubernetes.controller.handlers import lbaas as h_lbaas
from kuryr_kubernetes import exceptions as k_exc
from kuryr_kubernetes.tests import base as test_base
_SUPPORTED_LISTENER_PROT = ('HTTP', 'HTTPS', 'TCP')
@mock.patch('kuryr_kubernetes.controller.drivers.base.LBaaSDriver.'
'get_instance', mock.Mock())
@mock.patch('kuryr_kubernetes.clients.get_kubernetes_client', mock.Mock())
class TestServiceHandler(test_base.TestCase):
@mock.patch('kuryr_kubernetes.clients.get_kubernetes_client')
def test_on_present(self, get_k8s_client):
svc_event = {
'apiVersion': 'v1',
'kind': 'Service',
"metadata": {
"creationTimestamp": "2020-07-25T18:15:12Z",
"finalizers": [
"openstack.org/service"
],
"labels": {
"run": "test"
},
"name": "test",
"namespace": "test",
"resourceVersion": "413753",
"uid": "a026ae48-6141-4029-b743-bac48dae7f06"
},
"spec": {
"clusterIP": "2.2.2.2",
"ports": [
{
"port": 1,
"protocol": "TCP",
"targetPort": 1
}
],
"selector": {
"run": "test"
},
"sessionAffinity": "None",
"type": "ClusterIP"
},
"status": {
"loadBalancer": {}
}
}
old_spec = {
'apiVersion': 'openstack.org/v1',
'kind': 'KuryrLoadBalancer',
'metadata': {
'name': 'test',
'finalizers': [''],
},
'spec': {
'ip': '1.1.1.1'
}
}
new_spec = {
'apiVersion': 'openstack.org/v1',
'kind': 'KuryrLoadBalancer',
'metadata': {
'name': 'test',
'finalizers': [''],
},
'spec': {
'ip': '2.2.2.2'
}
}
project_id = mock.sentinel.project_id
m_drv_project = mock.Mock()
m_drv_project.get_project.return_value = project_id
m_handler = mock.Mock(spec=h_lbaas.ServiceHandler)
m_handler._has_lbaas_spec_changes.return_value = True
m_handler.create_crd_spec.return_value = new_spec
m_handler._should_ignore.return_value = False
m_handler._drv_project = m_drv_project
m_handler.k8s = mock.Mock()
h_lbaas.ServiceHandler.on_present(m_handler, svc_event)
m_handler.create_crd_spec(svc_event)
m_handler._has_lbaas_spec_changes.return_value = True
m_handler._update_crd_spec(old_spec, svc_event)
@mock.patch('kuryr_kubernetes.clients.get_kubernetes_client')
def test_on_present_no_changes(self, get_k8s_client):
svc_event = {
'apiVersion': 'v1',
'kind': 'Service',
"metadata": {
"creationTimestamp": "2020-07-25T18:15:12Z",
"finalizers": [
"openstack.org/service"
],
"labels": {
"run": "test"
},
"name": "test",
"namespace": "test",
"resourceVersion": "413753",
"uid": "a026ae48-6141-4029-b743-bac48dae7f06"
},
"spec": {
"clusterIP": "2.2.2.2",
"ports": [
{
"port": 1,
"protocol": "TCP",
"targetPort": 1
}
],
"selector": {
"run": "test"
},
"sessionAffinity": "None",
"type": "ClusterIP"
},
"status": {
"loadBalancer": {}
}
}
old_spec = {
'apiVersion': 'openstack.org/v1',
'kind': 'KuryrLoadBalancer',
'metadata': {
'name': 'test',
'finalizers': [''],
},
'spec': {
'ip': '1.1.1.1'
}
}
project_id = mock.sentinel.project_id
m_drv_project = mock.Mock()
m_drv_project.get_project.return_value = project_id
m_handler = mock.Mock(spec=h_lbaas.ServiceHandler)
m_handler._has_lbaas_spec_changes.return_value = True
m_handler.create_crd_spec.return_value = old_spec
m_handler._should_ignore.return_value = False
m_handler._drv_project = m_drv_project
m_handler.k8s = mock.Mock()
h_lbaas.ServiceHandler.on_present(m_handler, svc_event)
m_handler.create_crd_spec(svc_event)
m_handler._has_lbaas_spec_changes.return_value = False
def test_get_service_ip(self):
svc_body = {'spec': {'type': 'ClusterIP',
'clusterIP': '192.168.0.11'}}
handler = h_lbaas.ServiceHandler()
ret = handler._get_service_ip(svc_body)
self.assertEqual('192.168.0.11', ret)
svc_body = {'spec': {'type': 'LoadBalancer',
'clusterIP': '192.168.0.11'}}
ret = handler._get_service_ip(svc_body)
self.assertEqual('192.168.0.11', ret)
def test_get_service_ip_funny(self):
svc_body = {'spec': {'type': 'ClusterIP',
'clusterIP': '172.30.0.011'}}
handler = h_lbaas.ServiceHandler()
ret = handler._get_service_ip(svc_body)
self.assertEqual('172.30.0.11', ret)
def test_is_supported_type_clusterip(self):
m_handler = mock.Mock(spec=h_lbaas.ServiceHandler)
svc_body = {'spec': {'type': 'ClusterIP',
'clusterIP': mock.sentinel.cluster_ip}}
ret = h_lbaas.ServiceHandler._is_supported_type(m_handler, svc_body)
self.assertEqual(ret, True)
def test_is_supported_type_loadbalancer(self):
m_handler = mock.Mock(spec=h_lbaas.ServiceHandler)
svc_body = {'spec': {'type': 'LoadBalancer',
'clusterIP': mock.sentinel.cluster_ip}}
ret = h_lbaas.ServiceHandler._is_supported_type(m_handler, svc_body)
self.assertEqual(ret, True)
def _make_test_net_obj(self, cidr_list):
subnets = [osv_subnet.Subnet(cidr=cidr) for cidr in cidr_list]
subnets_list = osv_subnet.SubnetList(objects=subnets)
return osv_network.Network(subnets=subnets_list)
@mock.patch('kuryr_kubernetes.utils.has_port_changes')
def test_has_lbaas_spec_changes(self, m_port_changes):
m_handler = mock.Mock(spec=h_lbaas.ServiceHandler)
service = mock.sentinel.service
lbaas_spec = mock.sentinel.lbaas_spec
for has_ip_changes in (True, False):
for has_port_changes in (True, False):
for timeout in (True, False):
for provider in (True, False):
m_handler._has_ip_changes.return_value = has_ip_changes
m_port_changes.return_value = has_port_changes
m_handler._has_timeout_changes.return_value = timeout
m_handler._has_provider_changes.return_value = provider
ret = h_lbaas.ServiceHandler._has_lbaas_spec_changes(
m_handler, service, lbaas_spec)
self.assertEqual(
has_ip_changes or has_port_changes or timeout
or provider, ret)
def test_has_ip_changes(self):
m_handler = mock.Mock(spec=h_lbaas.ServiceHandler)
m_service = {'apiVersion': 'v1',
'kind': 'Service',
"metadata": {"name": "test",
"namespace": "test"}}
m_handler._get_service_ip.return_value = '1.1.1.1'
m_lbaas_spec = mock.MagicMock()
m_lbaas_spec.ip.__str__.return_value = '2.2.2.2'
ret = h_lbaas.ServiceHandler._has_ip_changes(
m_handler, m_service, m_lbaas_spec)
self.assertTrue(ret)
def test_has_ip_changes__no_changes(self):
service = {
'apiVersion': 'v1',
'kind': 'Service',
"metadata": {
"creationTimestamp": "2020-07-25T18:15:12Z",
"finalizers": [
"openstack.org/service"
],
"labels": {
"run": "test"
},
"name": "test",
"namespace": "test",
"resourceVersion": "413753",
"uid": "a026ae48-6141-4029-b743-bac48dae7f06"
},
"spec": {
"clusterIP": "1.1.1.1"
}
}
m_handler = mock.Mock(spec=h_lbaas.ServiceHandler)
m_handler._get_service_ip.return_value = '1.1.1.1'
lb_crd = {
'apiVersion': 'openstack.org/v1',
'kind': 'KuryrLoadBalancer',
'metadata': {
'name': 'test',
'finalizers': [''],
},
'spec': {
'ip': '1.1.1.1'
}
}
ret = h_lbaas.ServiceHandler._has_ip_changes(
m_handler, service, lb_crd)
self.assertFalse(ret)
def test_has_ip_changes__no_spec(self):
m_handler = mock.Mock(spec=h_lbaas.ServiceHandler)
m_handler._get_service_ip.return_value = '1.1.1.1'
service = {
'apiVersion': 'v1',
'kind': 'Service',
"metadata": {
"creationTimestamp": "2020-07-25T18:15:12Z",
"finalizers": [
"openstack.org/service"
],
"labels": {
"run": "test"
},
"name": "test",
"namespace": "test",
"resourceVersion": "413753",
"uid": "a026ae48-6141-4029-b743-bac48dae7f06"
},
"spec": {
"clusterIP": "1.1.1.1"
}
}
lb_crd = {
"spec": {
"ip": None
}
}
ret = h_lbaas.ServiceHandler._has_ip_changes(
m_handler, service, lb_crd)
self.assertTrue(ret)
def test_has_ip_changes__no_nothing(self):
m_handler = mock.Mock(spec=h_lbaas.ServiceHandler)
service = {
'apiVersion': 'v1',
'kind': 'Service',
"metadata": {
"creationTimestamp": "2020-07-25T18:15:12Z",
"finalizers": [
"openstack.org/service"
],
"labels": {
"run": "test"
},
"name": "test",
"namespace": "test",
"resourceVersion": "413753",
"uid": "a026ae48-6141-4029-b743-bac48dae7f06"
},
"spec": {
"clusterIP": "1.1.1.1"
}
}
lb_crd = {
"spec": {
"ip": None
}
}
m_handler._get_service_ip.return_value = None
ret = h_lbaas.ServiceHandler._has_ip_changes(
m_handler, service, lb_crd)
self.assertFalse(ret)
def test_set_lbaas_spec(self):
self.skipTest("skipping until generalised annotation handling is "
"implemented")
def test_get_lbaas_spec(self):
self.skipTest("skipping until generalised annotation handling is "
"implemented")
class TestEndpointsHandler(test_base.TestCase):
def setUp(self):
super().setUp()
self._ep_name = 'my-service'
self._ep_namespace = mock.sentinel.namespace
self._ep_ip = '1.2.3.4'
self._ep = {
"kind": "Endpoints",
"apiVersion": "v1",
"metadata": {
"name": self._ep_name,
"namespace": self._ep_namespace,
},
"subsets": [
{
"addresses": [
{
"ip": self._ep_ip
},
],
"ports": [
{
"port": 8080,
"protocol": "TCP"
}
]
}
]
}
self._klb_name = 'my-service'
self._klb_ip = '1.1.1.1'
self._klb = {
'apiVersion': 'openstack.org/v1',
'kind': 'KuryrLoadBalancer',
'metadata': {
'name': self._klb_name,
'finalizers': [''],
},
'spec': {
'ip': self._klb_ip
}
}
def test_on_deleted(self):
m_handler = mock.Mock(spec=h_lbaas.EndpointsHandler)
h_lbaas.EndpointsHandler.on_deleted(m_handler, self._ep)
m_handler._remove_endpoints.assert_called_once_with(self._ep)
@mock.patch('kuryr_kubernetes.utils.get_klb_crd_path')
def test__remove_endpoints(self, get_klb_crd_path):
m_handler = mock.Mock()
h_lbaas.EndpointsHandler._remove_endpoints(m_handler, self._ep)
m_handler.k8s.patch_crd.assert_called_once_with(
'spec', get_klb_crd_path(self._ep), 'endpointSlices',
action='remove')
@mock.patch.object(logging.getLogger(
'kuryr_kubernetes.controller.handlers.lbaas'),
'debug')
def test__remove_endpoints_not_found(self, log):
m_handler = mock.Mock()
m_handler.k8s.patch_crd.side_effect = k_exc.K8sResourceNotFound('foo')
h_lbaas.EndpointsHandler._remove_endpoints(m_handler, self._ep)
log.assert_called_once()
def test__remove_endpoints_client_exception(self):
m_handler = mock.Mock()
m_handler.k8s.patch_crd.side_effect = k_exc.K8sClientException()
self.assertRaises(k_exc.K8sClientException,
h_lbaas.EndpointsHandler._remove_endpoints,
m_handler, self._ep)
@mock.patch.object(logging.getLogger(
'kuryr_kubernetes.controller.handlers.lbaas'),
'warning')
def test__remove_endpoints_unprocessable_entity(self, log):
m_handler = mock.Mock()
m_handler.k8s.patch_crd.side_effect = k_exc.K8sUnprocessableEntity(
'bar')
h_lbaas.EndpointsHandler._remove_endpoints(m_handler, self._ep)
log.assert_not_called()