Merge "Add Ceilometer driver"

This commit is contained in:
Jenkins
2016-07-26 21:52:38 +00:00
committed by Gerrit Code Review
10 changed files with 547 additions and 599 deletions

View File

@@ -17,8 +17,8 @@ import json
import os import os
from osprofiler.cmd import cliutils from osprofiler.cmd import cliutils
from osprofiler.drivers import base
from osprofiler import exc from osprofiler import exc
from osprofiler.parsers import ceilometer as ceiloparser
class BaseCommand(object): class BaseCommand(object):
@@ -29,6 +29,9 @@ class TraceCommands(BaseCommand):
group_name = "trace" group_name = "trace"
@cliutils.arg("trace", help="File with trace or trace id") @cliutils.arg("trace", help="File with trace or trace id")
@cliutils.arg("--connection-string", dest="conn_str",
default="ceilometer://",
help="storage driver's connection string")
@cliutils.arg("--json", dest="use_json", action="store_true", @cliutils.arg("--json", dest="use_json", action="store_true",
help="show trace in JSON") help="show trace in JSON")
@cliutils.arg("--html", dest="use_html", action="store_true", @cliutils.arg("--html", dest="use_html", action="store_true",
@@ -43,28 +46,11 @@ class TraceCommands(BaseCommand):
trace = json.load(open(args.trace)) trace = json.load(open(args.trace))
else: else:
try: try:
import ceilometerclient.client engine = base.get_driver(args.conn_str, **args.__dict__)
import ceilometerclient.exc
import ceilometerclient.shell
except ImportError:
raise ImportError(
"To use this command, you should install "
"'ceilometerclient' manually. Use command:\n "
"'pip install ceilometerclient'.")
try:
client = ceilometerclient.client.get_client(
args.ceilometer_api_version, **args.__dict__)
notifications = ceiloparser.get_notifications(
client, args.trace)
except Exception as e: except Exception as e:
if hasattr(e, "http_status") and e.http_status == 401: raise exc.CommandError(e.message)
msg = "Invalid OpenStack Identity credentials."
else:
msg = "Something has gone wrong. See logs for more details"
raise exc.CommandError(msg)
if notifications: trace = engine.get_report(args.trace)
trace = ceiloparser.parse_notifications(notifications)
if not trace: if not trace:
msg = ("Trace with UUID %s not found. " msg = ("Trace with UUID %s not found. "
@@ -76,13 +62,24 @@ class TraceCommands(BaseCommand):
% args.trace) % args.trace)
raise exc.CommandError(msg) raise exc.CommandError(msg)
# NOTE(ayelistratov): Ceilometer translates datetime objects to
# strings, other drivers store this data in ISO Date format.
# Since datetime.datetime is not JSON serializable by default,
# this method will handle that.
def datetime_json_serialize(obj):
if hasattr(obj, "isoformat"):
return obj.isoformat()
else:
return obj
if args.use_json: if args.use_json:
output = json.dumps(trace) output = json.dumps(trace, default=datetime_json_serialize)
elif args.use_html: elif args.use_html:
with open(os.path.join(os.path.dirname(__file__), with open(os.path.join(os.path.dirname(__file__),
"template.html")) as html_template: "template.html")) as html_template:
output = html_template.read().replace( output = html_template.read().replace(
"$DATA", json.dumps(trace, indent=2)) "$DATA", json.dumps(trace, indent=2,
default=datetime_json_serialize))
else: else:
raise exc.CommandError("You should choose one of the following " raise exc.CommandError("You should choose one of the following "
"output-formats: --json or --html.") "output-formats: --json or --html.")

View File

@@ -1,3 +1,4 @@
from osprofiler.drivers import base # noqa from osprofiler.drivers import base # noqa
from osprofiler.drivers import ceilometer # noqa
from osprofiler.drivers import messaging # noqa from osprofiler.drivers import messaging # noqa
from osprofiler.drivers import mongodb # noqa from osprofiler.drivers import mongodb # noqa

View File

@@ -0,0 +1,81 @@
# Copyright 2016 Mirantis Inc.
# All Rights Reserved.
#
# 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 osprofiler.drivers import base
from osprofiler import exc
class Ceilometer(base.Driver):
def __init__(self, connection_str, **kwargs):
"""Driver receiving profiled information from ceilometer."""
super(Ceilometer, self).__init__(connection_str)
try:
import ceilometerclient.client
import ceilometerclient.shell
except ImportError:
raise exc.CommandError(
"To use this command, you should install "
"'ceilometerclient' manually. Use command:\n "
"'pip install python-ceilometerclient'.")
try:
self.client = ceilometerclient.client.get_client(
kwargs["ceilometer_api_version"], **kwargs)
except Exception as e:
if hasattr(e, "http_status") and e.http_status == 401:
msg = "Invalid OpenStack Identity credentials."
else:
msg = ("Something has gone wrong. See ceilometer logs "
"for more details")
raise exc.CommandError(msg)
@classmethod
def get_name(cls):
return "ceilometer"
def get_report(self, base_id):
"""Retrieves and parses notification from ceilometer.
:param base_id: Base id of trace elements.
"""
_filter = [{"field": "base_id", "op": "eq", "value": base_id}]
# limit is hardcoded in this code state. Later that will be changed via
# connection string usage
notifications = [n.to_dict()
for n in self.client.events.list(_filter,
limit=100000)]
for n in notifications:
traits = n["traits"]
def find_field(f_name):
return [t["value"] for t in traits if t["name"] == f_name][0]
trace_id = find_field("trace_id")
parent_id = find_field("parent_id")
name = find_field("name")
project = find_field("project")
service = find_field("service")
host = find_field("host")
timestamp = find_field("timestamp")
payload = n.get("raw", {}).get("payload", {})
self._append_results(trace_id, parent_id, name, project, service,
host, timestamp, payload)
return self._parse_results()

View File

@@ -1,138 +0,0 @@
# Copyright 2014 Mirantis Inc.
# All Rights Reserved.
#
# 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 datetime
def _build_tree(nodes):
"""Builds the tree (forest) data structure based on the list of nodes.
Works in O(n).
:param nodes: list of nodes, where each node is a dictionary with fields
"parent_id", "trace_id", "info"
:returns: list of top level ("root") nodes in form of dictionaries,
each containing the "info" and "children" fields, where
"children" is the list of child nodes ("children" will be
empty for leafs)
"""
tree = []
for trace_id in nodes:
node = nodes[trace_id]
node.setdefault("children", [])
parent_id = node["parent_id"]
if parent_id in nodes:
nodes[parent_id].setdefault("children", [])
nodes[parent_id]["children"].append(node)
else:
tree.append(node) # no parent => top-level node
for node in nodes:
nodes[node]["children"].sort(key=lambda x: x["info"]["started"])
return sorted(tree, key=lambda x: x["info"]["started"])
def parse_notifications(notifications):
"""Parse & builds tree structure from list of ceilometer notifications."""
result = {}
started_at = 0
finished_at = 0
for n in notifications:
traits = n["traits"]
def find_field(f_name):
return [t["value"] for t in traits if t["name"] == f_name][0]
trace_id = find_field("trace_id")
parent_id = find_field("parent_id")
name = find_field("name")
project = find_field("project")
service = find_field("service")
host = find_field("host")
timestamp = find_field("timestamp")
timestamp = datetime.datetime.strptime(timestamp,
"%Y-%m-%dT%H:%M:%S.%f")
if trace_id not in result:
result[trace_id] = {
"info": {
"name": name.split("-")[0],
"project": project,
"service": service,
"host": host,
},
"trace_id": trace_id,
"parent_id": parent_id,
}
result[trace_id]["info"]["meta.raw_payload.%s" % name] = n.get(
"raw", {}).get("payload", {})
if name.endswith("stop"):
result[trace_id]["info"]["finished"] = timestamp
else:
result[trace_id]["info"]["started"] = timestamp
if not started_at or started_at > timestamp:
started_at = timestamp
if not finished_at or finished_at < timestamp:
finished_at = timestamp
def msec(dt):
# NOTE(boris-42): Unfortunately this is the simplest way that works in
# py26 and py27
microsec = (dt.microseconds + (dt.seconds + dt.days * 24 * 3600) * 1e6)
return int(microsec / 1000.0)
for r in result.values():
# NOTE(boris-42): We are not able to guarantee that ceilometer consumed
# all messages => so we should at make duration 0ms.
if "started" not in r["info"]:
r["info"]["started"] = r["info"]["finished"]
if "finished" not in r["info"]:
r["info"]["finished"] = r["info"]["started"]
r["info"]["started"] = msec(r["info"]["started"] - started_at)
r["info"]["finished"] = msec(r["info"]["finished"] - started_at)
return {
"info": {
"name": "total",
"started": 0,
"finished": msec(finished_at - started_at) if started_at else 0
},
"children": _build_tree(result)
}
def get_notifications(ceilometer, base_id):
"""Retrieves and parses notification from ceilometer.
:param ceilometer: Initialized ceilometer client.
:param base_id: Base id of trace elements.
"""
_filter = [{"field": "base_id", "op": "eq", "value": base_id}]
# limit is hardcoded in this code state. Later that will be changed via
# connection string usage
return [n.to_dict()
for n in ceilometer.events.list(_filter, limit=100000)]

View File

@@ -52,7 +52,7 @@ class ShellTestCase(test.TestCase):
self.ceiloclient = mock.MagicMock() self.ceiloclient = mock.MagicMock()
sys.modules["ceilometerclient"] = self.ceiloclient sys.modules["ceilometerclient"] = self.ceiloclient
self.addCleanup(sys.modules.pop, "ceilometerclient", None) self.addCleanup(sys.modules.pop, "ceilometerclient", None)
ceilo_modules = ["client", "exc", "shell"] ceilo_modules = ["client", "shell"]
for module in ceilo_modules: for module in ceilo_modules:
sys.modules["ceilometerclient.%s" % module] = getattr( sys.modules["ceilometerclient.%s" % module] = getattr(
self.ceiloclient, module) self.ceiloclient, module)
@@ -80,7 +80,7 @@ class ShellTestCase(test.TestCase):
self.assertEqual(str(actual_error), expected_message) self.assertEqual(str(actual_error), expected_message)
else: else:
raise ValueError( raise ValueError(
"Expected: `osprofiler.cmd.exc.CommandError` is raised with " "Expected: `osprofiler.exc.CommandError` is raised with "
"message: '%s'." % expected_message) "message: '%s'." % expected_message)
def test_username_is_not_presented(self): def test_username_is_not_presented(self):
@@ -116,14 +116,15 @@ class ShellTestCase(test.TestCase):
"env[OS_USER_DOMAIN_ID]") "env[OS_USER_DOMAIN_ID]")
self._test_with_command_error("trace show fake-uuid", msg) self._test_with_command_error("trace show fake-uuid", msg)
def test_trace_show_ceilometrclient_is_missed(self): def test_trace_show_ceilometerclient_is_missed(self):
sys.modules["ceilometerclient"] = None sys.modules["ceilometerclient"] = None
sys.modules["ceilometerclient.client"] = None sys.modules["ceilometerclient.client"] = None
sys.modules["ceilometerclient.exc"] = None
sys.modules["ceilometerclient.shell"] = None sys.modules["ceilometerclient.shell"] = None
self.assertRaises(ImportError, shell.main, msg = ("To use this command, you should install "
"trace show fake_uuid".split()) "'ceilometerclient' manually. Use command:\n "
"'pip install python-ceilometerclient'.")
self._test_with_command_error("trace show fake-uuid", msg)
def test_trace_show_unauthorized(self): def test_trace_show_unauthorized(self):
class FakeHTTPUnauthorized(Exception): class FakeHTTPUnauthorized(Exception):
@@ -139,18 +140,17 @@ class ShellTestCase(test.TestCase):
pass pass
self.ceiloclient.client.get_client.side_effect = FakeException self.ceiloclient.client.get_client.side_effect = FakeException
msg = "Something has gone wrong. See logs for more details" msg = "Something has gone wrong. See ceilometer logs for more details"
self._test_with_command_error("trace show fake_id", msg) self._test_with_command_error("trace show fake_id", msg)
@mock.patch("osprofiler.parsers.ceilometer.get_notifications") @mock.patch("osprofiler.drivers.ceilometer.Ceilometer.get_report")
@mock.patch("osprofiler.parsers.ceilometer.parse_notifications") def test_trace_show_no_selected_format(self, mock_get):
def test_trace_show_no_selected_format(self, mock_notifications, mock_get):
mock_get.return_value = "some_notificatios" mock_get.return_value = "some_notificatios"
msg = ("You should choose one of the following output-formats: " msg = ("You should choose one of the following output-formats: "
"--json or --html.") "--json or --html.")
self._test_with_command_error("trace show fake_id", msg) self._test_with_command_error("trace show fake_id", msg)
@mock.patch("osprofiler.parsers.ceilometer.get_notifications") @mock.patch("osprofiler.drivers.ceilometer.Ceilometer.get_report")
def test_trace_show_trace_id_not_found(self, mock_get): def test_trace_show_trace_id_not_found(self, mock_get):
mock_get.return_value = None mock_get.return_value = None
@@ -165,29 +165,26 @@ class ShellTestCase(test.TestCase):
self._test_with_command_error("trace show %s" % fake_trace_id, msg) self._test_with_command_error("trace show %s" % fake_trace_id, msg)
@mock.patch("sys.stdout", six.StringIO()) @mock.patch("sys.stdout", six.StringIO())
@mock.patch("osprofiler.parsers.ceilometer.get_notifications") @mock.patch("osprofiler.drivers.ceilometer.Ceilometer.get_report")
@mock.patch("osprofiler.parsers.ceilometer.parse_notifications") def test_trace_show_in_json(self, mock_get):
def test_trace_show_in_json(self, mock_notifications, mock_get):
mock_get.return_value = "some notification"
notifications = { notifications = {
"info": { "info": {
"started": 0, "finished": 0, "name": "total"}, "children": []} "started": 0, "finished": 0, "name": "total"}, "children": []}
mock_notifications.return_value = notifications
mock_get.return_value = notifications
self.run_command("trace show fake_id --json") self.run_command("trace show fake_id --json")
self.assertEqual("%s\n" % json.dumps(notifications), self.assertEqual("%s\n" % json.dumps(notifications),
sys.stdout.getvalue()) sys.stdout.getvalue())
@mock.patch("sys.stdout", six.StringIO()) @mock.patch("sys.stdout", six.StringIO())
@mock.patch("osprofiler.parsers.ceilometer.get_notifications") @mock.patch("osprofiler.drivers.ceilometer.Ceilometer.get_report")
@mock.patch("osprofiler.parsers.ceilometer.parse_notifications") def test_trace_show_in_html(self, mock_get):
def test_trace_show_in_html(self, mock_notifications, mock_get):
mock_get.return_value = "some notification"
notifications = { notifications = {
"info": { "info": {
"started": 0, "finished": 0, "name": "total"}, "children": []} "started": 0, "finished": 0, "name": "total"}, "children": []}
mock_notifications.return_value = notifications
mock_get.return_value = notifications
# NOTE(akurilin): to simplify assert statement, html-template should be # NOTE(akurilin): to simplify assert statement, html-template should be
# replaced. # replaced.
@@ -202,24 +199,23 @@ class ShellTestCase(test.TestCase):
with mock.patch("osprofiler.cmd.commands.open", with mock.patch("osprofiler.cmd.commands.open",
mock.mock_open(read_data=html_template), create=True): mock.mock_open(read_data=html_template), create=True):
self.run_command("trace show fake_id --html") self.run_command("trace show fake_id --html")
self.assertEqual("A long time ago in a galaxy far, far away..." self.assertEqual("A long time ago in a galaxy far, far away..."
" some_data = %s" " some_data = %s"
"It is a period of civil war. Rebel" "It is a period of civil war. Rebel"
"spaceships, striking from a hidden" "spaceships, striking from a hidden"
"base, have won their first victory" "base, have won their first victory"
"against the evil Galactic Empire." "against the evil Galactic Empire."
"\n" % json.dumps(notifications, indent=2), "\n" % json.dumps(notifications, indent=2),
sys.stdout.getvalue()) sys.stdout.getvalue())
@mock.patch("sys.stdout", six.StringIO()) @mock.patch("sys.stdout", six.StringIO())
@mock.patch("osprofiler.parsers.ceilometer.get_notifications") @mock.patch("osprofiler.drivers.ceilometer.Ceilometer.get_report")
@mock.patch("osprofiler.parsers.ceilometer.parse_notifications") def test_trace_show_write_to_file(self, mock_get):
def test_trace_show_write_to_file(self, mock_notifications, mock_get):
mock_get.return_value = "some notification"
notifications = { notifications = {
"info": { "info": {
"started": 0, "finished": 0, "name": "total"}, "children": []} "started": 0, "finished": 0, "name": "total"}, "children": []}
mock_notifications.return_value = notifications
mock_get.return_value = notifications
with mock.patch("osprofiler.cmd.commands.open", with mock.patch("osprofiler.cmd.commands.open",
mock.mock_open(), create=True) as mock_open: mock.mock_open(), create=True) as mock_open:

