Merge "Added function for figure out link for the resource."

This commit is contained in:
Zuul 2021-01-07 13:35:29 +00:00 committed by Gerrit Code Review
commit c86332c915
5 changed files with 184 additions and 11 deletions

View File

@ -116,7 +116,34 @@ class K8sClient(object):
url = self._base_url + path
response = self.session.get(url, headers=headers)
self._raise_from_response(response)
result = response.json() if json else response.text
if json:
result = response.json()
kind = result['kind']
api_version = result.get('apiVersion')
if not api_version:
api_version = utils.get_api_ver(path)
# Strip List from e.g. PodList. For some reason `.items` of a list
# returned from API doesn't have `kind` set.
# NOTE(gryf): Also, for the sake of calculating selfLink
# equivalent, we need to have both: kind and apiVersion, while the
# latter is not present on items list for core resources, while
# for custom resources there are both kind and apiVersion..
if kind.endswith('List'):
kind = kind[:-4]
for item in result['items']:
if not item.get('kind'):
item['kind'] = kind
if not item.get('apiVersion'):
item['apiVersion'] = api_version
else:
if not result.get('apiVersion'):
result['apiVersion'] = api_version
else:
result = response.text
return result
def _get_url_and_header(self, path, content_type):

View File

@ -111,7 +111,7 @@ class TestK8sClient(test_base.TestCase):
@mock.patch('requests.sessions.Session.get')
def test_get(self, m_get):
path = '/test'
ret = {'test': 'value'}
ret = {'kind': 'Pod', 'apiVersion': 'v1'}
m_resp = mock.MagicMock()
m_resp.ok = True
@ -121,6 +121,30 @@ class TestK8sClient(test_base.TestCase):
self.assertEqual(ret, self.client.get(path))
m_get.assert_called_once_with(self.base_url + path, headers=None)
@mock.patch('requests.sessions.Session.get')
def test_get_list(self, m_get):
path = '/test'
ret = {'kind': 'PodList',
'apiVersion': 'v1',
'items': [{'metadata': {'name': 'pod1'},
'spec': {},
'status': {}}]}
res = {'kind': 'PodList',
'apiVersion': 'v1',
'items': [{'metadata': {'name': 'pod1'},
'spec': {},
'status': {},
'kind': 'Pod',
'apiVersion': 'v1'}]}
m_resp = mock.MagicMock()
m_resp.ok = True
m_resp.json.return_value = ret
m_get.return_value = m_resp
self.assertDictEqual(res, self.client.get(path))
m_get.assert_called_once_with(self.base_url + path, headers=None)
@mock.patch('requests.sessions.Session.get')
def test_get_exception(self, m_get):
path = '/test'

View File

@ -375,3 +375,66 @@ class TestUtils(test_base.TestCase):
self.assertEqual(
target, ('10.0.1.208', 'test', 8080,
'4472fab1-f01c-46a7-b197-5cba4f2d7135'))
def test_get_res_link_core_res(self):
res = {'apiVersion': 'v1',
'kind': 'Pod',
'metadata': {'name': 'pod-1',
'namespace': 'default'}}
self.assertEqual(utils.get_res_link(res),
'/api/v1/namespaces/default/pods/pod-1')
def test_get_res_link_no_existent(self):
res = {'apiVersion': 'customapi/v1',
'kind': 'ItsATrap!',
'metadata': {'name': 'pod-1',
'namespace': 'default'}}
self.assertRaises(KeyError, utils.get_res_link, res)
def test_get_res_link_beta_res(self):
res = {'apiVersion': 'networking.k8s.io/v2beta2',
'kind': 'NetworkPolicy',
'metadata': {'name': 'np-1',
'namespace': 'default'}}
self.assertEqual(utils.get_res_link(res), '/apis/networking.k8s.io/'
'v2beta2/namespaces/default/networkpolicies/np-1')
def test_get_res_link_no_namespace(self):
res = {'apiVersion': 'v1',
'kind': 'Namespace',
'metadata': {'name': 'ns-1'}}
self.assertEqual(utils.get_res_link(res), '/api/v1/namespaces/ns-1')
def test_get_res_link_custom_api(self):
res = {'apiVersion': 'openstack.org/v1',
'kind': 'KuryrPort',
'metadata': {'name': 'kp-1',
'namespace': 'default'}}
self.assertEqual(utils.get_res_link(res),
'/apis/openstack.org/v1/namespaces/default/'
'kuryrports/kp-1')
def test_get_res_link_no_apiversion(self):
res = {'kind': 'KuryrPort',
'metadata': {'name': 'kp-1',
'namespace': 'default'}}
self.assertRaises(KeyError, utils.get_res_link, res)
def test_get_api_ver_core_api(self):
path = '/api/v1/namespaces/default/pods/pod-123'
self.assertEqual(utils.get_api_ver(path), 'v1')
def test_get_api_ver_custom_resource(self):
path = '/apis/openstack.org/v1/namespaces/default/kuryrport/pod-123'
self.assertEqual(utils.get_api_ver(path), 'openstack.org/v1')
def test_get_api_ver_random_path(self):
path = '/?search=foo'
self.assertRaises(ValueError, utils.get_api_ver, path)
def test_get_res_selflink_still_available(self):
res = {'metadata': {'selfLink': '/foo'}}
self.assertEqual(utils.get_res_link(res), '/foo')

