aodh/ceilometer/utils.py
Eoghan Glynn e9cde137b0 Avoid false negatives on message signature comparison
Fixes bug 1262255

Previously, samples for the 'instance.scheduled' meter were always
rejected with a false negative on the message signature verification.

The problem occured because certain resource metadata (such as the
block_device_mapping) in the 'scheduler.run_instance.scheduled'
notification are realized as a list of dict, and are thus similarly
encoded in the corresponding sample payload.

However the message signature computation then became indeterminate,
as the unicode representation of equivalent dicts is not guaranteed
equal (e.g. depends on insertion order in the case of hash collisions).

Now, we avoid false negatives by explicitly re-ordering insertion
into such dicts.

Change-Id: I77f7d89229518cf040608d7eb3307e2257bce07a
2014-01-06 14:34:22 +00:00

119 lines
4.1 KiB
Python

# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2011 Justin Santa Barbara
# 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.
"""Utilities and helper functions."""
import calendar
import datetime
import decimal
from ceilometer.openstack.common import timeutils
def recursive_keypairs(d, separator=':'):
"""Generator that produces sequence of keypairs for nested dictionaries.
"""
for name, value in sorted(d.iteritems()):
if isinstance(value, dict):
for subname, subvalue in recursive_keypairs(value):
yield ('%s%s%s' % (name, separator, subname), subvalue)
elif isinstance(value, (tuple, list)):
# When doing a pair of JSON encode/decode operations to the tuple,
# the tuple would become list. So we have to generate the value as
# list here.
# in the special case of the list item itself being a dict,
# create an equivalent dict with a predictable insertion order
# to avoid inconsistencies in the message signature computation
# for equivalent payloads modulo ordering
first = lambda i: i[0]
m = map(lambda x: unicode(dict(sorted(x.items(), key=first))
if isinstance(x, dict)
else x).encode('utf-8'),
value)
yield name, list(m)
else:
yield name, value
def dt_to_decimal(utc):
"""Datetime to Decimal.
Some databases don't store microseconds in datetime
so we always store as Decimal unixtime.
"""
if utc is None:
return None
decimal.getcontext().prec = 30
return decimal.Decimal(str(calendar.timegm(utc.utctimetuple()))) + \
(decimal.Decimal(str(utc.microsecond)) /
decimal.Decimal("1000000.0"))
def decimal_to_dt(dec):
"""Return a datetime from Decimal unixtime format.
"""
if dec is None:
return None
integer = int(dec)
micro = (dec - decimal.Decimal(integer)) * decimal.Decimal(1000000)
daittyme = datetime.datetime.utcfromtimestamp(integer)
return daittyme.replace(microsecond=int(round(micro)))
def sanitize_timestamp(timestamp):
"""Return a naive utc datetime object."""
if not timestamp:
return timestamp
if not isinstance(timestamp, datetime.datetime):
timestamp = timeutils.parse_isotime(timestamp)
return timeutils.normalize_time(timestamp)
def stringify_timestamps(data):
"""Stringify any datetimes in given dict."""
isa_timestamp = lambda v: isinstance(v, datetime.datetime)
return dict((k, v.isoformat() if isa_timestamp(v) else v)
for (k, v) in data.iteritems())
def dict_to_keyval(value, key_base=None):
"""Expand a given dict to its corresponding key-value pairs.
Generated keys are fully qualified, delimited using dot notation.
ie. key = 'key.child_key.grandchild_key[0]'
"""
val_iter, key_func = None, None
if isinstance(value, dict):
val_iter = value.iteritems()
key_func = lambda k: key_base + '.' + k if key_base else k
elif isinstance(value, (tuple, list)):
val_iter = enumerate(value)
key_func = lambda k: key_base + '[%d]' % k
if val_iter:
for k, v in val_iter:
key_gen = key_func(k)
if isinstance(v, dict) or isinstance(v, (tuple, list)):
for key_gen, v in dict_to_keyval(v, key_gen):
yield key_gen, v
else:
yield key_gen, v