Move meter signature computing into meter_publish
Blueprint: oslo-multi-publisher Change-Id: I7e758dfb56604fb5a92690ac9719b129837159cf Signed-off-by: Julien Danjou <julien@danjou.info>
This commit is contained in:
parent
1667ded277
commit
43d728c631
@ -1,6 +1,6 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright © 2012 eNovance <licensing@enovance.com>
|
# Copyright © 2012-2013 eNovance <licensing@enovance.com>
|
||||||
#
|
#
|
||||||
# Author: Julien Danjou <julien@danjou.info>
|
# Author: Julien Danjou <julien@danjou.info>
|
||||||
#
|
#
|
||||||
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter as meter_api
|
from ceilometer.publisher import meter as publisher_meter
|
||||||
from ceilometer import extension_manager
|
from ceilometer import extension_manager
|
||||||
from ceilometer.openstack.common import context
|
from ceilometer.openstack.common import context
|
||||||
from ceilometer.openstack.common import log
|
from ceilometer.openstack.common import log
|
||||||
@ -87,9 +87,9 @@ class CollectorService(service.PeriodicService):
|
|||||||
# Set ourselves up as a separate worker for the metering data,
|
# Set ourselves up as a separate worker for the metering data,
|
||||||
# since the default for service is to use create_consumer().
|
# since the default for service is to use create_consumer().
|
||||||
self.conn.create_worker(
|
self.conn.create_worker(
|
||||||
cfg.CONF.metering_topic,
|
cfg.CONF.publisher_meter.metering_topic,
|
||||||
rpc_dispatcher.RpcDispatcher([self]),
|
rpc_dispatcher.RpcDispatcher([self]),
|
||||||
'ceilometer.collector.' + cfg.CONF.metering_topic,
|
'ceilometer.collector.' + cfg.CONF.publisher_meter.metering_topic,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _setup_subscription(self, ext, *args, **kwds):
|
def _setup_subscription(self, ext, *args, **kwds):
|
||||||
@ -139,7 +139,9 @@ class CollectorService(service.PeriodicService):
|
|||||||
meter['resource_id'],
|
meter['resource_id'],
|
||||||
meter.get('timestamp', 'NO TIMESTAMP'),
|
meter.get('timestamp', 'NO TIMESTAMP'),
|
||||||
meter['counter_volume'])
|
meter['counter_volume'])
|
||||||
if meter_api.verify_signature(meter, cfg.CONF.metering_secret):
|
if publisher_meter.verify_signature(
|
||||||
|
meter,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret):
|
||||||
try:
|
try:
|
||||||
# Convert the timestamp to a datetime instance.
|
# Convert the timestamp to a datetime instance.
|
||||||
# Storage engines are responsible for converting
|
# Storage engines are responsible for converting
|
||||||
|
@ -15,27 +15,41 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
"""Compute the signature of a metering message.
|
"""Publish a counter using the preferred RPC mechanism.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
|
import itertools
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
METER_OPTS = [
|
from ceilometer.openstack.common import log
|
||||||
|
from ceilometer.openstack.common import rpc
|
||||||
|
from ceilometer import publisher
|
||||||
|
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
METER_PUBLISH_OPTS = [
|
||||||
|
cfg.StrOpt('metering_topic',
|
||||||
|
default='metering',
|
||||||
|
help='the topic ceilometer uses for metering messages',
|
||||||
|
deprecated_group="DEFAULT",
|
||||||
|
),
|
||||||
cfg.StrOpt('metering_secret',
|
cfg.StrOpt('metering_secret',
|
||||||
default='change this or be hacked',
|
default='change this or be hacked',
|
||||||
help='Secret value for signing metering messages',
|
help='Secret value for signing metering messages',
|
||||||
|
deprecated_group="DEFAULT",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def register_opts(config):
|
def register_opts(config):
|
||||||
"""Register the options for signing metering messages.
|
"""Register the options for publishing metering messages.
|
||||||
"""
|
"""
|
||||||
config.register_opts(METER_OPTS)
|
config.register_opts(METER_PUBLISH_OPTS, group="publisher_meter")
|
||||||
|
|
||||||
|
|
||||||
register_opts(cfg.CONF)
|
register_opts(cfg.CONF)
|
||||||
@ -101,3 +115,40 @@ def meter_message_from_counter(counter, secret, source):
|
|||||||
}
|
}
|
||||||
msg['message_signature'] = compute_signature(msg, secret)
|
msg['message_signature'] = compute_signature(msg, secret)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
class MeterPublisher(publisher.PublisherBase):
|
||||||
|
def publish_counters(self, context, counters, source):
|
||||||
|
"""Send a metering message for publishing
|
||||||
|
|
||||||
|
:param context: Execution context from the service or RPC call
|
||||||
|
:param counter: Counter from pipeline after transformation
|
||||||
|
:param source: counter source
|
||||||
|
"""
|
||||||
|
|
||||||
|
meters = [
|
||||||
|
meter_message_from_counter(
|
||||||
|
counter,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
|
source)
|
||||||
|
for counter in counters
|
||||||
|
]
|
||||||
|
|
||||||
|
topic = cfg.CONF.publisher_meter.metering_topic
|
||||||
|
msg = {
|
||||||
|
'method': 'record_metering_data',
|
||||||
|
'version': '1.0',
|
||||||
|
'args': {'data': meters},
|
||||||
|
}
|
||||||
|
LOG.debug('PUBLISH: %s', str(msg))
|
||||||
|
rpc.cast(context, topic, msg)
|
||||||
|
|
||||||
|
for meter_name, meter_list in itertools.groupby(
|
||||||
|
sorted(meters, key=lambda m: m['counter_name']),
|
||||||
|
lambda m: m['counter_name']):
|
||||||
|
msg = {
|
||||||
|
'method': 'record_metering_data',
|
||||||
|
'version': '1.0',
|
||||||
|
'args': {'data': list(meter_list)},
|
||||||
|
}
|
||||||
|
rpc.cast(context, topic + '.' + meter_name, msg)
|
@ -1,83 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
|
||||||
#
|
|
||||||
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
"""Publish a counter using the preferred RPC mechanism.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import itertools
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
|
|
||||||
from ceilometer.collector import meter as meter_api
|
|
||||||
from ceilometer.openstack.common import log
|
|
||||||
from ceilometer.openstack.common import rpc
|
|
||||||
from ceilometer import publisher
|
|
||||||
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
|
|
||||||
PUBLISH_OPTS = [
|
|
||||||
cfg.StrOpt('metering_topic',
|
|
||||||
default='metering',
|
|
||||||
help='the topic ceilometer uses for metering messages',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(config):
|
|
||||||
"""Register the options for publishing metering messages.
|
|
||||||
"""
|
|
||||||
config.register_opts(PUBLISH_OPTS)
|
|
||||||
|
|
||||||
|
|
||||||
register_opts(cfg.CONF)
|
|
||||||
|
|
||||||
|
|
||||||
class MeterPublisher(publisher.PublisherBase):
|
|
||||||
def publish_counters(self, context, counters, source):
|
|
||||||
"""Send a metering message for publishing
|
|
||||||
|
|
||||||
:param context: Execution context from the service or RPC call
|
|
||||||
:param counter: Counter from pipeline after transformation
|
|
||||||
:param source: counter source
|
|
||||||
"""
|
|
||||||
|
|
||||||
meters = [
|
|
||||||
meter_api.meter_message_from_counter(counter,
|
|
||||||
cfg.CONF.metering_secret,
|
|
||||||
source)
|
|
||||||
for counter in counters
|
|
||||||
]
|
|
||||||
|
|
||||||
topic = cfg.CONF.metering_topic
|
|
||||||
msg = {
|
|
||||||
'method': 'record_metering_data',
|
|
||||||
'version': '1.0',
|
|
||||||
'args': {'data': meters},
|
|
||||||
}
|
|
||||||
LOG.debug('PUBLISH: %s', str(msg))
|
|
||||||
rpc.cast(context, topic, msg)
|
|
||||||
|
|
||||||
for meter_name, meter_list in itertools.groupby(
|
|
||||||
sorted(meters, key=lambda m: m['counter_name']),
|
|
||||||
lambda m: m['counter_name']):
|
|
||||||
msg = {
|
|
||||||
'method': 'record_metering_data',
|
|
||||||
'version': '1.0',
|
|
||||||
'args': {'data': list(meter_list)},
|
|
||||||
}
|
|
||||||
rpc.cast(context, topic + '.' + meter_name, msg)
|
|
@ -404,7 +404,7 @@
|
|||||||
#matchmaker_heartbeat_ttl=600
|
#matchmaker_heartbeat_ttl=600
|
||||||
|
|
||||||
|
|
||||||
######## defined in ceilometer.publisher.meter_publish ########
|
######## defined in ceilometer.publisher.meter ########
|
||||||
|
|
||||||
# the topic ceilometer uses for metering messages (string
|
# the topic ceilometer uses for metering messages (string
|
||||||
# value)
|
# value)
|
||||||
|
@ -6,4 +6,4 @@
|
|||||||
- "*"
|
- "*"
|
||||||
transformers:
|
transformers:
|
||||||
publishers:
|
publishers:
|
||||||
- meter_publisher
|
- meter
|
||||||
|
3
setup.py
3
setup.py
@ -140,7 +140,8 @@ setuptools.setup(
|
|||||||
accumulator = ceilometer.transformer.accumulator:TransformerAccumulator
|
accumulator = ceilometer.transformer.accumulator:TransformerAccumulator
|
||||||
|
|
||||||
[ceilometer.publisher]
|
[ceilometer.publisher]
|
||||||
meter_publisher = ceilometer.publisher.meter_publish:MeterPublisher
|
meter_publisher = ceilometer.publisher.meter:MeterPublisher
|
||||||
|
meter = ceilometer.publisher.meter:MeterPublisher
|
||||||
|
|
||||||
[paste.filter_factory]
|
[paste.filter_factory]
|
||||||
swift=ceilometer.objectstore.swift_middleware:filter_factory
|
swift=ceilometer.objectstore.swift_middleware:filter_factory
|
||||||
|
@ -23,7 +23,7 @@ import datetime
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from ceilometer.tests import api as tests_api
|
from ceilometer.tests import api as tests_api
|
||||||
@ -71,8 +71,9 @@ class TestListEvents(tests_api.TestBase):
|
|||||||
'tag': 'self.counter2'}
|
'tag': 'self.counter2'}
|
||||||
),
|
),
|
||||||
]:
|
]:
|
||||||
msg = meter.meter_message_from_counter(cnt,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
cnt,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'source1')
|
'source1')
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import logging
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from ceilometer.tests import api as tests_api
|
from ceilometer.tests import api as tests_api
|
||||||
@ -100,8 +100,9 @@ class TestListMeters(tests_api.TestBase):
|
|||||||
timestamp=datetime.datetime(2012, 7, 2, 10, 43),
|
timestamp=datetime.datetime(2012, 7, 2, 10, 43),
|
||||||
resource_metadata={'display_name': 'test-server',
|
resource_metadata={'display_name': 'test-server',
|
||||||
'tag': 'four.counter'})]:
|
'tag': 'four.counter'})]:
|
||||||
msg = meter.meter_message_from_counter(cnt,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
cnt,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test_list_resources')
|
'test_list_resources')
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import logging
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from ceilometer.tests import api as tests_api
|
from ceilometer.tests import api as tests_api
|
||||||
@ -55,8 +55,9 @@ class TestListProjects(tests_api.TestBase):
|
|||||||
resource_metadata={'display_name': 'test-server',
|
resource_metadata={'display_name': 'test-server',
|
||||||
'tag': 'self.counter'}
|
'tag': 'self.counter'}
|
||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(counter1,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter1,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test_list_projects',
|
'test_list_projects',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
@ -73,8 +74,9 @@ class TestListProjects(tests_api.TestBase):
|
|||||||
resource_metadata={'display_name': 'test-server',
|
resource_metadata={'display_name': 'test-server',
|
||||||
'tag': 'self.counter2'}
|
'tag': 'self.counter2'}
|
||||||
)
|
)
|
||||||
msg2 = meter.meter_message_from_counter(counter2,
|
msg2 = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter2,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test_list_users',
|
'test_list_users',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg2)
|
self.conn.record_metering_data(msg2)
|
||||||
|
@ -24,7 +24,7 @@ import logging
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from ceilometer.tests import api as tests_api
|
from ceilometer.tests import api as tests_api
|
||||||
@ -93,8 +93,9 @@ class TestListResourcesBase(tests_api.TestBase):
|
|||||||
resource_metadata={'display_name': 'test-server',
|
resource_metadata={'display_name': 'test-server',
|
||||||
'tag': 'self.counter4'}
|
'tag': 'self.counter4'}
|
||||||
)]:
|
)]:
|
||||||
msg = meter.meter_message_from_counter(cnt,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
cnt,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test_list_resources')
|
'test_list_resources')
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import logging
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from ceilometer.tests import api as tests_api
|
from ceilometer.tests import api as tests_api
|
||||||
@ -57,8 +57,9 @@ class TestListUsers(tests_api.TestBase):
|
|||||||
'tag': 'self.counter',
|
'tag': 'self.counter',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(counter1,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter1,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test_list_users',
|
'test_list_users',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
@ -76,8 +77,9 @@ class TestListUsers(tests_api.TestBase):
|
|||||||
'tag': 'self.counter2',
|
'tag': 'self.counter2',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg2 = meter.meter_message_from_counter(counter2,
|
msg2 = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter2,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'not-test',
|
'not-test',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg2)
|
self.conn.record_metering_data(msg2)
|
||||||
|
@ -23,7 +23,7 @@ import datetime
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from ceilometer.tests import api as tests_api
|
from ceilometer.tests import api as tests_api
|
||||||
@ -52,8 +52,9 @@ class TestMaxProjectVolume(tests_api.TestBase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.counters.append(c)
|
self.counters.append(c)
|
||||||
msg = meter.meter_message_from_counter(c,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
c,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'source1',
|
'source1',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
|
@ -22,7 +22,7 @@ import datetime
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from ceilometer.tests import api as tests_api
|
from ceilometer.tests import api as tests_api
|
||||||
@ -51,8 +51,9 @@ class TestMaxResourceVolume(tests_api.TestBase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.counters.append(c)
|
self.counters.append(c)
|
||||||
msg = meter.meter_message_from_counter(c,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
c,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'source1',
|
'source1',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
|
@ -23,7 +23,7 @@ import datetime
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from ceilometer.tests import api as tests_api
|
from ceilometer.tests import api as tests_api
|
||||||
@ -52,8 +52,9 @@ class TestSumProjectVolume(tests_api.TestBase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.counters.append(c)
|
self.counters.append(c)
|
||||||
msg = meter.meter_message_from_counter(c,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
c,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'source1',
|
'source1',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
|
@ -23,7 +23,7 @@ import datetime
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from ceilometer.tests import api as tests_api
|
from ceilometer.tests import api as tests_api
|
||||||
@ -52,8 +52,9 @@ class TestSumResourceVolume(tests_api.TestBase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.counters.append(c)
|
self.counters.append(c)
|
||||||
msg = meter.meter_message_from_counter(c,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
c,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'source1',
|
'source1',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
|
@ -23,7 +23,7 @@ import logging
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
@ -50,8 +50,9 @@ class TestListEvents(FunctionalTest):
|
|||||||
'ignored_list': ['not-returned'],
|
'ignored_list': ['not-returned'],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(self.counter1,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
self.counter1,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test_source',
|
'test_source',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
@ -69,8 +70,9 @@ class TestListEvents(FunctionalTest):
|
|||||||
'tag': 'self.counter2',
|
'tag': 'self.counter2',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg2 = meter.meter_message_from_counter(self.counter2,
|
msg2 = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
self.counter2,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'source2',
|
'source2',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg2)
|
self.conn.record_metering_data(msg2)
|
||||||
|
@ -23,7 +23,7 @@ import logging
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
@ -99,8 +99,9 @@ class TestListMeters(FunctionalTest):
|
|||||||
timestamp=datetime.datetime(2012, 7, 2, 10, 43),
|
timestamp=datetime.datetime(2012, 7, 2, 10, 43),
|
||||||
resource_metadata={'display_name': 'test-server',
|
resource_metadata={'display_name': 'test-server',
|
||||||
'tag': 'self.counter4'})]:
|
'tag': 'self.counter4'})]:
|
||||||
msg = meter.meter_message_from_counter(cnt,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
cnt,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test_source')
|
'test_source')
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import logging
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
@ -51,8 +51,9 @@ class TestListResources(FunctionalTest):
|
|||||||
timestamp=datetime.datetime(2012, 7, 2, 10, 40),
|
timestamp=datetime.datetime(2012, 7, 2, 10, 40),
|
||||||
resource_metadata=None
|
resource_metadata=None
|
||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(counter1,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter1,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test',
|
'test',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
@ -74,8 +75,9 @@ class TestListResources(FunctionalTest):
|
|||||||
'tag': 'self.counter',
|
'tag': 'self.counter',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(counter1,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter1,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test',
|
'test',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
@ -93,8 +95,9 @@ class TestListResources(FunctionalTest):
|
|||||||
'tag': 'self.counter2',
|
'tag': 'self.counter2',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg2 = meter.meter_message_from_counter(counter2,
|
msg2 = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter2,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test',
|
'test',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg2)
|
self.conn.record_metering_data(msg2)
|
||||||
@ -116,8 +119,9 @@ class TestListResources(FunctionalTest):
|
|||||||
'tag': 'self.counter',
|
'tag': 'self.counter',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(counter1,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter1,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test',
|
'test',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
@ -135,8 +139,9 @@ class TestListResources(FunctionalTest):
|
|||||||
'tag': 'self.counter2',
|
'tag': 'self.counter2',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg2 = meter.meter_message_from_counter(counter2,
|
msg2 = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter2,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test',
|
'test',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg2)
|
self.conn.record_metering_data(msg2)
|
||||||
@ -158,8 +163,9 @@ class TestListResources(FunctionalTest):
|
|||||||
'tag': 'self.counter',
|
'tag': 'self.counter',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(counter1,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter1,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test_list_resources',
|
'test_list_resources',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
@ -177,8 +183,9 @@ class TestListResources(FunctionalTest):
|
|||||||
'tag': 'self.counter2',
|
'tag': 'self.counter2',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg2 = meter.meter_message_from_counter(counter2,
|
msg2 = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter2,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'not-test',
|
'not-test',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg2)
|
self.conn.record_metering_data(msg2)
|
||||||
@ -203,8 +210,9 @@ class TestListResources(FunctionalTest):
|
|||||||
'tag': 'self.counter',
|
'tag': 'self.counter',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(counter1,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter1,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test_list_resources',
|
'test_list_resources',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
@ -222,8 +230,9 @@ class TestListResources(FunctionalTest):
|
|||||||
'tag': 'self.counter2',
|
'tag': 'self.counter2',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg2 = meter.meter_message_from_counter(counter2,
|
msg2 = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter2,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'not-test',
|
'not-test',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg2)
|
self.conn.record_metering_data(msg2)
|
||||||
@ -248,8 +257,9 @@ class TestListResources(FunctionalTest):
|
|||||||
'tag': 'self.counter',
|
'tag': 'self.counter',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(counter1,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter1,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test_list_resources',
|
'test_list_resources',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
@ -267,8 +277,9 @@ class TestListResources(FunctionalTest):
|
|||||||
'tag': 'self.counter2',
|
'tag': 'self.counter2',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg2 = meter.meter_message_from_counter(counter2,
|
msg2 = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter2,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'not-test',
|
'not-test',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg2)
|
self.conn.record_metering_data(msg2)
|
||||||
@ -295,8 +306,9 @@ class TestListResources(FunctionalTest):
|
|||||||
'ignored_list': ['not-returned'],
|
'ignored_list': ['not-returned'],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(counter1,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter1,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test',
|
'test',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
@ -323,8 +335,9 @@ class TestListResources(FunctionalTest):
|
|||||||
'tag': 'self.counter',
|
'tag': 'self.counter',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(counter1,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
counter1,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test_list_resources',
|
'test_list_resources',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
|
@ -22,7 +22,7 @@ import datetime
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from ceilometer.storage.impl_mongodb import require_map_reduce
|
from ceilometer.storage.impl_mongodb import require_map_reduce
|
||||||
@ -54,8 +54,9 @@ class TestMaxProjectVolume(FunctionalTest):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.counters.append(c)
|
self.counters.append(c)
|
||||||
msg = meter.meter_message_from_counter(c,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
c,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'source1',
|
'source1',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
|
@ -22,7 +22,7 @@ import datetime
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
@ -53,8 +53,9 @@ class TestMaxResourceVolume(FunctionalTest):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.counters.append(c)
|
self.counters.append(c)
|
||||||
msg = meter.meter_message_from_counter(c,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
c,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'source1',
|
'source1',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
|
@ -22,7 +22,7 @@ import datetime
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
@ -53,8 +53,9 @@ class TestSumProjectVolume(FunctionalTest):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.counters.append(c)
|
self.counters.append(c)
|
||||||
msg = meter.meter_message_from_counter(c,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
c,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'source1',
|
'source1',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
|
@ -22,7 +22,7 @@ import datetime
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
@ -53,8 +53,9 @@ class TestSumResourceVolume(FunctionalTest):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.counters.append(c)
|
self.counters.append(c)
|
||||||
msg = meter.meter_message_from_counter(c,
|
msg = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
c,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'source1',
|
'source1',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
|
@ -26,7 +26,7 @@ from oslo.config import cfg
|
|||||||
from stevedore import extension
|
from stevedore import extension
|
||||||
from stevedore.tests import manager as test_manager
|
from stevedore.tests import manager as test_manager
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer.collector import service
|
from ceilometer.collector import service
|
||||||
from ceilometer.storage import base
|
from ceilometer.storage import base
|
||||||
from ceilometer.tests import base as tests_base
|
from ceilometer.tests import base as tests_base
|
||||||
@ -89,7 +89,7 @@ class TestCollectorService(tests_base.TestCase):
|
|||||||
super(TestCollectorService, self).setUp()
|
super(TestCollectorService, self).setUp()
|
||||||
self.srv = service.CollectorService('the-host', 'the-topic')
|
self.srv = service.CollectorService('the-host', 'the-topic')
|
||||||
self.ctx = None
|
self.ctx = None
|
||||||
#cfg.CONF.metering_secret = 'not-so-secret'
|
#cfg.CONF.publisher_meter.metering_secret = 'not-so-secret'
|
||||||
|
|
||||||
@patch('ceilometer.pipeline.setup_pipeline', MagicMock())
|
@patch('ceilometer.pipeline.setup_pipeline', MagicMock())
|
||||||
def test_init_host(self):
|
def test_init_host(self):
|
||||||
@ -107,7 +107,7 @@ class TestCollectorService(tests_base.TestCase):
|
|||||||
}
|
}
|
||||||
msg['message_signature'] = meter.compute_signature(
|
msg['message_signature'] = meter.compute_signature(
|
||||||
msg,
|
msg,
|
||||||
cfg.CONF.metering_secret,
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.srv.storage_conn = self.mox.CreateMock(base.Connection)
|
self.srv.storage_conn = self.mox.CreateMock(base.Connection)
|
||||||
@ -146,7 +146,7 @@ class TestCollectorService(tests_base.TestCase):
|
|||||||
}
|
}
|
||||||
msg['message_signature'] = meter.compute_signature(
|
msg['message_signature'] = meter.compute_signature(
|
||||||
msg,
|
msg,
|
||||||
cfg.CONF.metering_secret,
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
)
|
)
|
||||||
|
|
||||||
expected = {}
|
expected = {}
|
||||||
@ -168,7 +168,7 @@ class TestCollectorService(tests_base.TestCase):
|
|||||||
}
|
}
|
||||||
msg['message_signature'] = meter.compute_signature(
|
msg['message_signature'] = meter.compute_signature(
|
||||||
msg,
|
msg,
|
||||||
cfg.CONF.metering_secret,
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
)
|
)
|
||||||
|
|
||||||
expected = {}
|
expected = {}
|
||||||
|
@ -1,184 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
|
||||||
#
|
|
||||||
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
"""Tests for ceilometer.meter
|
|
||||||
"""
|
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
|
||||||
from ceilometer import counter
|
|
||||||
from ceilometer.openstack.common import jsonutils
|
|
||||||
|
|
||||||
|
|
||||||
def test_compute_signature_change_key():
|
|
||||||
sig1 = meter.compute_signature({'a': 'A', 'b': 'B'}, 'not-so-secret')
|
|
||||||
sig2 = meter.compute_signature({'A': 'A', 'b': 'B'}, 'not-so-secret')
|
|
||||||
assert sig1 != sig2
|
|
||||||
|
|
||||||
|
|
||||||
def test_compute_signature_change_value():
|
|
||||||
sig1 = meter.compute_signature({'a': 'A', 'b': 'B'}, 'not-so-secret')
|
|
||||||
sig2 = meter.compute_signature({'a': 'a', 'b': 'B'}, 'not-so-secret')
|
|
||||||
assert sig1 != sig2
|
|
||||||
|
|
||||||
|
|
||||||
def test_compute_signature_same():
|
|
||||||
sig1 = meter.compute_signature({'a': 'A', 'b': 'B'}, 'not-so-secret')
|
|
||||||
sig2 = meter.compute_signature({'a': 'A', 'b': 'B'}, 'not-so-secret')
|
|
||||||
assert sig1 == sig2
|
|
||||||
|
|
||||||
|
|
||||||
def test_compute_signature_signed():
|
|
||||||
data = {'a': 'A', 'b': 'B'}
|
|
||||||
sig1 = meter.compute_signature(data, 'not-so-secret')
|
|
||||||
data['message_signature'] = sig1
|
|
||||||
sig2 = meter.compute_signature(data, 'not-so-secret')
|
|
||||||
assert sig1 == sig2
|
|
||||||
|
|
||||||
|
|
||||||
def test_compute_signature_use_configured_secret():
|
|
||||||
data = {'a': 'A', 'b': 'B'}
|
|
||||||
sig1 = meter.compute_signature(data, 'not-so-secret')
|
|
||||||
sig2 = meter.compute_signature(data, 'different-value')
|
|
||||||
assert sig1 != sig2
|
|
||||||
|
|
||||||
|
|
||||||
def test_verify_signature_signed():
|
|
||||||
data = {'a': 'A', 'b': 'B'}
|
|
||||||
sig1 = meter.compute_signature(data, 'not-so-secret')
|
|
||||||
data['message_signature'] = sig1
|
|
||||||
assert meter.verify_signature(data, 'not-so-secret')
|
|
||||||
|
|
||||||
|
|
||||||
def test_verify_signature_unsigned():
|
|
||||||
data = {'a': 'A', 'b': 'B'}
|
|
||||||
assert not meter.verify_signature(data, 'not-so-secret')
|
|
||||||
|
|
||||||
|
|
||||||
def test_verify_signature_incorrect():
|
|
||||||
data = {'a': 'A', 'b': 'B',
|
|
||||||
'message_signature': 'Not the same'}
|
|
||||||
assert not meter.verify_signature(data, 'not-so-secret')
|
|
||||||
|
|
||||||
|
|
||||||
def test_recursive_keypairs():
|
|
||||||
data = {'a': 'A',
|
|
||||||
'b': 'B',
|
|
||||||
'nested': {'a': 'A',
|
|
||||||
'b': 'B',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pairs = list(meter.recursive_keypairs(data))
|
|
||||||
assert pairs == [('a', 'A'),
|
|
||||||
('b', 'B'),
|
|
||||||
('nested:a', 'A'),
|
|
||||||
('nested:b', 'B'),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def test_verify_signature_nested():
|
|
||||||
data = {'a': 'A',
|
|
||||||
'b': 'B',
|
|
||||||
'nested': {'a': 'A',
|
|
||||||
'b': 'B',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
data['message_signature'] = meter.compute_signature(data, 'not-so-secret')
|
|
||||||
assert meter.verify_signature(data, 'not-so-secret')
|
|
||||||
|
|
||||||
|
|
||||||
def test_verify_signature_nested_json():
|
|
||||||
data = {'a': 'A',
|
|
||||||
'b': 'B',
|
|
||||||
'nested': {'a': 'A',
|
|
||||||
'b': 'B',
|
|
||||||
'c': ('c',),
|
|
||||||
'd': ['d']
|
|
||||||
},
|
|
||||||
}
|
|
||||||
data['message_signature'] = meter.compute_signature(data, 'not-so-secret')
|
|
||||||
jsondata = jsonutils.loads(jsonutils.dumps(data))
|
|
||||||
assert meter.verify_signature(jsondata, 'not-so-secret')
|
|
||||||
|
|
||||||
|
|
||||||
TEST_COUNTER = counter.Counter(name='name',
|
|
||||||
type='typ',
|
|
||||||
unit='',
|
|
||||||
volume=1,
|
|
||||||
user_id='user',
|
|
||||||
project_id='project',
|
|
||||||
resource_id=2,
|
|
||||||
timestamp='today',
|
|
||||||
resource_metadata={'key': 'value'},
|
|
||||||
)
|
|
||||||
|
|
||||||
TEST_NOTICE = {
|
|
||||||
u'_context_auth_token': u'3d8b13de1b7d499587dfc69b77dc09c2',
|
|
||||||
u'_context_is_admin': True,
|
|
||||||
u'_context_project_id': u'7c150a59fe714e6f9263774af9688f0e',
|
|
||||||
u'_context_quota_class': None,
|
|
||||||
u'_context_read_deleted': u'no',
|
|
||||||
u'_context_remote_address': u'10.0.2.15',
|
|
||||||
u'_context_request_id': u'req-d68b36e0-9233-467f-9afb-d81435d64d66',
|
|
||||||
u'_context_roles': [u'admin'],
|
|
||||||
u'_context_timestamp': u'2012-05-08T20:23:41.425105',
|
|
||||||
u'_context_user_id': u'1e3ce043029547f1a61c1996d1a531a2',
|
|
||||||
u'event_type': u'compute.instance.create.end',
|
|
||||||
u'message_id': u'dae6f69c-00e0-41c0-b371-41ec3b7f4451',
|
|
||||||
u'payload': {u'created_at': u'2012-05-08 20:23:41',
|
|
||||||
u'deleted_at': u'',
|
|
||||||
u'disk_gb': 0,
|
|
||||||
u'display_name': u'testme',
|
|
||||||
u'fixed_ips': [{u'address': u'10.0.0.2',
|
|
||||||
u'floating_ips': [],
|
|
||||||
u'meta': {},
|
|
||||||
u'type': u'fixed',
|
|
||||||
u'version': 4}],
|
|
||||||
u'image_ref_url': u'http://10.0.2.15:9292/images/UUID',
|
|
||||||
u'instance_id': u'9f9d01b9-4a58-4271-9e27-398b21ab20d1',
|
|
||||||
u'instance_type': u'm1.tiny',
|
|
||||||
u'instance_type_id': 2,
|
|
||||||
u'launched_at': u'2012-05-08 20:23:47.985999',
|
|
||||||
u'memory_mb': 512,
|
|
||||||
u'state': u'active',
|
|
||||||
u'state_description': u'',
|
|
||||||
u'tenant_id': u'7c150a59fe714e6f9263774af9688f0e',
|
|
||||||
u'user_id': u'1e3ce043029547f1a61c1996d1a531a2'},
|
|
||||||
u'priority': u'INFO',
|
|
||||||
u'publisher_id': u'compute.vagrant-precise',
|
|
||||||
u'timestamp': u'2012-05-08 20:23:48.028195',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_meter_message_from_counter_signed():
|
|
||||||
msg = meter.meter_message_from_counter(TEST_COUNTER, 'not-so-secret',
|
|
||||||
'src')
|
|
||||||
assert 'message_signature' in msg
|
|
||||||
|
|
||||||
|
|
||||||
def test_meter_message_from_counter_field():
|
|
||||||
def compare(f, c, msg_f, msg):
|
|
||||||
assert msg == c
|
|
||||||
msg = meter.meter_message_from_counter(TEST_COUNTER, 'not-so-secret',
|
|
||||||
'src')
|
|
||||||
name_map = {'name': 'counter_name',
|
|
||||||
'type': 'counter_type',
|
|
||||||
'unit': 'counter_unit',
|
|
||||||
'volume': 'counter_volume',
|
|
||||||
}
|
|
||||||
for f in TEST_COUNTER._fields:
|
|
||||||
msg_f = name_map.get(f, f)
|
|
||||||
yield compare, f, getattr(TEST_COUNTER, f), msg_f, msg[msg_f]
|
|
@ -22,11 +22,185 @@ import datetime
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from ceilometer import counter
|
||||||
|
from ceilometer.openstack.common import jsonutils
|
||||||
from ceilometer.openstack.common import rpc
|
from ceilometer.openstack.common import rpc
|
||||||
|
from ceilometer.publisher import meter
|
||||||
from ceilometer.tests import base
|
from ceilometer.tests import base
|
||||||
|
|
||||||
from ceilometer import counter
|
|
||||||
from ceilometer.publisher import meter_publish
|
def test_compute_signature_change_key():
|
||||||
|
sig1 = meter.compute_signature({'a': 'A', 'b': 'B'},
|
||||||
|
'not-so-secret')
|
||||||
|
sig2 = meter.compute_signature({'A': 'A', 'b': 'B'},
|
||||||
|
'not-so-secret')
|
||||||
|
assert sig1 != sig2
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_signature_change_value():
|
||||||
|
sig1 = meter.compute_signature({'a': 'A', 'b': 'B'},
|
||||||
|
'not-so-secret')
|
||||||
|
sig2 = meter.compute_signature({'a': 'a', 'b': 'B'},
|
||||||
|
'not-so-secret')
|
||||||
|
assert sig1 != sig2
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_signature_same():
|
||||||
|
sig1 = meter.compute_signature({'a': 'A', 'b': 'B'},
|
||||||
|
'not-so-secret')
|
||||||
|
sig2 = meter.compute_signature({'a': 'A', 'b': 'B'},
|
||||||
|
'not-so-secret')
|
||||||
|
assert sig1 == sig2
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_signature_signed():
|
||||||
|
data = {'a': 'A', 'b': 'B'}
|
||||||
|
sig1 = meter.compute_signature(data, 'not-so-secret')
|
||||||
|
data['message_signature'] = sig1
|
||||||
|
sig2 = meter.compute_signature(data, 'not-so-secret')
|
||||||
|
assert sig1 == sig2
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_signature_use_configured_secret():
|
||||||
|
data = {'a': 'A', 'b': 'B'}
|
||||||
|
sig1 = meter.compute_signature(data, 'not-so-secret')
|
||||||
|
sig2 = meter.compute_signature(data, 'different-value')
|
||||||
|
assert sig1 != sig2
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_signature_signed():
|
||||||
|
data = {'a': 'A', 'b': 'B'}
|
||||||
|
sig1 = meter.compute_signature(data, 'not-so-secret')
|
||||||
|
data['message_signature'] = sig1
|
||||||
|
assert meter.verify_signature(data, 'not-so-secret')
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_signature_unsigned():
|
||||||
|
data = {'a': 'A', 'b': 'B'}
|
||||||
|
assert not meter.verify_signature(data, 'not-so-secret')
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_signature_incorrect():
|
||||||
|
data = {'a': 'A', 'b': 'B',
|
||||||
|
'message_signature': 'Not the same'}
|
||||||
|
assert not meter.verify_signature(data, 'not-so-secret')
|
||||||
|
|
||||||
|
|
||||||
|
def test_recursive_keypairs():
|
||||||
|
data = {'a': 'A',
|
||||||
|
'b': 'B',
|
||||||
|
'nested': {'a': 'A',
|
||||||
|
'b': 'B',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pairs = list(meter.recursive_keypairs(data))
|
||||||
|
assert pairs == [('a', 'A'),
|
||||||
|
('b', 'B'),
|
||||||
|
('nested:a', 'A'),
|
||||||
|
('nested:b', 'B'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_signature_nested():
|
||||||
|
data = {'a': 'A',
|
||||||
|
'b': 'B',
|
||||||
|
'nested': {'a': 'A',
|
||||||
|
'b': 'B',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data['message_signature'] = meter.compute_signature(
|
||||||
|
data,
|
||||||
|
'not-so-secret')
|
||||||
|
assert meter.verify_signature(data, 'not-so-secret')
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_signature_nested_json():
|
||||||
|
data = {'a': 'A',
|
||||||
|
'b': 'B',
|
||||||
|
'nested': {'a': 'A',
|
||||||
|
'b': 'B',
|
||||||
|
'c': ('c',),
|
||||||
|
'd': ['d']
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data['message_signature'] = meter.compute_signature(
|
||||||
|
data,
|
||||||
|
'not-so-secret')
|
||||||
|
jsondata = jsonutils.loads(jsonutils.dumps(data))
|
||||||
|
assert meter.verify_signature(jsondata, 'not-so-secret')
|
||||||
|
|
||||||
|
|
||||||
|
TEST_COUNTER = counter.Counter(name='name',
|
||||||
|
type='typ',
|
||||||
|
unit='',
|
||||||
|
volume=1,
|
||||||
|
user_id='user',
|
||||||
|
project_id='project',
|
||||||
|
resource_id=2,
|
||||||
|
timestamp='today',
|
||||||
|
resource_metadata={'key': 'value'},
|
||||||
|
)
|
||||||
|
|
||||||
|
TEST_NOTICE = {
|
||||||
|
u'_context_auth_token': u'3d8b13de1b7d499587dfc69b77dc09c2',
|
||||||
|
u'_context_is_admin': True,
|
||||||
|
u'_context_project_id': u'7c150a59fe714e6f9263774af9688f0e',
|
||||||
|
u'_context_quota_class': None,
|
||||||
|
u'_context_read_deleted': u'no',
|
||||||
|
u'_context_remote_address': u'10.0.2.15',
|
||||||
|
u'_context_request_id': u'req-d68b36e0-9233-467f-9afb-d81435d64d66',
|
||||||
|
u'_context_roles': [u'admin'],
|
||||||
|
u'_context_timestamp': u'2012-05-08T20:23:41.425105',
|
||||||
|
u'_context_user_id': u'1e3ce043029547f1a61c1996d1a531a2',
|
||||||
|
u'event_type': u'compute.instance.create.end',
|
||||||
|
u'message_id': u'dae6f69c-00e0-41c0-b371-41ec3b7f4451',
|
||||||
|
u'payload': {u'created_at': u'2012-05-08 20:23:41',
|
||||||
|
u'deleted_at': u'',
|
||||||
|
u'disk_gb': 0,
|
||||||
|
u'display_name': u'testme',
|
||||||
|
u'fixed_ips': [{u'address': u'10.0.0.2',
|
||||||
|
u'floating_ips': [],
|
||||||
|
u'meta': {},
|
||||||
|
u'type': u'fixed',
|
||||||
|
u'version': 4}],
|
||||||
|
u'image_ref_url': u'http://10.0.2.15:9292/images/UUID',
|
||||||
|
u'instance_id': u'9f9d01b9-4a58-4271-9e27-398b21ab20d1',
|
||||||
|
u'instance_type': u'm1.tiny',
|
||||||
|
u'instance_type_id': 2,
|
||||||
|
u'launched_at': u'2012-05-08 20:23:47.985999',
|
||||||
|
u'memory_mb': 512,
|
||||||
|
u'state': u'active',
|
||||||
|
u'state_description': u'',
|
||||||
|
u'tenant_id': u'7c150a59fe714e6f9263774af9688f0e',
|
||||||
|
u'user_id': u'1e3ce043029547f1a61c1996d1a531a2'},
|
||||||
|
u'priority': u'INFO',
|
||||||
|
u'publisher_id': u'compute.vagrant-precise',
|
||||||
|
u'timestamp': u'2012-05-08 20:23:48.028195',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_meter_message_from_counter_signed():
|
||||||
|
msg = meter.meter_message_from_counter(
|
||||||
|
TEST_COUNTER,
|
||||||
|
'not-so-secret',
|
||||||
|
'src')
|
||||||
|
assert 'message_signature' in msg
|
||||||
|
|
||||||
|
|
||||||
|
def test_meter_message_from_counter_field():
|
||||||
|
def compare(f, c, msg_f, msg):
|
||||||
|
assert msg == c
|
||||||
|
msg = meter.meter_message_from_counter(
|
||||||
|
TEST_COUNTER, 'not-so-secret',
|
||||||
|
'src')
|
||||||
|
name_map = {'name': 'counter_name',
|
||||||
|
'type': 'counter_type',
|
||||||
|
'unit': 'counter_unit',
|
||||||
|
'volume': 'counter_volume',
|
||||||
|
}
|
||||||
|
for f in TEST_COUNTER._fields:
|
||||||
|
msg_f = name_map.get(f, f)
|
||||||
|
yield compare, f, getattr(TEST_COUNTER, f), msg_f, msg[msg_f]
|
||||||
|
|
||||||
|
|
||||||
class TestPublish(base.TestCase):
|
class TestPublish(base.TestCase):
|
||||||
@ -96,7 +270,7 @@ class TestPublish(base.TestCase):
|
|||||||
super(TestPublish, self).setUp()
|
super(TestPublish, self).setUp()
|
||||||
self.published = []
|
self.published = []
|
||||||
self.stubs.Set(rpc, 'cast', self.faux_cast)
|
self.stubs.Set(rpc, 'cast', self.faux_cast)
|
||||||
publisher = meter_publish.MeterPublisher()
|
publisher = meter.MeterPublisher()
|
||||||
publisher.publish_counters(None,
|
publisher.publish_counters(None,
|
||||||
self.test_data,
|
self.test_data,
|
||||||
'test')
|
'test')
|
||||||
@ -106,7 +280,7 @@ class TestPublish(base.TestCase):
|
|||||||
for topic, rpc_call in self.published:
|
for topic, rpc_call in self.published:
|
||||||
meters = rpc_call['args']['data']
|
meters = rpc_call['args']['data']
|
||||||
self.assertIsInstance(meters, list)
|
self.assertIsInstance(meters, list)
|
||||||
if topic != cfg.CONF.metering_topic:
|
if topic != cfg.CONF.publisher_meter.metering_topic:
|
||||||
self.assertEqual(len(set(meter['counter_name']
|
self.assertEqual(len(set(meter['counter_name']
|
||||||
for meter in meters)),
|
for meter in meters)),
|
||||||
1,
|
1,
|
||||||
@ -114,7 +288,10 @@ class TestPublish(base.TestCase):
|
|||||||
|
|
||||||
def test_published_topics(self):
|
def test_published_topics(self):
|
||||||
topics = [topic for topic, meter in self.published]
|
topics = [topic for topic, meter in self.published]
|
||||||
self.assertIn(cfg.CONF.metering_topic, topics)
|
self.assertIn(cfg.CONF.publisher_meter.metering_topic, topics)
|
||||||
self.assertIn(cfg.CONF.metering_topic + '.' + 'test', topics)
|
self.assertIn(
|
||||||
self.assertIn(cfg.CONF.metering_topic + '.' + 'test2', topics)
|
cfg.CONF.publisher_meter.metering_topic + '.' + 'test', topics)
|
||||||
self.assertIn(cfg.CONF.metering_topic + '.' + 'test3', topics)
|
self.assertIn(
|
||||||
|
cfg.CONF.publisher_meter.metering_topic + '.' + 'test2', topics)
|
||||||
|
self.assertIn(
|
||||||
|
cfg.CONF.publisher_meter.metering_topic + '.' + 'test3', topics)
|
||||||
|
@ -25,7 +25,7 @@ import datetime
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
from ceilometer import storage
|
from ceilometer import storage
|
||||||
from ceilometer.tests import db as test_db
|
from ceilometer.tests import db as test_db
|
||||||
@ -54,8 +54,9 @@ class DBTestBase(test_db.TestBase):
|
|||||||
'tag': 'self.counter',
|
'tag': 'self.counter',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.msg1 = meter.meter_message_from_counter(self.counter,
|
self.msg1 = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
self.counter,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test-1',
|
'test-1',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(self.msg1)
|
self.conn.record_metering_data(self.msg1)
|
||||||
@ -74,8 +75,9 @@ class DBTestBase(test_db.TestBase):
|
|||||||
'tag': 'self.counter2',
|
'tag': 'self.counter2',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.msg2 = meter.meter_message_from_counter(self.counter2,
|
self.msg2 = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
self.counter2,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test-2',
|
'test-2',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(self.msg2)
|
self.conn.record_metering_data(self.msg2)
|
||||||
@ -94,8 +96,9 @@ class DBTestBase(test_db.TestBase):
|
|||||||
'tag': 'self.counter3',
|
'tag': 'self.counter3',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.msg3 = meter.meter_message_from_counter(self.counter3,
|
self.msg3 = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
self.counter3,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test-3',
|
'test-3',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(self.msg3)
|
self.conn.record_metering_data(self.msg3)
|
||||||
@ -114,8 +117,11 @@ class DBTestBase(test_db.TestBase):
|
|||||||
resource_metadata={'display_name': 'test-server',
|
resource_metadata={'display_name': 'test-server',
|
||||||
'tag': 'counter-%s' % i},
|
'tag': 'counter-%s' % i},
|
||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(c, cfg.CONF.metering_secret,
|
msg = meter.meter_message_from_counter(
|
||||||
'test')
|
c,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
|
'test',
|
||||||
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
self.msgs.append(msg)
|
self.msgs.append(msg)
|
||||||
|
|
||||||
@ -359,7 +365,8 @@ class StatisticsTest(DBTestBase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.counters.append(c)
|
self.counters.append(c)
|
||||||
msg = meter.meter_message_from_counter(c,
|
msg = meter.meter_message_from_counter(
|
||||||
|
c,
|
||||||
secret='not-so-secret',
|
secret='not-so-secret',
|
||||||
source='test',
|
source='test',
|
||||||
)
|
)
|
||||||
@ -379,7 +386,8 @@ class StatisticsTest(DBTestBase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.counters.append(c)
|
self.counters.append(c)
|
||||||
msg = meter.meter_message_from_counter(c,
|
msg = meter.meter_message_from_counter(
|
||||||
|
c,
|
||||||
secret='not-so-secret',
|
secret='not-so-secret',
|
||||||
source='test',
|
source='test',
|
||||||
)
|
)
|
||||||
@ -522,7 +530,7 @@ class CounterDataTypeTest(DBTestBase):
|
|||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(
|
msg = meter.meter_message_from_counter(
|
||||||
c,
|
c,
|
||||||
cfg.CONF.metering_secret,
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test-1',
|
'test-1',
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -541,7 +549,7 @@ class CounterDataTypeTest(DBTestBase):
|
|||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(
|
msg = meter.meter_message_from_counter(
|
||||||
c,
|
c,
|
||||||
cfg.CONF.metering_secret,
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test-1',
|
'test-1',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
@ -559,7 +567,7 @@ class CounterDataTypeTest(DBTestBase):
|
|||||||
)
|
)
|
||||||
msg = meter.meter_message_from_counter(
|
msg = meter.meter_message_from_counter(
|
||||||
c,
|
c,
|
||||||
cfg.CONF.metering_secret,
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'test-1',
|
'test-1',
|
||||||
)
|
)
|
||||||
self.conn.record_metering_data(msg)
|
self.conn.record_metering_data(msg)
|
||||||
|
@ -51,7 +51,7 @@ import datetime
|
|||||||
|
|
||||||
from tests.storage import base
|
from tests.storage import base
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
from ceilometer.storage.impl_mongodb import require_map_reduce
|
from ceilometer.storage.impl_mongodb import require_map_reduce
|
||||||
|
|
||||||
@ -158,7 +158,8 @@ class CompatibilityTest(MongoDBEngineTestBase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.counters.append(c)
|
self.counters.append(c)
|
||||||
msg = meter.meter_message_from_counter(c,
|
msg = meter.meter_message_from_counter(
|
||||||
|
c,
|
||||||
secret='not-so-secret',
|
secret='not-so-secret',
|
||||||
source='test')
|
source='test')
|
||||||
self.conn.record_metering_data(self.conn, msg)
|
self.conn.record_metering_data(self.conn, msg)
|
||||||
|
@ -27,7 +27,7 @@ import sys
|
|||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.collector import meter
|
from ceilometer.publisher import meter
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
from ceilometer import storage
|
from ceilometer import storage
|
||||||
from ceilometer.openstack.common import timeutils
|
from ceilometer.openstack.common import timeutils
|
||||||
@ -129,8 +129,9 @@ def main():
|
|||||||
timestamp=timestamp,
|
timestamp=timestamp,
|
||||||
resource_metadata={},
|
resource_metadata={},
|
||||||
)
|
)
|
||||||
data = meter.meter_message_from_counter(c,
|
data = meter.meter_message_from_counter(
|
||||||
cfg.CONF.metering_secret,
|
c,
|
||||||
|
cfg.CONF.publisher_meter.metering_secret,
|
||||||
'artificial')
|
'artificial')
|
||||||
conn.record_metering_data(data)
|
conn.record_metering_data(data)
|
||||||
n += 1
|
n += 1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user