diff options
| author | Bruce Ashfield <bruce.ashfield@windriver.com> | 2014-01-08 00:50:15 -0500 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@windriver.com> | 2014-01-15 00:33:53 -0500 |
| commit | b45880a1a11007476446c7e2f53b0fee43c43453 (patch) | |
| tree | 160739eceae26c926c0b93ebaf5a039788f025ee /meta-openstack/recipes-devtools/python | |
| parent | 9ee16ea7922a1777e95cd5ecdf577c6d5935bbd2 (diff) | |
| download | meta-cloud-services-b45880a1a11007476446c7e2f53b0fee43c43453.tar.gz | |
ceilometer: postgresql fixes
Cherry picking two ceilometer master changes to address postgresql database
issues:
https://bugs.launchpad.net/ceilometer/+bug/1241526
https://review.openstack.org/#/c/49456/
Signed-off-by: Bruce Ashfield <bruce.ashfield@windriver.com>
Diffstat (limited to 'meta-openstack/recipes-devtools/python')
3 files changed, 457 insertions, 0 deletions
diff --git a/meta-openstack/recipes-devtools/python/python-ceilometer/0001-Fix-for-get_resources-with-postgresql.patch b/meta-openstack/recipes-devtools/python/python-ceilometer/0001-Fix-for-get_resources-with-postgresql.patch new file mode 100644 index 0000000..52cffb3 --- /dev/null +++ b/meta-openstack/recipes-devtools/python/python-ceilometer/0001-Fix-for-get_resources-with-postgresql.patch | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | From f28a381b58516018ca35cdf7b4e2879a5bcac6ad Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Thomas Maddox <thomas.maddox@rackspace.com> | ||
| 3 | Date: Mon, 21 Oct 2013 15:55:49 +0000 | ||
| 4 | Subject: [PATCH 1/2] Fix for get_resources with postgresql | ||
| 5 | |||
| 6 | Add max_ts and min_ts to GROUP BY in sub-query, since they need to be aggregated to SELECT them. | ||
| 7 | |||
| 8 | Closes-Bug: #1241526 | ||
| 9 | Change-Id: Ifdd2bc661b5da31bd40d1c3fa1fc442d7417399f | ||
| 10 | (cherry picked from commit 0a98159bc9c727a89bd8c15347ac380a21acaa59) | ||
| 11 | |||
| 12 | Signed-off-by: Bruce Ashfield <bruce.ashfield@windriver.com> | ||
| 13 | --- | ||
| 14 | ceilometer/storage/impl_sqlalchemy.py | 6 +++++- | ||
| 15 | 1 file changed, 5 insertions(+), 1 deletion(-) | ||
| 16 | |||
| 17 | diff --git a/ceilometer/storage/impl_sqlalchemy.py b/ceilometer/storage/impl_sqlalchemy.py | ||
| 18 | index a6e7d307e407..546c0c0e6553 100644 | ||
| 19 | --- a/ceilometer/storage/impl_sqlalchemy.py | ||
| 20 | +++ b/ceilometer/storage/impl_sqlalchemy.py | ||
| 21 | @@ -361,7 +361,11 @@ class Connection(base.Connection): | ||
| 22 | ).filter( | ||
| 23 | Meter.resource_id == ts_subquery.c.resource_id, | ||
| 24 | Meter.timestamp == ts_subquery.c.max_ts | ||
| 25 | - ).group_by(Meter.resource_id).subquery() | ||
| 26 | + ).group_by( | ||
| 27 | + ts_subquery.c.resource_id, | ||
| 28 | + ts_subquery.c.max_ts, | ||
| 29 | + ts_subquery.c.min_ts | ||
| 30 | + ).subquery() | ||
| 31 | |||
| 32 | query = session.query( | ||
| 33 | Meter, | ||
| 34 | -- | ||
| 35 | 1.7.10.4 | ||
| 36 | |||
diff --git a/meta-openstack/recipes-devtools/python/python-ceilometer/0002-enable-sql-metadata-query.patch b/meta-openstack/recipes-devtools/python/python-ceilometer/0002-enable-sql-metadata-query.patch new file mode 100644 index 0000000..c96c631 --- /dev/null +++ b/meta-openstack/recipes-devtools/python/python-ceilometer/0002-enable-sql-metadata-query.patch | |||
| @@ -0,0 +1,419 @@ | |||
| 1 | From 35488d1099c634d88d7e6c262eb9a6636ee2d7d8 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Gordon Chung <chungg@ca.ibm.com> | ||
| 3 | Date: Wed, 2 Oct 2013 15:45:26 -0400 | ||
| 4 | Subject: [PATCH 2/2] enable sql metadata query | ||
| 5 | |||
| 6 | explode metadata key/values to their own tables/rows (based on type). | ||
| 7 | build a key string using dot notation similar to other nosql db | ||
| 8 | and filter based on that. | ||
| 9 | |||
| 10 | Blueprint: sqlalchemy-metadata-query | ||
| 11 | Related-Bug: #1093625 | ||
| 12 | |||
| 13 | Change-Id: I2076e67b79448f98124a57b62b5bfed7aa8ae2ad | ||
| 14 | (cherry picked from commit 1570462507eae1478123de25dbadc64b09c82af3) | ||
| 15 | |||
| 16 | Signed-off-by: Bruce Ashfield <bruce.ashfield@windriver.com> | ||
| 17 | --- | ||
| 18 | ceilometer/storage/impl_sqlalchemy.py | 79 +++++++++++++++++--- | ||
| 19 | .../versions/020_add_metadata_tables.py | 78 +++++++++++++++++++ | ||
| 20 | ceilometer/storage/sqlalchemy/models.py | 48 ++++++++++++ | ||
| 21 | ceilometer/utils.py | 24 ++++++ | ||
| 22 | doc/source/install/dbreco.rst | 4 +- | ||
| 23 | tests/api/v2/test_list_meters_scenarios.py | 1 + | ||
| 24 | tests/test_utils.py | 16 ++++ | ||
| 25 | 7 files changed, 239 insertions(+), 11 deletions(-) | ||
| 26 | create mode 100644 ceilometer/storage/sqlalchemy/migrate_repo/versions/020_add_metadata_tables.py | ||
| 27 | |||
| 28 | diff --git a/ceilometer/storage/impl_sqlalchemy.py b/ceilometer/storage/impl_sqlalchemy.py | ||
| 29 | index 546c0c0e6553..8d321eaaeffe 100644 | ||
| 30 | --- a/ceilometer/storage/impl_sqlalchemy.py | ||
| 31 | +++ b/ceilometer/storage/impl_sqlalchemy.py | ||
| 32 | @@ -18,10 +18,12 @@ | ||
| 33 | """SQLAlchemy storage backend.""" | ||
| 34 | |||
| 35 | from __future__ import absolute_import | ||
| 36 | - | ||
| 37 | import datetime | ||
| 38 | import operator | ||
| 39 | import os | ||
| 40 | +import types | ||
| 41 | + | ||
| 42 | +from sqlalchemy import and_ | ||
| 43 | from sqlalchemy import func | ||
| 44 | from sqlalchemy import desc | ||
| 45 | from sqlalchemy.orm import aliased | ||
| 46 | @@ -39,6 +41,10 @@ from ceilometer.storage.sqlalchemy.models import AlarmChange | ||
| 47 | from ceilometer.storage.sqlalchemy.models import Base | ||
| 48 | from ceilometer.storage.sqlalchemy.models import Event | ||
| 49 | from ceilometer.storage.sqlalchemy.models import Meter | ||
| 50 | +from ceilometer.storage.sqlalchemy.models import MetaBool | ||
| 51 | +from ceilometer.storage.sqlalchemy.models import MetaFloat | ||
| 52 | +from ceilometer.storage.sqlalchemy.models import MetaInt | ||
| 53 | +from ceilometer.storage.sqlalchemy.models import MetaText | ||
| 54 | from ceilometer.storage.sqlalchemy.models import Project | ||
| 55 | from ceilometer.storage.sqlalchemy.models import Resource | ||
| 56 | from ceilometer.storage.sqlalchemy.models import Source | ||
| 57 | @@ -100,7 +106,40 @@ class SQLAlchemyStorage(base.StorageEngine): | ||
| 58 | return Connection(conf) | ||
| 59 | |||
| 60 | |||
| 61 | -def make_query_from_filter(query, sample_filter, require_meter=True): | ||
| 62 | +META_TYPE_MAP = {bool: MetaBool, | ||
| 63 | + str: MetaText, | ||
| 64 | + unicode: MetaText, | ||
| 65 | + types.NoneType: MetaText, | ||
| 66 | + int: MetaInt, | ||
| 67 | + long: MetaInt, | ||
| 68 | + float: MetaFloat} | ||
| 69 | + | ||
| 70 | + | ||
| 71 | +def apply_metaquery_filter(session, query, metaquery): | ||
| 72 | + """Apply provided metaquery filter to existing query. | ||
| 73 | + | ||
| 74 | + :param session: session used for original query | ||
| 75 | + :param query: Query instance | ||
| 76 | + :param metaquery: dict with metadata to match on. | ||
| 77 | + """ | ||
| 78 | + | ||
| 79 | + for k, v in metaquery.iteritems(): | ||
| 80 | + key = k[9:] # strip out 'metadata.' prefix | ||
| 81 | + try: | ||
| 82 | + _model = META_TYPE_MAP[type(v)] | ||
| 83 | + except KeyError: | ||
| 84 | + raise NotImplementedError(_('Query on %(key)s is of %(value)s ' | ||
| 85 | + 'type and is not supported') % | ||
| 86 | + {"key": k, "value": type(v)}) | ||
| 87 | + else: | ||
| 88 | + meta_q = session.query(_model).\ | ||
| 89 | + filter(and_(_model.meta_key == key, | ||
| 90 | + _model.value == v)).subquery() | ||
| 91 | + query = query.filter_by(id=meta_q.c.id) | ||
| 92 | + return query | ||
| 93 | + | ||
| 94 | + | ||
| 95 | +def make_query_from_filter(session, query, sample_filter, require_meter=True): | ||
| 96 | """Return a query dictionary based on the settings in the filter. | ||
| 97 | |||
| 98 | :param filter: SampleFilter instance | ||
| 99 | @@ -134,7 +173,8 @@ def make_query_from_filter(query, sample_filter, require_meter=True): | ||
| 100 | query = query.filter_by(resource_id=sample_filter.resource) | ||
| 101 | |||
| 102 | if sample_filter.metaquery: | ||
| 103 | - raise NotImplementedError(_('metaquery not implemented')) | ||
| 104 | + query = apply_metaquery_filter(session, query, | ||
| 105 | + sample_filter.metaquery) | ||
| 106 | |||
| 107 | return query | ||
| 108 | |||
| 109 | @@ -229,6 +269,21 @@ class Connection(base.Connection): | ||
| 110 | meter.message_signature = data['message_signature'] | ||
| 111 | meter.message_id = data['message_id'] | ||
| 112 | |||
| 113 | + if rmetadata: | ||
| 114 | + if isinstance(rmetadata, dict): | ||
| 115 | + for key, v in utils.dict_to_keyval(rmetadata): | ||
| 116 | + try: | ||
| 117 | + _model = META_TYPE_MAP[type(v)] | ||
| 118 | + except KeyError: | ||
| 119 | + LOG.warn(_("Unknown metadata type. Key (%s) will " | ||
| 120 | + "not be queryable."), key) | ||
| 121 | + else: | ||
| 122 | + session.add(_model(id=meter.id, | ||
| 123 | + meta_key=key, | ||
| 124 | + value=v)) | ||
| 125 | + | ||
| 126 | + session.flush() | ||
| 127 | + | ||
| 128 | @staticmethod | ||
| 129 | def clear_expired_metering_data(ttl): | ||
| 130 | """Clear expired data from the backend storage system according to the | ||
| 131 | @@ -306,8 +361,6 @@ class Connection(base.Connection): | ||
| 132 | # just fail. | ||
| 133 | if pagination: | ||
| 134 | raise NotImplementedError(_('Pagination not implemented')) | ||
| 135 | - if metaquery: | ||
| 136 | - raise NotImplementedError(_('metaquery not implemented')) | ||
| 137 | |||
| 138 | # (thomasm) We need to get the max timestamp first, since that's the | ||
| 139 | # most accurate. We also need to filter down in the subquery to | ||
| 140 | @@ -331,6 +384,11 @@ class Connection(base.Connection): | ||
| 141 | ts_subquery = ts_subquery.filter( | ||
| 142 | Meter.sources.any(id=source)) | ||
| 143 | |||
| 144 | + if metaquery: | ||
| 145 | + ts_subquery = apply_metaquery_filter(session, | ||
| 146 | + ts_subquery, | ||
| 147 | + metaquery) | ||
| 148 | + | ||
| 149 | # Here we limit the samples being used to a specific time period, | ||
| 150 | # if requested. | ||
| 151 | if start_timestamp: | ||
| 152 | @@ -409,8 +467,6 @@ class Connection(base.Connection): | ||
| 153 | |||
| 154 | if pagination: | ||
| 155 | raise NotImplementedError(_('Pagination not implemented')) | ||
| 156 | - if metaquery: | ||
| 157 | - raise NotImplementedError(_('metaquery not implemented')) | ||
| 158 | |||
| 159 | session = sqlalchemy_session.get_session() | ||
| 160 | |||
| 161 | @@ -434,6 +490,11 @@ class Connection(base.Connection): | ||
| 162 | query_meter = session.query(Meter).\ | ||
| 163 | join(subquery_meter, Meter.id == subquery_meter.c.id) | ||
| 164 | |||
| 165 | + if metaquery: | ||
| 166 | + query_meter = apply_metaquery_filter(session, | ||
| 167 | + query_meter, | ||
| 168 | + metaquery) | ||
| 169 | + | ||
| 170 | alias_meter = aliased(Meter, query_meter.subquery()) | ||
| 171 | query = session.query(Resource, alias_meter).join( | ||
| 172 | alias_meter, Resource.id == alias_meter.resource_id) | ||
| 173 | @@ -469,7 +530,7 @@ class Connection(base.Connection): | ||
| 174 | |||
| 175 | session = sqlalchemy_session.get_session() | ||
| 176 | query = session.query(Meter) | ||
| 177 | - query = make_query_from_filter(query, sample_filter, | ||
| 178 | + query = make_query_from_filter(session, query, sample_filter, | ||
| 179 | require_meter=False) | ||
| 180 | if limit: | ||
| 181 | query = query.limit(limit) | ||
| 182 | @@ -521,7 +582,7 @@ class Connection(base.Connection): | ||
| 183 | if groupby: | ||
| 184 | query = query.group_by(*group_attributes) | ||
| 185 | |||
| 186 | - return make_query_from_filter(query, sample_filter) | ||
| 187 | + return make_query_from_filter(session, query, sample_filter) | ||
| 188 | |||
| 189 | @staticmethod | ||
| 190 | def _stats_result_to_model(result, period, period_start, | ||
| 191 | diff --git a/ceilometer/storage/sqlalchemy/migrate_repo/versions/020_add_metadata_tables.py b/ceilometer/storage/sqlalchemy/migrate_repo/versions/020_add_metadata_tables.py | ||
| 192 | new file mode 100644 | ||
| 193 | index 000000000000..085cd6b8f398 | ||
| 194 | --- /dev/null | ||
| 195 | +++ b/ceilometer/storage/sqlalchemy/migrate_repo/versions/020_add_metadata_tables.py | ||
| 196 | @@ -0,0 +1,78 @@ | ||
| 197 | +# | ||
| 198 | +# Copyright 2013 OpenStack Foundation | ||
| 199 | +# All Rights Reserved. | ||
| 200 | +# | ||
| 201 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
| 202 | +# not use this file except in compliance with the License. You may obtain | ||
| 203 | +# a copy of the License at | ||
| 204 | +# | ||
| 205 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
| 206 | +# | ||
| 207 | +# Unless required by applicable law or agreed to in writing, software | ||
| 208 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
| 209 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
| 210 | +# License for the specific language governing permissions and limitations | ||
| 211 | +# under the License. | ||
| 212 | +import json | ||
| 213 | + | ||
| 214 | +from sqlalchemy import Boolean | ||
| 215 | +from sqlalchemy import Column | ||
| 216 | +from sqlalchemy import Float | ||
| 217 | +from sqlalchemy import ForeignKey | ||
| 218 | +from sqlalchemy import Integer | ||
| 219 | +from sqlalchemy import MetaData | ||
| 220 | +from sqlalchemy import String | ||
| 221 | +from sqlalchemy import Table | ||
| 222 | +from sqlalchemy import Text | ||
| 223 | +from sqlalchemy.sql import select | ||
| 224 | + | ||
| 225 | +from ceilometer import utils | ||
| 226 | + | ||
| 227 | +tables = [('metadata_text', Text, True), | ||
| 228 | + ('metadata_bool', Boolean, False), | ||
| 229 | + ('metadata_int', Integer, False), | ||
| 230 | + ('metadata_float', Float, False)] | ||
| 231 | + | ||
| 232 | + | ||
| 233 | +def upgrade(migrate_engine): | ||
| 234 | + meta = MetaData(bind=migrate_engine) | ||
| 235 | + meter = Table('meter', meta, autoload=True) | ||
| 236 | + meta_tables = {} | ||
| 237 | + for t_name, t_type, t_nullable in tables: | ||
| 238 | + meta_tables[t_name] = Table( | ||
| 239 | + t_name, meta, | ||
| 240 | + Column('id', Integer, ForeignKey('meter.id'), primary_key=True), | ||
| 241 | + Column('meta_key', String(255), index=True, primary_key=True), | ||
| 242 | + Column('value', t_type, nullable=t_nullable), | ||
| 243 | + mysql_engine='InnoDB', | ||
| 244 | + mysql_charset='utf8', | ||
| 245 | + ) | ||
| 246 | + meta_tables[t_name].create() | ||
| 247 | + | ||
| 248 | + for row in select([meter]).execute(): | ||
| 249 | + meter_id = row['id'] | ||
| 250 | + rmeta = json.loads(row['resource_metadata']) | ||
| 251 | + for key, v in utils.dict_to_keyval(rmeta): | ||
| 252 | + if isinstance(v, basestring) or v is None: | ||
| 253 | + meta_tables['metadata_text'].insert().values(id=meter_id, | ||
| 254 | + meta_key=key, | ||
| 255 | + value=v) | ||
| 256 | + elif isinstance(v, bool): | ||
| 257 | + meta_tables['metadata_bool'].insert().values(id=meter_id, | ||
| 258 | + meta_key=key, | ||
| 259 | + value=v) | ||
| 260 | + elif isinstance(v, (int, long)): | ||
| 261 | + meta_tables['metadata_int'].insert().values(id=meter_id, | ||
| 262 | + meta_key=key, | ||
| 263 | + value=v) | ||
| 264 | + elif isinstance(v, float): | ||
| 265 | + meta_tables['metadata_float'].insert().values(id=meter_id, | ||
| 266 | + meta_key=key, | ||
| 267 | + value=v) | ||
| 268 | + | ||
| 269 | + | ||
| 270 | +def downgrade(migrate_engine): | ||
| 271 | + meta = MetaData(bind=migrate_engine) | ||
| 272 | + for t in tables: | ||
| 273 | + table = Table(t[0], meta, autoload=True) | ||
| 274 | + table.drop() | ||
| 275 | diff --git a/ceilometer/storage/sqlalchemy/models.py b/ceilometer/storage/sqlalchemy/models.py | ||
| 276 | index 45f98cb59553..8f890b3056d8 100644 | ||
| 277 | --- a/ceilometer/storage/sqlalchemy/models.py | ||
| 278 | +++ b/ceilometer/storage/sqlalchemy/models.py | ||
| 279 | @@ -141,6 +141,54 @@ class Source(Base): | ||
| 280 | id = Column(String(255), primary_key=True) | ||
| 281 | |||
| 282 | |||
| 283 | +class MetaText(Base): | ||
| 284 | + """Metering text metadata.""" | ||
| 285 | + | ||
| 286 | + __tablename__ = 'metadata_text' | ||
| 287 | + __table_args__ = ( | ||
| 288 | + Index('ix_meta_text_key', 'meta_key'), | ||
| 289 | + ) | ||
| 290 | + id = Column(Integer, ForeignKey('meter.id'), primary_key=True) | ||
| 291 | + meta_key = Column(String(255), primary_key=True) | ||
| 292 | + value = Column(Text) | ||
| 293 | + | ||
| 294 | + | ||
| 295 | +class MetaBool(Base): | ||
| 296 | + """Metering boolean metadata.""" | ||
| 297 | + | ||
| 298 | + __tablename__ = 'metadata_bool' | ||
| 299 | + __table_args__ = ( | ||
| 300 | + Index('ix_meta_bool_key', 'meta_key'), | ||
| 301 | + ) | ||
| 302 | + id = Column(Integer, ForeignKey('meter.id'), primary_key=True) | ||
| 303 | + meta_key = Column(String(255), primary_key=True) | ||
| 304 | + value = Column(Boolean) | ||
| 305 | + | ||
| 306 | + | ||
| 307 | +class MetaInt(Base): | ||
| 308 | + """Metering integer metadata.""" | ||
| 309 | + | ||
| 310 | + __tablename__ = 'metadata_int' | ||
| 311 | + __table_args__ = ( | ||
| 312 | + Index('ix_meta_int_key', 'meta_key'), | ||
| 313 | + ) | ||
| 314 | + id = Column(Integer, ForeignKey('meter.id'), primary_key=True) | ||
| 315 | + meta_key = Column(String(255), primary_key=True) | ||
| 316 | + value = Column(Integer, default=False) | ||
| 317 | + | ||
| 318 | + | ||
| 319 | +class MetaFloat(Base): | ||
| 320 | + """Metering float metadata.""" | ||
| 321 | + | ||
| 322 | + __tablename__ = 'metadata_float' | ||
| 323 | + __table_args__ = ( | ||
| 324 | + Index('ix_meta_float_key', 'meta_key'), | ||
| 325 | + ) | ||
| 326 | + id = Column(Integer, ForeignKey('meter.id'), primary_key=True) | ||
| 327 | + meta_key = Column(String(255), primary_key=True) | ||
| 328 | + value = Column(Float, default=False) | ||
| 329 | + | ||
| 330 | + | ||
| 331 | class Meter(Base): | ||
| 332 | """Metering data.""" | ||
| 333 | |||
| 334 | diff --git a/ceilometer/utils.py b/ceilometer/utils.py | ||
| 335 | index d5ca45f9654b..a00de72da8c5 100644 | ||
| 336 | --- a/ceilometer/utils.py | ||
| 337 | +++ b/ceilometer/utils.py | ||
| 338 | @@ -81,3 +81,27 @@ def stringify_timestamps(data): | ||
| 339 | isa_timestamp = lambda v: isinstance(v, datetime.datetime) | ||
| 340 | return dict((k, v.isoformat() if isa_timestamp(v) else v) | ||
| 341 | for (k, v) in data.iteritems()) | ||
| 342 | + | ||
| 343 | + | ||
| 344 | +def dict_to_keyval(value, key_base=None): | ||
| 345 | + """Expand a given dict to its corresponding key-value pairs. | ||
| 346 | + | ||
| 347 | + Generated keys are fully qualified, delimited using dot notation. | ||
| 348 | + ie. key = 'key.child_key.grandchild_key[0]' | ||
| 349 | + """ | ||
| 350 | + val_iter, key_func = None, None | ||
| 351 | + if isinstance(value, dict): | ||
| 352 | + val_iter = value.iteritems() | ||
| 353 | + key_func = lambda k: key_base + '.' + k if key_base else k | ||
| 354 | + elif isinstance(value, (tuple, list)): | ||
| 355 | + val_iter = enumerate(value) | ||
| 356 | + key_func = lambda k: key_base + '[%d]' % k | ||
| 357 | + | ||
| 358 | + if val_iter: | ||
| 359 | + for k, v in val_iter: | ||
| 360 | + key_gen = key_func(k) | ||
| 361 | + if isinstance(v, dict) or isinstance(v, (tuple, list)): | ||
| 362 | + for key_gen, v in dict_to_keyval(v, key_gen): | ||
| 363 | + yield key_gen, v | ||
| 364 | + else: | ||
| 365 | + yield key_gen, v | ||
| 366 | diff --git a/doc/source/install/dbreco.rst b/doc/source/install/dbreco.rst | ||
| 367 | index fe6032990ade..249cdc7d92c7 100644 | ||
| 368 | --- a/doc/source/install/dbreco.rst | ||
| 369 | +++ b/doc/source/install/dbreco.rst | ||
| 370 | @@ -43,8 +43,8 @@ The following is a table indicating the status of each database drivers: | ||
| 371 | Driver API querying API statistics Alarms | ||
| 372 | ================== ============================= =================== ====== | ||
| 373 | MongoDB Yes Yes Yes | ||
| 374 | -MySQL Yes, except metadata querying Yes Yes | ||
| 375 | -PostgreSQL Yes, except metadata querying Yes Yes | ||
| 376 | +MySQL Yes Yes Yes | ||
| 377 | +PostgreSQL Yes Yes Yes | ||
| 378 | HBase Yes Yes, except groupby No | ||
| 379 | DB2 Yes Yes No | ||
| 380 | ================== ============================= =================== ====== | ||
| 381 | diff --git a/tests/api/v2/test_list_meters_scenarios.py b/tests/api/v2/test_list_meters_scenarios.py | ||
| 382 | index fe2c5b78db8f..3381e15dadc2 100644 | ||
| 383 | --- a/tests/api/v2/test_list_meters_scenarios.py | ||
| 384 | +++ b/tests/api/v2/test_list_meters_scenarios.py | ||
| 385 | @@ -252,6 +252,7 @@ class TestListMeters(FunctionalTest, | ||
| 386 | set(['meter.mine'])) | ||
| 387 | self.assertEqual(set(r['resource_metadata']['is_public'] for r | ||
| 388 | in data), set(['False'])) | ||
| 389 | + # FIXME(gordc): verify no false positive (Bug#1236496) | ||
| 390 | |||
| 391 | def test_list_meters_query_string_metadata(self): | ||
| 392 | data = self.get_json('/meters/meter.test', | ||
| 393 | diff --git a/tests/test_utils.py b/tests/test_utils.py | ||
| 394 | index e5185abb11b0..a14c657554f0 100644 | ||
| 395 | --- a/tests/test_utils.py | ||
| 396 | +++ b/tests/test_utils.py | ||
| 397 | @@ -70,3 +70,19 @@ class TestUtils(tests_base.TestCase): | ||
| 398 | |||
| 399 | def test_decimal_to_dt_with_none_parameter(self): | ||
| 400 | self.assertEqual(utils.decimal_to_dt(None), None) | ||
| 401 | + | ||
| 402 | + def test_dict_to_kv(self): | ||
| 403 | + data = {'a': 'A', | ||
| 404 | + 'b': 'B', | ||
| 405 | + 'nested': {'a': 'A', | ||
| 406 | + 'b': 'B', | ||
| 407 | + }, | ||
| 408 | + 'nested2': [{'c': 'A'}, {'c': 'B'}] | ||
| 409 | + } | ||
| 410 | + pairs = list(utils.dict_to_keyval(data)) | ||
| 411 | + self.assertEqual(pairs, [('a', 'A'), | ||
| 412 | + ('b', 'B'), | ||
| 413 | + ('nested2[0].c', 'A'), | ||
| 414 | + ('nested2[1].c', 'B'), | ||
| 415 | + ('nested.a', 'A'), | ||
| 416 | + ('nested.b', 'B')]) | ||
| 417 | -- | ||
| 418 | 1.7.10.4 | ||
| 419 | |||
diff --git a/meta-openstack/recipes-devtools/python/python-ceilometer_git.bb b/meta-openstack/recipes-devtools/python/python-ceilometer_git.bb index e727200..e35e67e 100644 --- a/meta-openstack/recipes-devtools/python/python-ceilometer_git.bb +++ b/meta-openstack/recipes-devtools/python/python-ceilometer_git.bb | |||
| @@ -10,6 +10,8 @@ SRCNAME = "ceilometer" | |||
| 10 | SRC_URI = "git://github.com/openstack/${SRCNAME}.git;branch=stable/havana \ | 10 | SRC_URI = "git://github.com/openstack/${SRCNAME}.git;branch=stable/havana \ |
| 11 | file://ceilometer.conf \ | 11 | file://ceilometer.conf \ |
| 12 | file://ceilometer.init \ | 12 | file://ceilometer.init \ |
| 13 | file://0001-Fix-for-get_resources-with-postgresql.patch \ | ||
| 14 | file://0002-enable-sql-metadata-query.patch \ | ||
| 13 | " | 15 | " |
| 14 | 16 | ||
| 15 | SRCREV="4d15cc05e9d2ada01b90e1d3c15427608cc49f54" | 17 | SRCREV="4d15cc05e9d2ada01b90e1d3c15427608cc49f54" |
