def test_unmarshal_with_float_object(self):
     w = Watch()
     event = w.unmarshal_event('{"type": "ADDED", "object": 1}', 'float')
     self.assertEqual("ADDED", event['type'])
     self.assertEqual(1.0, event['object'])
     self.assertTrue(isinstance(event['object'], float))
     self.assertEqual(1, event['raw_object'])
 def test_unmarshal_with_no_return_type(self):
     w = Watch()
     event = w.unmarshal_event('{"type": "ADDED", "object": ["test1"]}',
                               None)
     self.assertEqual("ADDED", event['type'])
     self.assertEqual(["test1"], event['object'])
     self.assertEqual(["test1"], event['raw_object'])
예제 #3
0
 def test_unmarshal_with_empty_return_type(self):
     # empty string as a return_type is a default value
     # if watch can't detect object by function's name
     w = Watch()
     event = w.unmarshal_event('{"type": "ADDED", "object": ["test1"]}', '')
     self.assertEqual("ADDED", event['type'])
     self.assertEqual(["test1"], event['object'])
     self.assertEqual(["test1"], event['raw_object'])
 def test_unmarshal_with_custom_object(self):
     w = Watch()
     event = w.unmarshal_event(
         '{"type": "ADDED", "object": {"apiVersion":'
         '"test.com/v1beta1","kind":"foo","metadata":'
         '{"name": "bar", "resourceVersion": "1"}}}', 'object')
     self.assertEqual("ADDED", event['type'])
     # make sure decoder deserialized json into dictionary and updated
     # Watch.resource_version
     self.assertTrue(isinstance(event['object'], dict))
     self.assertEqual("1", event['object']['metadata']['resourceVersion'])
     self.assertEqual("1", w.resource_version)
예제 #5
0
    async def run(self):
        options = {}
        if self.arg:
            options = json.loads(self.arg)

        release = options.get('release_name')
        pod = options.get('pod_name')
        container = options.get('container_name')
        tail_lines = options.get('tail_lines', 500)
        limit_bytes = options.get('limit_bytes')

        await self.middleware.call('chart.release.validate_pod_log_args',
                                   release, pod, container)
        if tail_lines is not None and tail_lines < 1:
            raise CallError('Tail lines must be null or greater then 0.')
        elif limit_bytes is not None and limit_bytes < 1:
            raise CallError('Limit bytes must be null or greater then 0.')

        release_data = await self.middleware.call('chart.release.get_instance',
                                                  release)

        async with api_client() as (api, context):
            self.watch = Watch()
            try:
                async with self.watch.stream(
                        context['core_api'].read_namespaced_pod_log,
                        name=pod,
                        container=container,
                        namespace=release_data['namespace'],
                        tail_lines=tail_lines,
                        limit_bytes=limit_bytes,
                        timestamps=True,
                ) as stream:
                    async for event in stream:
                        # Event should contain a timestamp in RFC3339 format, we should parse it and supply it
                        # separately so UI can highlight the timestamp giving us a cleaner view of the logs
                        timestamp = event.split(maxsplit=1)[0].strip()
                        try:
                            timestamp = str(parse(timestamp))
                        except (TypeError, ParserError):
                            timestamp = None
                        else:
                            event = event.split(maxsplit=1)[-1].lstrip()

                        self.send_event('ADDED',
                                        fields={
                                            'data': event,
                                            'timestamp': timestamp
                                        })
            except ClientConnectionError:
                pass
    async def test_unmarshall_k8s_error_response(self):
        """Never parse messages of type ERROR.

        This test uses an actually recorded error, in this case for an outdated
        resource version.

        """
        # An actual error response sent by K8s during testing.
        k8s_err = {
            'type': 'ERROR',
            'object': {
                'kind': 'Status',
                'apiVersion': 'v1',
                'metadata': {},
                'status': 'Failure',
                'message': 'too old resource version: 1 (8146471)',
                'reason': 'Gone',
                'code': 410
            }
        }

        ret = Watch().unmarshal_event(json.dumps(k8s_err), None)
        self.assertEqual(ret['type'], k8s_err['type'])
        self.assertEqual(ret['object'], k8s_err['object'])
        self.assertEqual(ret['object'], k8s_err['object'])
예제 #7
0
    async def test_unmarshall_k8s_error_response(self):
        """Never parse messages of type ERROR.

        This test uses an actually recorded error, in this case for an outdated
        resource version.

        """
        # An actual error response sent by K8s during testing.
        k8s_err = {
            'type': 'ERROR',
            'object': {
                'kind': 'Status',
                'apiVersion': 'v1',
                'metadata': {},
                'status': 'Failure',
                'message': 'too old resource version: 1 (8146471)',
                'reason': 'Gone',
                'code': 410
            }
        }

        with self.assertRaisesRegex(
                kubernetes_asyncio.client.exceptions.ApiException,
                r'\(410\)\nReason: Gone: too old resource version: 1 \(8146471\)'
        ):
            Watch().unmarshal_event(json.dumps(k8s_err), None)