View File

@@ -0,0 +1,411 @@
# Copyright 2016 Mirantis Inc.
# All Rights Reserved.
#
# 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 mock
from osprofiler.drivers.ceilometer import Ceilometer
from osprofiler.tests import test
class CeilometerParserTestCase(test.TestCase):
def setUp(self):
super(CeilometerParserTestCase, self).setUp()
self.ceilometer = Ceilometer("ceilometer://",
ceilometer_api_version="2")
def test_build_empty_tree(self):
self.assertEqual([], self.ceilometer._build_tree({}))
def test_build_complex_tree(self):
test_input = {
"2": {"parent_id": "0", "trace_id": "2", "info": {"started": 1}},
"1": {"parent_id": "0", "trace_id": "1", "info": {"started": 0}},
"21": {"parent_id": "2", "trace_id": "21", "info": {"started": 6}},
"22": {"parent_id": "2", "trace_id": "22", "info": {"started": 7}},
"11": {"parent_id": "1", "trace_id": "11", "info": {"started": 1}},
"113": {"parent_id": "11", "trace_id": "113",
"info": {"started": 3}},
"112": {"parent_id": "11", "trace_id": "112",
"info": {"started": 2}},
"114": {"parent_id": "11", "trace_id": "114",
"info": {"started": 5}}
}
expected_output = [
{
"parent_id": "0",
"trace_id": "1",
"info": {"started": 0},
"children": [
{
"parent_id": "1",
"trace_id": "11",
"info": {"started": 1},
"children": [
{"parent_id": "11", "trace_id": "112",
"info": {"started": 2}, "children": []},
{"parent_id": "11", "trace_id": "113",
"info": {"started": 3}, "children": []},
{"parent_id": "11", "trace_id": "114",
"info": {"started": 5}, "children": []}
]
}
]
},
{
"parent_id": "0",
"trace_id": "2",
"info": {"started": 1},
"children": [
{"parent_id": "2", "trace_id": "21",
"info": {"started": 6}, "children": []},
{"parent_id": "2", "trace_id": "22",
"info": {"started": 7}, "children": []}
]
}
]
result = self.ceilometer._build_tree(test_input)
self.assertEqual(expected_output, result)
def test_get_report_empty(self):
self.ceilometer.client = mock.MagicMock()
self.ceilometer.client.events.list.return_value = []
expected = {
"info": {
"name": "total",
"started": 0,
"finished": None
},
"children": []
}
base_id = "10"
self.assertEqual(expected, self.ceilometer.get_report(base_id))
def test_get_report(self):
self.ceilometer.client = mock.MagicMock()
results = [mock.MagicMock(), mock.MagicMock(), mock.MagicMock(),
mock.MagicMock(), mock.MagicMock()]
self.ceilometer.client.events.list.return_value = results
results[0].to_dict.return_value = {
"traits": [
{
"type": "string",
"name": "base_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "host",
"value": "ubuntu"
},
{
"type": "string",
"name": "method",
"value": "POST"
},
{
"type": "string",
"name": "name",
"value": "wsgi-start"
},
{
"type": "string",
"name": "parent_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "project",
"value": "keystone"
},
{
"type": "string",
"name": "service",
"value": "main"
},
{
"type": "string",
"name": "timestamp",
"value": "2015-12-23T14:02:22.338776"
},
{
"type": "string",
"name": "trace_id",
"value": "06320327-2c2c-45ae-923a-515de890276a"
}
],
"raw": {},
"generated": "2015-12-23T10:41:38.415793",
"event_type": "profiler.main",
"message_id": "65fc1553-3082-4a6f-9d1e-0e3183f57a47"}
results[1].to_dict.return_value = {
"traits":
[
{
"type": "string",
"name": "base_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "host",
"value": "ubuntu"
},
{
"type": "string",
"name": "name",
"value": "wsgi-stop"
},
{
"type": "string",
"name": "parent_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "project",
"value": "keystone"
},
{
"type": "string",
"name": "service",
"value": "main"
},
{
"type": "string",
"name": "timestamp",
"value": "2015-12-23T14:02:22.380405"
},
{
"type": "string",
"name": "trace_id",
"value": "016c97fd-87f3-40b2-9b55-e431156b694b"
}
],
"raw": {},
"generated": "2015-12-23T10:41:38.406052",
"event_type": "profiler.main",
"message_id": "3256d9f1-48ba-4ac5-a50b-64fa42c6e264"}
results[2].to_dict.return_value = {
"traits":
[
{
"type": "string",
"name": "base_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "db.params",
"value": "[]"
},
{
"type": "string",
"name": "db.statement",
"value": "SELECT 1"
},
{
"type": "string",
"name": "host",
"value": "ubuntu"
},
{
"type": "string",
"name": "name",
"value": "db-start"
},
{
"type": "string",
"name": "parent_id",
"value": "06320327-2c2c-45ae-923a-515de890276a"
},
{
"type": "string",
"name": "project",
"value": "keystone"
},
{
"type": "string",
"name": "service",
"value": "main"
},
{
"type": "string",
"name": "timestamp",
"value": "2015-12-23T14:02:22.395365"
},
{
"type": "string",
"name": "trace_id",
"value": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a"
}
],
"raw": {},
"generated": "2015-12-23T10:41:38.984161",
"event_type": "profiler.main",
"message_id": "60368aa4-16f0-4f37-a8fb-89e92fdf36ff"}
results[3].to_dict.return_value = {
"traits":
[
{
"type": "string",
"name": "base_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "host",
"value": "ubuntu"
},
{
"type": "string",
"name": "name",
"value": "db-stop"
},
{
"type": "string",
"name": "parent_id",
"value": "06320327-2c2c-45ae-923a-515de890276a"
},
{
"type": "string",
"name": "project",
"value": "keystone"
},
{
"type": "string",
"name": "service",
"value": "main"
},
{
"type": "string",
"name": "timestamp",
"value": "2015-12-23T14:02:22.415486"
},
{
"type": "string",
"name": "trace_id",
"value": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a"
}
],
"raw": {},
"generated": "2015-12-23T10:41:39.019378",
"event_type": "profiler.main",
"message_id": "3fbeb339-55c5-4f28-88e4-15bee251dd3d"}
results[4].to_dict.return_value = {
"traits":
[
{
"type": "string",
"name": "base_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "host",
"value": "ubuntu"
},
{
"type": "string",
"name": "method",
"value": "GET"
},
{
"type": "string",
"name": "name",
"value": "wsgi-start"
},
{
"type": "string",
"name": "parent_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "project",
"value": "keystone"
},
{
"type": "string",
"name": "service",
"value": "main"
},
{
"type": "string",
"name": "timestamp",
"value": "2015-12-23T14:02:22.427444"
},
{
"type": "string",
"name": "trace_id",
"value": "016c97fd-87f3-40b2-9b55-e431156b694b"
}
],
"raw": {},
"generated": "2015-12-23T10:41:38.360409",
"event_type": "profiler.main",
"message_id": "57b971a9-572f-4f29-9838-3ed2564c6b5b"}
expected = {"children": [
{"children": [{"children": [],
"info": {"finished": 76,
"host": "ubuntu",
"meta.raw_payload.db-start": {},
"meta.raw_payload.db-stop": {},
"name": "db",
"project": "keystone",
"service": "main",
"started": 56},
"parent_id": "06320327-2c2c-45ae-923a-515de890276a",
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a"}
],
"info": {"finished": 0,
"host": "ubuntu",
"meta.raw_payload.wsgi-start": {},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 0},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "06320327-2c2c-45ae-923a-515de890276a"},
{"children": [],
"info": {"finished": 41,
"host": "ubuntu",
"meta.raw_payload.wsgi-start": {},
"meta.raw_payload.wsgi-stop": {},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 88},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "016c97fd-87f3-40b2-9b55-e431156b694b"}],
"info": {"finished": 88, "name": "total", "started": 0}}
base_id = "10"
result = self.ceilometer.get_report(base_id)
expected_filter = [{"field": "base_id", "op": "eq", "value": base_id}]
self.ceilometer.client.events.list.assert_called_once_with(
expected_filter, limit=100000)
self.assertEqual(expected, result)

View File

@@ -1,402 +0,0 @@
# Copyright 2014 Mirantis Inc.
# All Rights Reserved.
#
# 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 mock
from osprofiler.parsers import ceilometer
from osprofiler.tests import test
class CeilometerParserTestCase(test.TestCase):
def test_build_empty_tree(self):
self.assertEqual(ceilometer._build_tree({}), [])
def test_build_complex_tree(self):
test_input = {
"2": {"parent_id": "0", "trace_id": "2", "info": {"started": 1}},
"1": {"parent_id": "0", "trace_id": "1", "info": {"started": 0}},
"21": {"parent_id": "2", "trace_id": "21", "info": {"started": 6}},
"22": {"parent_id": "2", "trace_id": "22", "info": {"started": 7}},
"11": {"parent_id": "1", "trace_id": "11", "info": {"started": 1}},
"113": {"parent_id": "11", "trace_id": "113",
"info": {"started": 3}},
"112": {"parent_id": "11", "trace_id": "112",
"info": {"started": 2}},
"114": {"parent_id": "11", "trace_id": "114",
"info": {"started": 5}}
}
expected_output = [
{
"parent_id": "0",
"trace_id": "1",
"info": {"started": 0},
"children": [
{
"parent_id": "1",
"trace_id": "11",
"info": {"started": 1},
"children": [
{"parent_id": "11", "trace_id": "112",
"info": {"started": 2}, "children": []},
{"parent_id": "11", "trace_id": "113",
"info": {"started": 3}, "children": []},
{"parent_id": "11", "trace_id": "114",
"info": {"started": 5}, "children": []}
]
}
]
},
{
"parent_id": "0",
"trace_id": "2",
"info": {"started": 1},
"children": [
{"parent_id": "2", "trace_id": "21",
"info": {"started": 6}, "children": []},
{"parent_id": "2", "trace_id": "22",
"info": {"started": 7}, "children": []}
]
}
]
self.assertEqual(ceilometer._build_tree(test_input), expected_output)
def test_parse_notifications_empty(self):
expected = {
"info": {
"name": "total",
"started": 0,
"finished": 0
},
"children": []
}
self.assertEqual(ceilometer.parse_notifications([]), expected)
def test_parse_notifications(self):
events = [
{
"traits": [
{
"type": "string",
"name": "base_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "host",
"value": "ubuntu"
},
{
"type": "string",
"name": "method",
"value": "POST"
},
{
"type": "string",
"name": "name",
"value": "wsgi-start"
},
{
"type": "string",
"name": "parent_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "project",
"value": "keystone"
},
{
"type": "string",
"name": "service",
"value": "main"
},
{
"type": "string",
"name": "timestamp",
"value": "2015-12-23T14:02:22.338776"
},
{
"type": "string",
"name": "trace_id",
"value": "06320327-2c2c-45ae-923a-515de890276a"
}
],
"raw": {},
"generated": "2015-12-23T10:41:38.415793",
"event_type": "profiler.main",
"message_id": "65fc1553-3082-4a6f-9d1e-0e3183f57a47"},
{
"traits":
[
{
"type": "string",
"name": "base_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "host",
"value": "ubuntu"
},
{
"type": "string",
"name": "name",
"value": "wsgi-stop"
},
{
"type": "string",
"name": "parent_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "project",
"value": "keystone"
},
{
"type": "string",
"name": "service",
"value": "main"
},
{
"type": "string",
"name": "timestamp",
"value": "2015-12-23T14:02:22.380405"
},
{
"type": "string",
"name": "trace_id",
"value": "016c97fd-87f3-40b2-9b55-e431156b694b"
}
],
"raw": {},
"generated": "2015-12-23T10:41:38.406052",
"event_type": "profiler.main",
"message_id": "3256d9f1-48ba-4ac5-a50b-64fa42c6e264"},
{
"traits":
[
{
"type": "string",
"name": "base_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "db.params",
"value": "[]"
},
{
"type": "string",
"name": "db.statement",
"value": "SELECT 1"
},
{
"type": "string",
"name": "host",
"value": "ubuntu"
},
{
"type": "string",
"name": "name",
"value": "db-start"
},
{
"type": "string",
"name": "parent_id",
"value": "06320327-2c2c-45ae-923a-515de890276a"
},
{
"type": "string",
"name": "project",
"value": "keystone"
},
{
"type": "string",
"name": "service",
"value": "main"
},
{
"type": "string",
"name": "timestamp",
"value": "2015-12-23T14:02:22.395365"
},
{
"type": "string",
"name": "trace_id",
"value": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a"
}
],
"raw": {},
"generated": "2015-12-23T10:41:38.984161",
"event_type": "profiler.main",
"message_id": "60368aa4-16f0-4f37-a8fb-89e92fdf36ff"
},
{
"traits":
[
{
"type": "string",
"name": "base_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "host",
"value": "ubuntu"
},
{
"type": "string",
"name": "name",
"value": "db-stop"
},
{
"type": "string",
"name": "parent_id",
"value": "06320327-2c2c-45ae-923a-515de890276a"
},
{
"type": "string",
"name": "project",
"value": "keystone"
},
{
"type": "string",
"name": "service",
"value": "main"
},
{
"type": "string",
"name": "timestamp",
"value": "2015-12-23T14:02:22.415486"
},
{
"type": "string",
"name": "trace_id",
"value": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a"
}
],
"raw": {},
"generated": "2015-12-23T10:41:39.019378",
"event_type": "profiler.main",
"message_id": "3fbeb339-55c5-4f28-88e4-15bee251dd3d"
},
{
"traits":
[
{
"type": "string",
"name": "base_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "host",
"value": "ubuntu"
},
{
"type": "string",
"name": "method",
"value": "GET"
},
{
"type": "string",
"name": "name",
"value": "wsgi-start"
},
{
"type": "string",
"name": "parent_id",
"value": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
},
{
"type": "string",
"name": "project",
"value": "keystone"
},
{
"type": "string",
"name": "service",
"value": "main"
},
{
"type": "string",
"name": "timestamp",
"value": "2015-12-23T14:02:22.427444"
},
{
"type": "string",
"name": "trace_id",
"value": "016c97fd-87f3-40b2-9b55-e431156b694b"
}
],
"raw": {},
"generated": "2015-12-23T10:41:38.360409",
"event_type": "profiler.main",
"message_id": "57b971a9-572f-4f29-9838-3ed2564c6b5b"
}
]
expected = {"children": [
{"children": [{"children": [],
"info": {"finished": 76,
"host": "ubuntu",
"meta.raw_payload.db-start": {},
"meta.raw_payload.db-stop": {},
"name": "db",
"project": "keystone",
"service": "main",
"started": 56},
"parent_id": "06320327-2c2c-45ae-923a-515de890276a",
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a"}
],
"info": {"finished": 0,
"host": "ubuntu",
"meta.raw_payload.wsgi-start": {},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 0},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "06320327-2c2c-45ae-923a-515de890276a"},
{"children": [],
"info": {"finished": 41,
"host": "ubuntu",
"meta.raw_payload.wsgi-start": {},
"meta.raw_payload.wsgi-stop": {},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 88},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "016c97fd-87f3-40b2-9b55-e431156b694b"}],
"info": {"finished": 88, "name": "total", "started": 0}}
self.assertEqual(expected, ceilometer.parse_notifications(events))
def test_get_notifications(self):
mock_ceil_client = mock.MagicMock()
results = [mock.MagicMock(), mock.MagicMock()]
mock_ceil_client.events.list.return_value = results
base_id = "10"
result = ceilometer.get_notifications(mock_ceil_client, base_id)
expected_filter = [{"field": "base_id", "op": "eq", "value": base_id}]
mock_ceil_client.events.list.assert_called_once_with(expected_filter,
limit=100000)
self.assertEqual(result, [results[0].to_dict(), results[1].to_dict()])

View File

@@ -10,4 +10,6 @@ oslosphinx>=2.5.0,!=3.4.0 # Apache-2.0
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
# Bandit security code scanner # Bandit security code scanner
bandit>=0.17.3 # Apache-2.0 bandit>=0.17.3 # Apache-2.0
python-ceilometerclient>=2.2.1 # Apache-2.0