Added method for creating k8s events object.

Kubernetes provides a convenient way for examining resource life cycle.
Ephemeral Event objects are created for some of the steps during
resource object change of the state. Those event can be accessed using
`kubectl get events` or for particular object `kubectl describe <type>
<resource name>`, so that operator can examine the state and the reason
for potential failure of given resource object.

In this patch there is a method added for creating Event objects, so
that anything wrong regarding Kubernetes object life cycle can be
"annotated" with such object.

Change-Id: I291613adf9282ec5399fbe5de75e82472c2bc9c0
This commit is contained in:
Roman Dobosz 2021-09-14 15:08:58 +02:00
parent 1a120c23f6
commit fe7d4de91f
3 changed files with 100 additions and 4 deletions

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import contextlib
import datetime
import functools
import itertools
import os
@ -23,6 +24,7 @@ import urllib3
from oslo_log import log as logging
from oslo_serialization import jsonutils
import pytz
import requests
from requests import adapters
@ -445,3 +447,55 @@ class K8sClient(object):
params.get('resourceVersion'))
time.sleep(t)
attempt += 1
def add_event(self, resource, reason, message, type_='Normal'):
"""Create an Event object for the provided resource."""
involved_object = {'apiVersion': resource['apiVersion'],
'kind': resource['kind'],
'name': resource['metadata']['name'],
'namespace': resource['metadata']['namespace'],
'uid': resource['metadata']['uid']}
# This is needed for Event date, otherwise LAST SEEN/Age will be empty
# and misleading.
now = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC)
date_time = now.strftime("%Y-%m-%dT%H:%M:%SZ")
name = ".".join((resource['metadata']['name'],
self._get_hex_timestamp(now)))
event = {'kind': 'Event',
'apiVersion': 'v1',
'firstTimestamp': date_time,
'metadata': {'name': name},
'reason': reason,
'message': message,
'type': type_,
'involvedObject': involved_object}
try:
return self.post(f'{constants.K8S_API_BASE}/namespaces/'
f'{resource["metadata"]["namespace"]}/events',
event)
except exc.K8sClientException:
LOG.warning(f'There was non critical error during creating an '
'Event for resource: "{resource}", with reason: '
f'"{reason}", message: "{message}" and type: '
f'"{type_}"')
return {}
def _get_hex_timestamp(self, datetimeobj):
"""Get hex representation for timestamp.
In Kuberenets, Event name is constructed name of the bounded object
and timestamp in hexadecimal representation.
Note, that Python timestamp is represented as floating figure:
1631622163.8534190654754638671875
while those which origin from K8s, after change to int:
1631622163915909162
so, to get similar integer, we need to multiply the float by
100000000 to get the same precision and cast to integer, to get rid
of the fractures, and finally convert it to hex representation.
"""
timestamp = datetime.datetime.timestamp(datetimeobj)
return format(int(timestamp * 100000000), 'x')

View File

@ -163,7 +163,8 @@ def get_sgr_obj(sgr_id='7621d1e0-a2d2-4496-94eb-ffd375d20877',
return os_sgr.SecurityGroupRule(**sgr_data)
def get_k8s_pod():
def get_k8s_pod(name='pod-5bb648d658-55n76', namespace='namespace',
uid='683da866-6bb1-4da2-bf6a-a5f4137c38e7'):
return {'apiVersion': 'v1',
'kind': 'Pod',
@ -173,9 +174,9 @@ def get_k8s_pod():
'labels': {'app': 'pod',
'pod-template-hash': '5bb648d658'},
'operation': 'Update',
'name': 'pod-5bb648d658-55n76',
'namespace': 'default',
'name': name,
'namespace': namespace,
'resourceVersion': '19416',
'uid': '683da866-6bb1-4da2-bf6a-a5f4137c38e7'},
'uid': uid},
'spec': {},
'status': {}}

View File

@ -23,6 +23,7 @@ import requests
from kuryr_kubernetes import exceptions as exc
from kuryr_kubernetes import k8s_client
from kuryr_kubernetes.tests import base as test_base
from kuryr_kubernetes.tests import fake
class TestK8sClient(test_base.TestCase):
@ -482,3 +483,43 @@ class TestK8sClient(test_base.TestCase):
m_resp.status_code = 500
self.assertRaises(exc.K8sClientException,
self.client._raise_from_response, m_resp)
def test_add_event(self):
self.client.post = mock.MagicMock()
get_hex_ts = self.client._get_hex_timestamp = mock.MagicMock()
get_hex_ts.return_value = 'deadc0de'
namespace = 'n1'
uid = 'deadbeef'
name = 'pod-123'
pod = fake.get_k8s_pod(name=name, namespace=namespace, uid=uid)
event_name = f'{name}.deadc0de'
self.client.add_event(pod, 'reason', 'message')
# Event path
url = self.client.post.call_args[0][0]
data = self.client.post.call_args[0][1]
self.assertEqual(url, f'/api/v1/namespaces/{namespace}/events')
# Event fields
self.assertEqual(data['metadata']['name'], event_name)
self.assertEqual(data['reason'], 'reason')
self.assertEqual(data['message'], 'message')
self.assertEqual(data['type'], 'Normal')
# involvedObject
self.assertDictEqual(data['involvedObject'],
{'apiVersion': pod['apiVersion'],
'kind': pod['kind'],
'name': name,
'namespace': namespace,
'uid': uid})
def test_add_event_k8s_exception(self):
self.client.post = mock.MagicMock()
self.client.post.side_effect = exc.K8sClientException
pod = fake.get_k8s_pod()
self.assertDictEqual(self.client.add_event(pod, 'reason1', 'message2'),
{})