From a8ba77e63a5feb82c878eca6181496acafec9691 Mon Sep 17 00:00:00 2001 From: Amy Sutedja Date: Mon, 22 Jun 2020 13:39:06 -0700 Subject: [PATCH] Telemetry internals --- splunklib/wire/__init__.py | 17 ++++++ splunklib/wire/_internal/__init__.py | 18 ++++++ splunklib/wire/_internal/json_sink.py | 49 ++++++++++++++++ splunklib/wire/_internal/telemetry.py | 23 ++++++++ splunklib/wire/_internal/telemetry_metric.py | 62 ++++++++++++++++++++ tests/test_telemetry.py | 60 +++++++++++++++++++ 6 files changed, 229 insertions(+) create mode 100644 splunklib/wire/__init__.py create mode 100644 splunklib/wire/_internal/__init__.py create mode 100644 splunklib/wire/_internal/json_sink.py create mode 100644 splunklib/wire/_internal/telemetry.py create mode 100644 splunklib/wire/_internal/telemetry_metric.py create mode 100644 tests/test_telemetry.py diff --git a/splunklib/wire/__init__.py b/splunklib/wire/__init__.py new file mode 100644 index 000000000..f91c25868 --- /dev/null +++ b/splunklib/wire/__init__.py @@ -0,0 +1,17 @@ +# coding=utf-8 +# +# Copyright © 2011-2020 Splunk, Inc. +# +# 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 ._internal import * diff --git a/splunklib/wire/_internal/__init__.py b/splunklib/wire/_internal/__init__.py new file mode 100644 index 000000000..83028bbf3 --- /dev/null +++ b/splunklib/wire/_internal/__init__.py @@ -0,0 +1,18 @@ +# coding=utf-8 +# +# Copyright © 2011-2020 Splunk, Inc. +# +# 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 .telemetry import * +from .telemetry_metric import * diff --git a/splunklib/wire/_internal/json_sink.py b/splunklib/wire/_internal/json_sink.py new file mode 100644 index 000000000..290cc779d --- /dev/null +++ b/splunklib/wire/_internal/json_sink.py @@ -0,0 +1,49 @@ +# coding=utf-8 +# +# Copyright © 2011-2020 Splunk, Inc. +# +# 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. + +import json + +from splunklib.client import Entity + +class JsonSink(Entity): + """This class represents a JSON-based write-only sink of entities in the Splunk + instance, notably telemetry-metric. + """ + JSON_HEADER = [('Content-Type', 'application/json')] + + def __init__(self, service, path, **kwargs): + super(JsonSink, self).__init__(service, path, skip_refresh=True, **kwargs) + + def _post(self, url, **kwargs): + owner, app, sharing = self._proper_namespace() + + return self.service.post(self.path + url, owner=owner, app=app, sharing=sharing, **kwargs) + + def submit(self, data): + """ + Submits an item to the sink. + + :param data: data to submit + :type data: ``dict`` + + :return: return data + :rtype: ``dict`` + """ + + response = self._post('', headers=self.__class__.JSON_HEADER, body=json.dumps(data)) + body = json.loads(response.body.read().decode('utf-8')) + + return response, body diff --git a/splunklib/wire/_internal/telemetry.py b/splunklib/wire/_internal/telemetry.py new file mode 100644 index 000000000..83a4b4fae --- /dev/null +++ b/splunklib/wire/_internal/telemetry.py @@ -0,0 +1,23 @@ +# coding=utf-8 +# +# Copyright © 2011-2020 Splunk, Inc. +# +# 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 splunklib.wire._internal.json_sink import JsonSink + +PATH_TELEMETRY = "telemetry-metric" + +class Telemetry(JsonSink): + def __init__(self, service, **kwargs): + super(Telemetry, self).__init__(service, PATH_TELEMETRY, **kwargs) diff --git a/splunklib/wire/_internal/telemetry_metric.py b/splunklib/wire/_internal/telemetry_metric.py new file mode 100644 index 000000000..478ce87ec --- /dev/null +++ b/splunklib/wire/_internal/telemetry_metric.py @@ -0,0 +1,62 @@ +# coding=utf-8 +# +# Copyright © 2011-2020 Splunk, Inc. +# +# 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. + +class TelemetryMetric: + def __init__(self, metric_type, component, data, opt_in_required=2): + self.metric_type = metric_type + self.component = component + self.data = data + self.opt_in_required = opt_in_required + + @property + def metric_type(self): + return self._metric_type + + @metric_type.setter + def metric_type(self, value): + self._metric_type = value + + @property + def component(self): + return self._component + + @component.setter + def component(self, value): + self._component = value + + @property + def data(self): + return self._data + + @data.setter + def data(self, value): + self._data = value + + @property + def opt_in_required(self): + return self._opt_in_required + + @opt_in_required.setter + def opt_in_required(self, value): + self._opt_in_required = value + + def to_wire(self): + return { + 'type': self.metric_type, + 'component': self.component, + 'data': self.data, + 'optInRequired': self.opt_in_required, + } diff --git a/tests/test_telemetry.py b/tests/test_telemetry.py new file mode 100644 index 000000000..7a402dd1d --- /dev/null +++ b/tests/test_telemetry.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# +# Copyright 2011-2014 Splunk, Inc. +# +# 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 __future__ import absolute_import +import pytest + +from tests import testlib +from splunklib.wire._internal.telemetry import Telemetry +from splunklib.wire._internal.telemetry_metric import TelemetryMetric + +try: + import unittest +except ImportError: + import unittest2 as unittest + +@pytest.mark.app +class TelemetryTestCase(testlib.SDKTestCase): + def setUp(self): + super(TelemetryTestCase, self).setUp() + + self.service.namespace['owner'] = 'nobody' + self.service.namespace['app'] = 'sdk-app-collection' + + self.telemetry = Telemetry(self.service) + + def test_submit(self): + # create a telemetry metric + metric = TelemetryMetric(**{ + 'metric_type': 'event', + 'component': 'telemetry_test_case', + 'data': { + 'testValue': 32 + } + }) + + # call out to telemetry + response, _body = self.telemetry.submit(metric.to_wire()) + + # it should return a 201 + self.assertEqual(response.status, 201) + +if __name__ == "__main__": + try: + import unittest2 as unittest + except ImportError: + import unittest + unittest.main()