View File

@ -12,6 +12,7 @@
import ipaddress
import random
import re
import socket
import time
@ -72,6 +73,70 @@ MEMOIZE_NODE = cache.get_memoization_decorator(
CONF, nodes_cache_region, "nodes_caching")
cache.configure_cache_region(CONF, nodes_cache_region)
RESOURCE_MAP = {'Endpoints': 'endpoints',
'KuryrLoadBalancer': 'kuryrloadbalancers',
'KuryrPort': 'kuryrports',
'KuryrNetworkPolicy': 'kuryrnetworkpolicies',
'KuryrNetwork': 'kuryrnetworks',
'Namespace': 'namespaces',
'NetworkPolicy': 'networkpolicies',
'Node': 'nodes',
'Pod': 'pods',
'Service': 'services'}
API_RE = re.compile(r'v\d+')
def get_res_link(obj):
"""Return selfLink equivalent for provided resource"""
# First try, if we still have it
try:
return obj['metadata']['selfLink']
except KeyError:
pass
# If not, let's proceed with the path assembling.
try:
res_type = RESOURCE_MAP[obj['kind']]
except KeyError:
LOG.error('Unknown resource kind: %s', obj.get('kind'))
raise
namespace = ''
if obj['metadata'].get('namespace'):
namespace = f"/namespaces/{obj['metadata']['namespace']}"
try:
api = f"/apis/{obj['apiVersion']}"
if API_RE.match(obj['apiVersion']):
api = f"/api/{obj['apiVersion']}"
except KeyError:
LOG.error("Object doesn't have an apiVersion available: %s", obj)
raise
return f"{api}{namespace}/{res_type}/{obj['metadata']['name']}"
def get_api_ver(path):
"""Get apiVersion out of resource path.
Path usually is something simillar to:
/api/v1/namespaces/default/pods/pod-5bb648d658-55n76
in case of core resources, and:
/apis/openstack.org/v1/namespaces/default/kuryrloadbalancers/lb-324
in case of custom resoures.
"""
if path.startswith('/api/'):
return path.split('/')[2]
if path.startswith('/apis/'):
return '/'.join(path.split('/')[2:4])
raise ValueError('Provided path is not Kubernetes api path: %s', path)
def utf8_json_decoder(byte_data):
"""Deserializes the bytes into UTF-8 encoded JSON.

View File

@ -16,12 +16,13 @@
import sys
import time
from oslo_config import cfg
from oslo_log import log as logging
from kuryr_kubernetes import clients
from kuryr_kubernetes import exceptions
from kuryr_kubernetes.handlers import health
from kuryr_kubernetes import utils
from oslo_config import cfg
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
@ -143,13 +144,6 @@ class Watcher(health.HealthHandler):
return
for resource in resources:
kind = response['kind']
# Strip List from e.g. PodList. For some reason `.items` of a list
# returned from API doesn't have `kind` set.
if kind.endswith('List'):
kind = kind[:-4]
resource['kind'] = kind
event = {
'type': 'MODIFIED',
'object': resource,