예제 #8
0
    async def run(self) -> None:
        """Watch for changes to the configured custom object.

        This method is intended to be run as a background async task.  It will
        run forever, adding any custom object changes to the associated queue.
        """
        self._logger.debug("Starting Kubernetes watcher")
        consecutive_failures = 0
        watch_call = (
            self._api.list_cluster_custom_object,
            "gafaelfawr.lsst.io",
            "v1alpha1",
            self._plural,
        )
        while True:
            try:
                async with Watch().stream(*watch_call) as stream:
                    async for raw_event in stream:
                        event = self._parse_raw_event(raw_event)
                        if event:
                            await self._queue.put(event)
                        consecutive_failures = 0
            except ApiException as e:
                msg = "ApiException from watch"
                consecutive_failures += 1
                if consecutive_failures > 10:
                    raise
                else:
                    self._logger.exception(msg, error=str(e))
                    msg = "Pausing 10s before attempting to continue"
                    self._logger.info()
                    await asyncio.sleep(10)
예제 #9
0
파일: pods.py 프로젝트: wesleywwf/freenas
    async def run(self):
        release = self.arg['release_name']
        pod = self.arg['pod_name']
        container = self.arg['container_name']
        tail_lines = self.arg['tail_lines']
        limit_bytes = self.arg['limit_bytes']

        await self.middleware.call('chart.release.validate_pod_log_args',
                                   release, pod, container)
        release_data = await self.middleware.call('chart.release.get_instance',
                                                  release)

        async with api_client() as (api, context):
            self.watch = Watch()
            try:
                async with self.watch.stream(
                        context['core_api'].read_namespaced_pod_log,
                        name=pod,
                        container=container,
                        namespace=release_data['namespace'],
                        tail_lines=tail_lines,
                        limit_bytes=limit_bytes,
                        timestamps=True,
                ) as stream:
                    async for event in stream:
                        # Event should contain a timestamp in RFC3339 format, we should parse it and supply it
                        # separately so UI can highlight the timestamp giving us a cleaner view of the logs
                        timestamp = event.split(maxsplit=1)[0].strip()
                        try:
                            timestamp = str(parse(timestamp))
                        except (TypeError, ParserError):
                            timestamp = None
                        else:
                            event = event.split(maxsplit=1)[-1].lstrip()

                        self.send_event('ADDED',
                                        fields={
                                            'data': event,
                                            'timestamp': timestamp
                                        })
            except ClientConnectionError:
                pass
예제 #10
0
    async def test_unmarshall_k8s_error_response_401_gke(self):
        """Never parse messages of type ERROR.

        This test uses an actually recorded error returned by GKE.

        """
        # An actual error response sent by K8s during testing.
        k8s_err = {
            'kind': 'Status',
            'apiVersion': 'v1',
            'metadata': {},
            'status': 'Failure',
            'message': 'Unauthorized',
            'reason': 'Unauthorized',
            'code': 401
        }

        with self.assertRaisesRegex(
                kubernetes_asyncio.client.exceptions.ApiException,
                r'\(401\)\nReason: Unauthorized: Unauthorized'):
            Watch().unmarshal_event(json.dumps(k8s_err), None)
예제 #11
0
파일: pods.py 프로젝트: wesleywwf/freenas
class KubernetesPodLogsFollowTailEventSource(EventSource):
    """
    Retrieve logs of a container in a pod in a chart release.

    Name of chart release, name of pod and name of container is required.
    Optionally `tail_lines` and `limit_bytes` can be specified.

    `tail_lines` is an option to select how many lines of logs to retrieve for the said container. It
    defaults to 500. If set to `null`, it will retrieve complete logs of the container.

    `limit_bytes` is an option to select how many bytes to retrieve from the tail lines selected. If set
    to null ( which is the default ), it will not limit the bytes returned. To clarify, `tail_lines`
    is applied first and the required number of lines are retrieved and then `limit_bytes` is applied.
    """
    ACCEPTS = Dict(
        Int('tail_lines', default=500, validators=[Range(min=1)]),
        Int('limit_bytes', default=None, null=True, validators=[Range(min=1)]),
        Str('release_name', required=True),
        Str('pod_name', required=True),
        Str('container_name', required=True),
    )
    RETURNS = Dict(Str('data', required=True),
                   Str('timestamp', required=True, null=True))

    def __init__(self, *args, **kwargs):
        super(KubernetesPodLogsFollowTailEventSource,
              self).__init__(*args, **kwargs)
        self.watch = None

    async def run(self):
        release = self.arg['release_name']
        pod = self.arg['pod_name']
        container = self.arg['container_name']
        tail_lines = self.arg['tail_lines']
        limit_bytes = self.arg['limit_bytes']

        await self.middleware.call('chart.release.validate_pod_log_args',
                                   release, pod, container)
        release_data = await self.middleware.call('chart.release.get_instance',
                                                  release)

        async with api_client() as (api, context):
            self.watch = Watch()
            try:
                async with self.watch.stream(
                        context['core_api'].read_namespaced_pod_log,
                        name=pod,
                        container=container,
                        namespace=release_data['namespace'],
                        tail_lines=tail_lines,
                        limit_bytes=limit_bytes,
                        timestamps=True,
                ) as stream:
                    async for event in stream:
                        # Event should contain a timestamp in RFC3339 format, we should parse it and supply it
                        # separately so UI can highlight the timestamp giving us a cleaner view of the logs
                        timestamp = event.split(maxsplit=1)[0].strip()
                        try:
                            timestamp = str(parse(timestamp))
                        except (TypeError, ParserError):
                            timestamp = None
                        else:
                            event = event.split(maxsplit=1)[-1].lstrip()

                        self.send_event('ADDED',
                                        fields={
                                            'data': event,
                                            'timestamp': timestamp
                                        })
            except ClientConnectionError:
                pass

    async def cancel(self):
        await super().cancel()
        if self.watch:
            await self.watch.close()

    async def on_finish(self):
        self.watch = None