diff --git a/collectd_ceilometer/ceilometer/writer.py b/collectd_ceilometer/ceilometer/writer.py index 38b0aad..9fe5210 100644 --- a/collectd_ceilometer/ceilometer/writer.py +++ b/collectd_ceilometer/ceilometer/writer.py @@ -16,12 +16,11 @@ from __future__ import unicode_literals from collectd_ceilometer.ceilometer import sender as ceilometer_sender -from collections import defaultdict +from collectd_ceilometer.common.meters.storage import SampleContainer from collections import namedtuple import json import logging import six -import threading import time LOGGER = logging.getLogger(__name__) @@ -46,43 +45,6 @@ class Sample(namedtuple('Sample', ['value', 'timestamp', 'meta', } -class SampleContainer(object): - """Sample storage""" - - def __init__(self): - self._lock = threading.Lock() - self._data = defaultdict(list) - - def add(self, key, samples, limit): - """Store list of samples under the key - - Store the list of samples under the given key. If numer of stored - samples is greater than the given limit, all the samples are returned - and the stored samples are dropped. Otherwise None is returned. - - @param key key of the samples - @param samples list of samples - @param limit sample list limit - """ - with self._lock: - current = self._data[key] - current += samples - if len(current) >= limit: - self._data[key] = [] - return current - return None - - def reset(self): - """Reset stored samples - - Returns all samples and removes them from the container. - """ - with self._lock: - retval = self._data - self._data = defaultdict(list) - return retval - - class Writer(object): """Data collector""" diff --git a/collectd_ceilometer/common/meters/storage.py b/collectd_ceilometer/common/meters/storage.py index 800e1b6..45f8780 100644 --- a/collectd_ceilometer/common/meters/storage.py +++ b/collectd_ceilometer/common/meters/storage.py @@ -15,7 +15,9 @@ from __future__ import unicode_literals +from collections import defaultdict import six +import threading from collectd_ceilometer.common.meters.base import Meter from collectd_ceilometer.common.meters.libvirt import LibvirtMeter @@ -41,3 +43,40 @@ class MeterStorage(object): """Get meter for the collectd plugin""" # return specialized meter class for collectd plugin or default Meter return self._meters.get(plugin, self._default) + + +class SampleContainer(object): + """Temporary storage for collectd samples""" + + def __init__(self): + self._lock = threading.Lock() + self._data = defaultdict(list) + + def add(self, key, samples, limit): + """Store list of samples under the key + + Store the list of samples under the given key. If the number of stored + samples is greater than the given limit, all the samples are returned + and the stored samples are dropped. Otherwise None is returned. + + @param key key of the samples + @param samples list of samples + @param limit sample list limit + """ + with self._lock: + current = self._data[key] + current += samples + if len(current) >= limit: + self._data[key] = [] + return current + return None + + def reset(self): + """Reset stored samples + + Returns all samples and removes them from the container. + """ + with self._lock: + retval = self._data + self._data = defaultdict(list) + return retval diff --git a/collectd_ceilometer/gnocchi/writer.py b/collectd_ceilometer/gnocchi/writer.py index 89da3ca..f0551a9 100644 --- a/collectd_ceilometer/gnocchi/writer.py +++ b/collectd_ceilometer/gnocchi/writer.py @@ -15,14 +15,13 @@ from __future__ import unicode_literals +from collectd_ceilometer.common.meters.storage import SampleContainer from collectd_ceilometer.gnocchi import sender as gnocchi_sender -from collections import defaultdict from collections import namedtuple import datetime import json import logging import six -import threading LOGGER = logging.getLogger(__name__) @@ -39,43 +38,6 @@ class Sample(namedtuple('Sample', ['value', 'timestamp', 'meta', } -class SampleContainer(object): - """Sample storage""" - - def __init__(self): - self._lock = threading.Lock() - self._data = defaultdict(list) - - def add(self, key, samples, limit): - """Store list of samples under the key - - Store the list of samples under the given key. If numer of stored - samples is greater than the given limit, all the samples are returned - and the stored samples are dropped. Otherwise None is returned. - - @param key key of the samples - @param samples list of samples - @param limit sample list limit - """ - with self._lock: - current = self._data[key] - current += samples - if len(current) >= limit: - self._data[key] = [] - return current - return None - - def reset(self): - """Reset stored samples - - Returns all samples and removes them from the container. - """ - with self._lock: - retval = self._data - self._data = defaultdict(list) - return retval - - class Writer(object): """Data collector""" diff --git a/collectd_ceilometer/tests/common/test_sample_container.py b/collectd_ceilometer/tests/common/test_sample_container.py new file mode 100644 index 0000000..f10aabf --- /dev/null +++ b/collectd_ceilometer/tests/common/test_sample_container.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- + +# Copyright 2010-2011 OpenStack Foundation +# Copyright (c) 2015 Intel Corporation. +# +# 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. + +"""Plugin tests""" + +from __future__ import unicode_literals + +from collectd_ceilometer.common.meters.storage import SampleContainer + +import unittest + +from collections import defaultdict + + +class TestSampleContainer(unittest.TestCase): + """Test the common.meters.storage.SampleContainer class""" + + def setUp(self): + super(TestSampleContainer, self).setUp() + self.container = SampleContainer() + + def test_sample_container_init(self): + """Test creating the SampleContainer + + Set-up: create a container + Test: Container is empty + Expected behaviour: container is an empty dict of lists + """ + self.assertEqual({}, self.container._data) + + def test_sample_container_add(self): + """Test adding an element to SampleContainer + + Set-up: create empty SampleContainer + Test: add an element to the SampleContainer + Expected behaviour: _data contains the added elements + """ + retval = self.container.add("key1", ["value1"], 5) + + self.assertEqual(retval, None) + self.assertEqual(["value1", ], self.container._data["key1"]) + + def test_sample_container_add_exceeds_limit(self): + """Test adding an element to the Container so len > limit. + + Set-up: len(SampleContainer._data ) < limit; + Test: Add items to the container so len() > limit + Expected behaviour: SampleContainer._data[key] is empty and add() + returns a list of samples of length limit + """ + self.assertEqual(self.container._data, defaultdict(list)) + + retval = self.container.add("key1", ["1", "2", "3", ], 2) + + self.assertEqual(retval, ["1", "2", "3", ]) + self.assertEqual([], self.container._data["key1"]) + + def test_sample_container_reset(self): + """Test resetting the contents of a meter entry in SampleContainer + + Set-up: add some entries to the container (two meters) + action: call container.reset + Expected behaviour: the container will be equivalent to a default dict + and reset returns the stored data + """ + expected = {"key1": ["one", "two", "three", ], + "key2": ["1", "2", "3", ]} + + self.container.add("key1", expected["key1"], 42) + self.container.add("key2", expected["key2"], 42) + + self.assertEqual(expected, self.container._data) + + retval = self.container.reset() + + self.assertEqual(expected, retval) + self.assertEqual({}, self.container._data)