Skip to content

Commit

Permalink
Merge pull request #112 from fiaas/log-typeerror-watch_list
Browse files Browse the repository at this point in the history
Discard watch event if a (valid) Model instance can't be created from its resource payload
  • Loading branch information
oyvindio authored Jan 13, 2023
2 parents b929651 + 95ae77e commit 71a9118
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 6 deletions.
12 changes: 10 additions & 2 deletions k8s/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,13 @@ def watch_list(cls, namespace=None):
if line:
try:
event_json = json.loads(line)
event = WatchEvent(event_json, cls)
yield event
try:
event = WatchEvent(event_json, cls)
yield event
except TypeError:
LOG.exception(
"Unable to create instance of %s from watch event json, discarding event. event_json=%r",
cls.__name__, event_json)
except ValueError:
LOG.exception("Unable to parse JSON on watch event, discarding event. Line: %r", line)

Expand Down Expand Up @@ -307,6 +312,9 @@ def __repr__(self):
return "{cls}(type={type}, object={object})".format(cls=self.__class__.__name__, type=self.type,
object=self.object)

def __eq__(self, other):
return self.type == other.type and self.object == other.object


class LabelSelector(object):
"""Base for label select operations"""
Expand Down
129 changes: 125 additions & 4 deletions tests/k8s/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
# -*- coding: utf-8 -*-

# Copyright 2017-2019 The FIAAS Authors
#
#
# 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.
Expand All @@ -20,12 +20,14 @@
import pytest

from k8s import config
from k8s.base import Model, Field
from k8s.base import Model, WatchEvent
from k8s.fields import Field, RequiredField
from k8s.client import Client, SENSITIVE_HEADERS, _session_factory

import requests


# pylint: disable=R0201
@pytest.mark.usefixtures("k8s_config")
class TestClient(object):
@pytest.fixture
Expand Down Expand Up @@ -187,6 +189,124 @@ def test_redacts_sensitive_headers(self, key):
assert sensitive_value not in text


# pylint: disable=R0201
@pytest.mark.usefixtures("k8s_config")
class TestWatchListEvents(object):

def test_watch_list_payload_ok(self, get):
"""
verify watch events of WatchListExample create WatchEvent with the appropriate type and object
"""
response = mock.create_autospec(requests.Response)
response.status_code = 200
response.iter_lines.return_value = ['''
{
"type": "ADDED",
"object": {
"value": 1,
"requiredValue": 2
}
}''', '''
{
"type": "MODIFIED",
"object": {
"value": 3,
"requiredValue": 4
}
}
''']
get.return_value = response

expected = [
_create_watchevent(WatchEvent.ADDED, WatchListExample(value=1, requiredValue=2)),
_create_watchevent(WatchEvent.MODIFIED, WatchListExample(value=3, requiredValue=4)),
]

items = list(WatchListExample.watch_list())
assert items == expected

def test_watch_list_payload_invalid_json(self, get):
"""
verify event which does not cleanly unmarshal from json to dict is discarded
"""
response = mock.create_autospec(requests.Response)
response.status_code = 200
response.iter_lines.return_value = ['''
{
"type": "ADDED",
"object": {
"value": 1,
"requiredValue": 2
}
}
''', '''
definitely not valid json
''', '''
{
"type": "ADDED",
"object": {
"value": 5,
"requiredValue": 6
}
}''']
get.return_value = response

expected = [
_create_watchevent(WatchEvent.ADDED, WatchListExample(value=1, requiredValue=2)),
# "definitely not valid json" should be discarded
_create_watchevent(WatchEvent.ADDED, WatchListExample(value=5, requiredValue=6)),
]

items = list(WatchListExample.watch_list())
assert items == expected

def test_watch_list_payload_invalid_object(self, get):
"""
verify event which contains a resource not valid according to the Model class is discarded
"""
response = mock.create_autospec(requests.Response)
response.status_code = 200
response.iter_lines.return_value = ['''
{
"type": "ADDED",
"object": {
"value": 1,
"requiredValue": 2
}
}
''', '''
{
"type": "ADDED",
"object": {
"value": 10,
}
}
''', '''
{
"type": "ADDED",
"object": {
"value": 5,
"requiredValue": 6
}
}''']
get.return_value = response

expected = [
_create_watchevent(WatchEvent.ADDED, WatchListExample(value=1, requiredValue=2)),
# event with value=10 and requiredValue missing should be discarded
_create_watchevent(WatchEvent.ADDED, WatchListExample(value=5, requiredValue=6)),
]

items = list(WatchListExample.watch_list())
assert items == expected


def _create_watchevent(event_type, event_object):
"""factory function for WatchEvent to make it easier to create test data from actual objects, as the constructor
takes a dict (unmarshaled json)"""
return WatchEvent({"type": event_type, "object": event_object.as_dict()}, event_object.__class__)


def _absolute_url(url):
return config.api_server + url

Expand All @@ -199,6 +319,7 @@ class Meta:
watch_list_url_template = "/watch/{namespace}/example"

value = Field(int)
requiredValue = RequiredField(int)


class WatchListExampleUnsupported(Model):
Expand Down

0 comments on commit 71a9118

Please sign in to comment.