Refactor out Ceilometer V2 API related code

As of Queens the Ceilometer V2 API has been removed.  Storage
driver related code in monasca-ceilometer can thus be removed.

Backport some refactoring that came up in creating
https://review.openstack.org/#/c/562400/
Realized some class names could be clearer and fit better with
Ceilometer.

Update test-requirements.txt with Rocky release of
python-monascaclient.  Also include more license info.

Specify py35 instead of py34 in tox.ini (gates already using py35).

Include updates to devstack configuration to keep up with master
ceilometer.  These include adding values for the [monasca] section
in ceilometer.conf and refreshing setup.cfg as well as updating
how the connection url to Monasca API is configured.

Also include some testing notes in devstack/README.md

Change-Id: I9291dfc06d67f18109a0ff66184bd418c6361977
This commit is contained in:
Joseph Davis 2018-07-23 16:36:44 -07:00
parent 6ebd2dc1c2
commit 8b0fb6d048
23 changed files with 236 additions and 1191 deletions

View File

@ -14,7 +14,4 @@
class NotImplementedError(NotImplementedError):
# FIXME(jd) This is used by WSME to return a correct HTTP code. We should
# not expose it here but wrap our methods in the API to convert it to a
# proper HTTP error.
code = 501
pass

View File

@ -1,178 +0,0 @@
#
# Copyright 2016 Hewlett Packard
# (c) Copyright 2018 SUSE LLC
#
# 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.
"""Static mapping for Ceilometer static info like unit and type information
"""
import os
import pkg_resources
import yaml
from oslo_log import log
from ceilometer import sample
LOG = log.getLogger(__name__)
class CeilometerStaticMappingDefinitionException(Exception):
def __init__(self, message, definition_cfg):
super(CeilometerStaticMappingDefinitionException,
self).__init__(message)
self.message = message
self.definition_cfg = definition_cfg
def __str__(self):
return '%s %s: %s' % (self.__class__.__name__,
self.definition_cfg, self.message)
class CeilometerStaticMappingDefinition(object):
REQUIRED_FIELDS = ['name', 'type', 'unit']
def __init__(self, definition_cfg):
self.cfg = definition_cfg
missing = [field for field in self.REQUIRED_FIELDS
if not self.cfg.get(field)]
if missing:
raise CeilometerStaticMappingDefinitionException(
"Required fields %s not specified" % missing, self.cfg)
if ('type' not in self.cfg.get('lookup', []) and
self.cfg['type'] not in sample.TYPES):
raise CeilometerStaticMappingDefinitionException(
"Invalid type %s specified" % self.cfg['type'], self.cfg)
def get_config_file(conf):
config_file = conf.monasca.ceilometer_static_info_mapping
if not os.path.exists(config_file):
config_file = conf.find_file(config_file)
if not config_file:
config_file = pkg_resources.resource_filename(
__name__, "data/ceilometer_static_info_mapping.yaml")
return config_file
def setup_ceilometer_static_mapping_config(conf):
"""Setup the meters definitions from yaml config file."""
config_file = get_config_file(conf)
if config_file is not None:
LOG.debug("Static Ceilometer mapping file to map static info: %s",
config_file)
with open(config_file) as cf:
config = cf.read()
try:
ceilometer_static_mapping_config = yaml.safe_load(config)
except yaml.YAMLError as err:
if hasattr(err, 'problem_mark'):
mark = err.problem_mark
errmsg = ("Invalid YAML syntax in static Ceilometer "
"Mapping Definitions file %(file)s at line: "
"%(line)s, column: %(column)s."
% dict(file=config_file,
line=mark.line + 1,
column=mark.column + 1))
else:
errmsg = ("YAML error reading static Ceilometer Mapping "
"Definitions file %(file)s" %
dict(file=config_file))
LOG.error(errmsg)
raise
else:
LOG.debug("No static Ceilometer Definitions configuration file "
"found! using default config.")
ceilometer_static_mapping_config = {}
LOG.debug("Ceilometer Monasca Definitions: %s",
ceilometer_static_mapping_config)
return ceilometer_static_mapping_config
def load_definitions(config_def):
if not config_def:
return []
ceilometer_static_mapping_defs = {}
for meter_info_static_map in reversed(config_def['meter_info_static_map']):
if meter_info_static_map.get('name') in ceilometer_static_mapping_defs:
# skip duplicate meters
LOG.warning("Skipping duplicate Ceilometer Monasca Mapping"
" Definition %s" % meter_info_static_map)
continue
try:
md = CeilometerStaticMappingDefinition(meter_info_static_map)
ceilometer_static_mapping_defs[meter_info_static_map['name']] = md
except CeilometerStaticMappingDefinitionException as me:
errmsg = ("Error loading Ceilometer Static Mapping "
"Definition : %(err)s" % dict(err=me.message))
LOG.error(errmsg)
return ceilometer_static_mapping_defs.values()
class ProcessMappedCeilometerStaticInfo(object):
"""Implentation for class to provide static info for ceilometer meters
The class will be responsible for providing the static information of
ceilometer meters enabled using pipeline.yaml configuration.
get_list_supported_meters: is a get function which can be used to get
list of pipeline meters.
get_ceilometer_meter_static_definition: returns entire definition for
provided meter name
get_meter_static_info_key_val: returns specific value for provided meter
name and a particular key from definition
"""
_inited = False
_instance = None
def __new__(cls, *args, **kwargs):
"""Singleton to avoid duplicated initialization."""
if not cls._instance:
cls._instance = super(ProcessMappedCeilometerStaticInfo, cls).\
__new__(cls, *args, **kwargs)
return cls._instance
def __init__(self, conf):
if not (self._instance and self._inited):
self.conf = conf
self._inited = True
self.__definitions = load_definitions(
setup_ceilometer_static_mapping_config(self.conf))
self.__mapped_meter_info_map = dict()
for d in self.__definitions:
self.__mapped_meter_info_map[d.cfg['name']] = d
def get_list_supported_meters(self):
return self.__mapped_meter_info_map
def get_ceilometer_meter_static_definition(self, meter_name):
return self.__mapped_meter_info_map.get(meter_name)
def get_meter_static_info_key_val(self, meter_name, key):
return self.__mapped_meter_info_map.get(meter_name).cfg[key]
def reinitialize(self, conf):
self.conf = conf
self.__definitions = load_definitions(
setup_ceilometer_static_mapping_config(self.conf))
self.__mapped_meter_info_map = dict()
for d in self.__definitions:
self.__mapped_meter_info_map[d.cfg['name']] = d

View File

@ -1,298 +0,0 @@
#
# Copyright 2016 Hewlett Packard
# (c) Copyright 2018 SUSE LLC
#
# 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.
"""Monasca metric to Ceilometer Meter Mapper
"""
import functools
import os
import pkg_resources
import six
import yaml
from jsonpath_rw_ext import parser
from oslo_log import log
from ceilometer import pipeline
from ceilometer import sample
LOG = log.getLogger(__name__)
class CeiloscaMappingDefinitionException(Exception):
def __init__(self, message, definition_cfg):
super(CeiloscaMappingDefinitionException, self).__init__(message)
self.message = message
self.definition_cfg = definition_cfg
def __str__(self):
return '%s %s: %s' % (self.__class__.__name__,
self.definition_cfg, self.message)
class CeiloscaMappingDefinition(object):
JSONPATH_RW_PARSER = parser.ExtentedJsonPathParser()
REQUIRED_FIELDS = ['name', 'monasca_metric_name', 'type', 'unit', 'source',
'resource_metadata', 'resource_id', 'project_id',
'user_id', 'region']
def __init__(self, definition_cfg):
self.cfg = definition_cfg
missing = [field for field in self.REQUIRED_FIELDS
if not self.cfg.get(field)]
if missing:
raise CeiloscaMappingDefinitionException(
"Required fields %s not specified" % missing, self.cfg)
self._monasca_metric_name = self.cfg.get('monasca_metric_name')
if isinstance(self._monasca_metric_name, six.string_types):
self._monasca_metric_name = [self._monasca_metric_name]
if ('type' not in self.cfg.get('lookup', []) and
self.cfg['type'] not in sample.TYPES):
raise CeiloscaMappingDefinitionException(
"Invalid type %s specified" % self.cfg['type'], self.cfg)
self._field_getter = {}
for name, field in self.cfg.items():
if name in ["monasca_metric_name", "lookup"] or not field:
continue
elif isinstance(field, six.integer_types):
self._field_getter[name] = field
elif isinstance(field, six.string_types) and not \
field.startswith('$'):
self._field_getter[name] = field
elif isinstance(field, dict) and name == 'resource_metadata':
meta = {}
for key, val in field.items():
parts = self.parse_jsonpath(val)
meta[key] = functools.partial(self._parse_jsonpath_field,
parts)
self._field_getter['resource_metadata'] = meta
else:
parts = self.parse_jsonpath(field)
self._field_getter[name] = functools.partial(
self._parse_jsonpath_field, parts)
def parse_jsonpath(self, field):
try:
parts = self.JSONPATH_RW_PARSER.parse(field)
except Exception as e:
raise CeiloscaMappingDefinitionException(
"Parse error in JSONPath specification "
"'%(jsonpath)s': %(err)s"
% dict(jsonpath=field, err=e), self.cfg)
return parts
def parse_fields(self, field, message, all_values=False):
getter = self._field_getter.get(field)
if not getter:
return
elif isinstance(getter, dict):
dict_val = {}
for key, val in getter.items():
dict_val[key] = val(message, all_values)
return dict_val
elif callable(getter):
return getter(message, all_values)
else:
return getter
@staticmethod
def _parse_jsonpath_field(parts, message, all_values):
values = [match.value for match in parts.find(message)
if match.value is not None]
if values:
if not all_values:
return values[0]
return values
def get_config_file(conf):
config_file = conf.monasca.ceilometer_monasca_metrics_mapping
if not os.path.exists(config_file):
config_file = conf.find_file(config_file)
if not config_file:
config_file = pkg_resources.resource_filename(
__name__, "data/ceilosca_mapping.yaml")
return config_file
def setup_ceilosca_mapping_config(conf):
"""Setup the meters definitions from yaml config file."""
config_file = get_config_file(conf)
if config_file is not None:
LOG.debug("Ceilometer Monasca Mapping Definitions file: %s",
config_file)
with open(config_file) as cf:
config = cf.read()
try:
ceilosca_mapping_config = yaml.safe_load(config)
except yaml.YAMLError as err:
if hasattr(err, 'problem_mark'):
mark = err.problem_mark
errmsg = ("Invalid YAML syntax in Ceilometer Monasca "
"Mapping Definitions file %(file)s at line: "
"%(line)s, column: %(column)s."
% dict(file=config_file,
line=mark.line + 1,
column=mark.column + 1))
else:
errmsg = ("YAML error reading Ceilometer Monasca Mapping "
"Definitions file %(file)s" %
dict(file=config_file))
LOG.error(errmsg)
raise
else:
LOG.debug("No Ceilometer Monasca Definitions configuration file "
"found! using default config.")
ceilosca_mapping_config = {}
LOG.debug("Ceilometer Monasca Definitions: %s",
ceilosca_mapping_config)
return ceilosca_mapping_config
def load_definitions(config_def):
if not config_def:
return []
ceilosca_mapping_defs = {}
for meter_metric_map in reversed(config_def['meter_metric_map']):
if meter_metric_map.get('name') in ceilosca_mapping_defs:
# skip duplicate meters
LOG.warning("Skipping duplicate Ceilometer Monasca Mapping"
" Definition %s" % meter_metric_map)
continue
try:
md = CeiloscaMappingDefinition(meter_metric_map)
ceilosca_mapping_defs[meter_metric_map['name']] = md
except CeiloscaMappingDefinitionException as me:
errmsg = ("Error loading Ceilometer Monasca Mapping "
"Definition : %(err)s" % dict(err=me.message))
LOG.error(errmsg)
return ceilosca_mapping_defs.values()
class ProcessMappedCeiloscaMetric(object):
"""Implentation for managing monasca mapped metrics to ceilometer meters
The class will be responsible for managing mapped meters and their
definition. You can use get functions for
get_monasca_metric_name: get mapped monasca metric name for ceilometer
meter name
get_list_monasca_metrics: get list of mapped metrics with their respective
definitions
get_ceilosca_mapped_metric_definition: get definition of a provided monasca
metric name
get_ceilosca_mapped_definition_key_val: get respective value of a provided
key from mapping definitions
The class would be a singleton class
"""
_inited = False
_instance = None
def __new__(cls, *args, **kwargs):
"""Singleton to avoid duplicated initialization."""
if not cls._instance:
cls._instance = super(ProcessMappedCeiloscaMetric, cls).__new__(
cls, *args, **kwargs)
return cls._instance
def __init__(self, conf):
if not (self._instance and self._inited):
self.conf = conf
self._inited = True
self.__definitions = load_definitions(
setup_ceilosca_mapping_config(self.conf))
self.__mapped_metric_map = dict()
self.__mon_metric_to_cm_meter_map = dict()
for d in self.__definitions:
self.__mapped_metric_map[d.cfg['monasca_metric_name']] = d
self.__mon_metric_to_cm_meter_map[d.cfg['name']] = (
d.cfg['monasca_metric_name'])
def get_monasca_metric_name(self, ceilometer_meter_name):
return self.__mon_metric_to_cm_meter_map.get(ceilometer_meter_name)
def get_list_monasca_metrics(self):
return self.__mapped_metric_map
def get_ceilosca_mapped_metric_definition(self, monasca_metric_name):
return self.__mapped_metric_map.get(monasca_metric_name)
def get_ceilosca_mapped_definition_key_val(self, monasca_metric_name, key):
return self.__mapped_metric_map.get(monasca_metric_name).cfg[key]
def reinitialize(self, conf):
self.conf = conf
self.__definitions = load_definitions(
setup_ceilosca_mapping_config(self.conf))
self.__mapped_metric_map = dict()
self.__mon_metric_to_cm_meter_map = dict()
for d in self.__definitions:
self.__mapped_metric_map[d.cfg['monasca_metric_name']] = d
self.__mon_metric_to_cm_meter_map[d.cfg['name']] = (
d.cfg['monasca_metric_name'])
class PipelineReader(object):
"""Implentation for class to provide ceilometer meters enabled by pipeline
The class will be responsible for providing the list of ceilometer meters
enabled using pipeline.yaml configuration.
get_pipeline_meters: is a get function which can be used to get list of
pipeline meters.
"""
_inited = False
_instance = None
def __new__(cls, *args, **kwargs):
"""Singleton to avoid duplicated initialization."""
if not cls._instance:
cls._instance = super(PipelineReader, cls).__new__(
cls, *args, **kwargs)
return cls._instance
def __init__(self, conf):
if not (self._instance and self._inited):
self._inited = True
self.conf = conf
self.__pipeline_manager = pipeline.setup_pipeline(self.conf)
self.__meters_from_pipeline = set()
for pipe in self.__pipeline_manager.pipelines:
if not isinstance(pipe, pipeline.EventPipeline):
for meter in pipe.source.meters:
if meter not in self.__meters_from_pipeline:
self.__meters_from_pipeline.add(meter)
def get_pipeline_meters(self):
return self.__meters_from_pipeline
def reinitialize(self):
self.__pipeline_manager = pipeline.setup_pipeline(self.conf)
self.__meters_from_pipeline = set()
for pipe in self.__pipeline_manager.pipelines:
if not isinstance(pipe, pipeline.EventPipeline):
for meter in pipe.source.meters:
if meter not in self.__meters_from_pipeline:
self.__meters_from_pipeline.add(meter)

View File

@ -1,147 +0,0 @@
#reference: https://docs.openstack.org/ceilometer/latest/admin/telemetry-measurements.html
---
meter_info_static_map:
- name: "disk.ephemeral.size"
type: "gauge"
unit: "GB"
- name: "disk.root.size"
type: "gauge"
unit: "GB"
- name: "image"
type: "gauge"
unit: "image"
- name: "image.delete"
type: "delta"
unit: "image"
- name: "image.size"
type: "gauge"
unit: "B"
- name: "image.update"
type: "gauge"
unit: "image"
- name: "image.upload"
type: "delta"
unit: "image"
- name: "instance"
type: "gauge"
unit: "instance"
- name: "ip.floating"
type: "gauge"
unit: "ip"
- name: "ip.floating.create"
type: "delta"
unit: "ip"
- name: "ip.floating.update"
type: "delta"
unit: "ip"
- name: "memory"
type: "gauge"
unit: "MB"
- name: "network"
type: "gauge"
unit: "network"
- name: "network.create"
type: "delta"
unit: "network"
- name: "network.delete"
type: "delta"
unit: "network"
- name: "network.update"
type: "delta"
unit: "network"
- name: "port"
type: "gauge"
unit: "port"
- name: "port.create"
type: "delta"
unit: "port"
- name: "port.delete"
type: "delta"
unit: "port"
- name: "port.update"
type: "delta"
unit: "port"
- name: "router"
type: "gauge"
unit: "router"
- name: "router.create"
type: "delta"
unit: "router"
- name: "router.delete"
type: "delta"
unit: "router"
- name: "router.update"
type: "delta"
unit: "router"
- name: "storage.objects"
type: "gauge"
unit: "object"
- name: "storage.objects.containers"
type: "gauge"
unit: "container"
- name: "storage.objects.size"
type: "gauge"
unit: "B"
- name: "subnet"
type: "gauge"
unit: "subnet"
- name: "subnet.create"
type: "delta"
unit: "subnet"
- name: "subnet.delete"
type: "delta"
unit: "subnet"
- name: "subnet.update"
type: "delta"
unit: "subnet"
- name: "vcpus"
type: "gauge"
unit: "vcpu"
- name: "volume"
type: "gauge"
unit: "volume"
- name: "volume.delete.end"
type: "delta"
unit: "volume"
- name: "volume.size"
type: "gauge"
unit: "GB"
- name: "volume.update.end"
type: "delta"
unit: "volume"

View File

@ -1,24 +0,0 @@
---
meter_metric_map:
- name: "network.outgoing.rate"
monasca_metric_name: "vm.net.out_rate"
resource_id: $.dimensions.resource_id
project_id: $.dimensions.project_id
user_id: $.dimensions.user_id
region: "NA"
type: "gauge"
unit: "b/s"
source: "NA"
resource_metadata: $.measurement[0][2]
- name: "network.incoming.rate"
monasca_metric_name: "vm.net.in_rate"
resource_id: $.dimensions.resource_id
project_id: $.dimensions.project_id
user_id: $.dimensions.user_id
region: "NA"
type: "gauge"
unit: "b/s"
source: "NA"
resource_metadata: $.measurement[0][2]

View File

@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
from monascaclient import client
from monascaclient import exc
from oslo_log import log
@ -121,6 +119,7 @@ class Client(object):
self._endpoint, **self._kwargs)
def call_func(self, func, **kwargs):
"""General method for calling any Monasca API function."""
@tenacity.retry(
wait=tenacity.wait_fixed(self._retry_interval),
stop=tenacity.stop_after_attempt(self._max_retries),
@ -156,101 +155,3 @@ class Client(object):
def metrics_create(self, **kwargs):
return self.call_func(self._mon_client.metrics.create,
**kwargs)
def metrics_list(self, **kwargs):
"""Using monasca pagination to get all metrics.
We yield endless metrics till caller doesn't want more or
no more is left.
"""
search_args = copy.deepcopy(kwargs)
metrics = self.call_func(self._mon_client.metrics.list, **search_args)
# check if api pagination is enabled
if self._enable_api_pagination:
# page through monasca results
while metrics:
for metric in metrics:
yield metric
# offset for metrics is the last metric's id
search_args['offset'] = metric['id']
metrics = self.call_func(self._mon_client.metrics.list,
**search_args)
else:
for metric in metrics:
yield metric
def metric_names_list(self, **kwargs):
return self.call_func(self._mon_client.metrics.list_names,
**kwargs)
def measurements_list(self, **kwargs):
"""Using monasca pagination to get all measurements.
We yield endless measurements till caller doesn't want more or
no more is left.
"""
search_args = copy.deepcopy(kwargs)
measurements = self.call_func(
self._mon_client.metrics.list_measurements,
**search_args)
# check if api pagination is enabled
if self._enable_api_pagination:
while measurements and len(measurements[0]["measurements"]) > 0:
for measurement in measurements:
if measurement["measurements"] is not None and \
len(measurement["measurements"]) > 0:
# offset for measurements is measurement id composited
# with the last measurement's timestamp
last_good_offset = '%s_%s' % (
measurement['id'],
measurement['measurements'][-1][0])
yield measurement
search_args['offset'] = last_good_offset
measurements = self.call_func(
self._mon_client.metrics.list_measurements,
**search_args)
else:
for measurement in measurements:
yield measurement
def statistics_list(self, **kwargs):
"""Using monasca pagination to get all statistics.
We yield endless statistics till caller doesn't want more or
no more is left.
"""
search_args = copy.deepcopy(kwargs)
statistics = self.call_func(self._mon_client.metrics.list_statistics,
**search_args)
# check if api pagination is enabled
if self._enable_api_pagination:
while statistics and len(statistics[0]["statistics"]) > 0:
for statistic in statistics:
if statistic["statistics"] is not None and \
len(statistic["statistics"]) > 0:
# offset for statistics is statistic id composited with
# the last statistic's timestamp
last_good_offset = '%s_%s' % (
statistic['id'], statistic['statistics'][-1][0])
yield statistic
# with groupby, the offset is unpredictable to me, we don't
# support pagination for it now.
if kwargs.get('group_by'):
break
search_args['offset'] = last_good_offset
statistics = self.call_func(
self._mon_client.metrics.list_statistics,
**search_args)
# unlike metrics.list and metrics.list_measurements
# return whole new data, metrics.list_statistics
# next page will use last page's final statistic
# data as the first one, so we need to pop it here.
# I think Monasca should treat this as a bug and fix it.
if statistics:
statistics[0]['statistics'].pop(0)
if len(statistics[0]['statistics']) == 0:
statistics.pop(0)
else:
for statistic in statistics:
yield statistic

View File

@ -19,18 +19,6 @@ from oslo_config import cfg
OPTS = [
# from ceilometer_static_info_mapping
cfg.StrOpt('ceilometer_static_info_mapping',
default='ceilometer_static_info_mapping.yaml',
help='Configuration mapping file to map ceilometer meters to '
'their units an type information'),
# from ceilosca_mapping
cfg.StrOpt('ceilometer_monasca_metrics_mapping',
default='ceilosca_mapping.yaml',
help='Configuration mapping file to map monasca metrics to '
'ceilometer meters'),
# from monasca_client
cfg.StrOpt('clientapi_version',
default='2_0',

View File

@ -1,5 +1,4 @@
# Copyright 2014 eNovance
# (c) Copyright 2018 SUSE LLC
#
# 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
@ -32,7 +31,7 @@ import ceilometer.ipmi.pollsters
import ceilometer.keystone_client
import ceilometer.meter.notifications
import ceilometer.middleware
import ceilometer.monasca_ceilometer_opts
import ceilometer.monasca_opts
import ceilometer.neutron_client
import ceilometer.notification
import ceilometer.nova_client
@ -95,28 +94,6 @@ def list_opts():
help='Number of seconds between checks to see if group '
'membership has changed'),
]),
('dispatcher_gnocchi', (
cfg.StrOpt(
'filter_project',
deprecated_for_removal=True,
default='gnocchi',
help='Gnocchi project used to filter out samples '
'generated by Gnocchi service activity'),
cfg.StrOpt(
'archive_policy',
deprecated_for_removal=True,
help='The archive policy to use when the dispatcher '
'create a new metric.'),
cfg.StrOpt(
'resources_definition_file',
deprecated_for_removal=True,
default='gnocchi_resources.yaml',
help=('The Yaml file that defines mapping between samples '
'and gnocchi resources/metrics')),
cfg.FloatOpt(
'request_timeout', default=6.05, min=0.0,
deprecated_for_removal=True,
help='Number of seconds before request to gnocchi times out'))),
('event', ceilometer.event.converter.OPTS),
('hardware', itertools.chain(
ceilometer.hardware.discovery.OPTS,
@ -125,7 +102,7 @@ def list_opts():
itertools.chain(ceilometer.ipmi.platform.intel_node_manager.OPTS,
ceilometer.ipmi.pollsters.OPTS)),
('meter', ceilometer.meter.notifications.OPTS),
('monasca', ceilometer.monasca_ceilometer_opts.OPTS),
('monasca', ceilometer.monasca_opts.OPTS),
('notification',
itertools.chain(ceilometer.notification.OPTS,
ceilometer.notification.EXCHANGES_OPTS)),
@ -133,6 +110,7 @@ def list_opts():
('publisher', ceilometer.publisher.utils.OPTS),
('publisher_notifier', ceilometer.publisher.messaging.NOTIFIER_OPTS),
('rgw_admin_credentials', ceilometer.objectstore.rgw.CREDENTIAL_OPTS),
('rgw_client', ceilometer.objectstore.rgw.CLIENT_OPTS),
('service_types',
itertools.chain(ceilometer.image.discovery.SERVICE_OPTS,
ceilometer.neutron_client.SERVICE_OPTS,

View File

@ -21,6 +21,7 @@ import threading
import time
from oslo_log import log
from six import moves
import ceilometer
from ceilometer import monasca_client as mon_client
@ -199,7 +200,7 @@ class MonascaPublisher(publisher.ConfigPublisherBase):
# Iterate over the retry_queue to eliminate
# metrics that have maxed out their retry attempts
for ctr in xrange(retry_count):
for ctr in moves.xrange(retry_count):
if self.retry_counter[ctr] > self.conf.monasca.max_retries:
if hasattr(self, 'archive_handler'):
self.archive_handler.publish_samples(

View File

@ -22,8 +22,6 @@ from oslo_utils import timeutils
import six
import yaml
from ceilometer.ceilosca_mapping.ceilosca_mapping import (
CeiloscaMappingDefinitionException)
from ceilometer import sample as sample_util
LOG = log.getLogger(__name__)
@ -37,6 +35,17 @@ class NoMappingsFound(Exception):
pass
class CeiloscaMappingDefinitionException(Exception):
def __init__(self, message, definition_cfg):
super(CeiloscaMappingDefinitionException, self).__init__(message)
self.message = message
self.definition_cfg = definition_cfg
def __str__(self):
return '%s %s: %s' % (self.__class__.__name__,
self.definition_cfg, self.message)
class MonascaDataFilter(object):
JSONPATH_RW_PARSER = parser.ExtentedJsonPathParser()
@ -113,7 +122,7 @@ class MonascaDataFilter(object):
# If following convention, dict will have one and only one
# element of the form <monasca key>: <json path>
if len(meta_key.keys()) == 1:
mon_key = meta_key.keys()[0]
mon_key = list(meta_key.keys())[0]
else:
# If no keys or more keys than one
raise CeiloscaMappingDefinitionException(

View File

@ -19,9 +19,7 @@ import mock
from oslo_utils import timeutils
from oslotest import base
from ceilometer.ceilosca_mapping.ceilosca_mapping import (
CeiloscaMappingDefinitionException)
from ceilometer import monasca_ceilometer_opts
from ceilometer import monasca_opts
from ceilometer.publisher import monasca_data_filter as mdf
from ceilometer import sample
from ceilometer import service
@ -31,7 +29,7 @@ class TestMonUtils(base.BaseTestCase):
def setUp(self):
super(TestMonUtils, self).setUp()
self.CONF = service.prepare_service([], [])
self.CONF.register_opts(list(monasca_ceilometer_opts.OPTS),
self.CONF.register_opts(list(monasca_opts.OPTS),
'monasca')
self._field_mappings = {
'dimensions': ['resource_id',
@ -418,7 +416,7 @@ class TestMonUtils(base.BaseTestCase):
try:
# Don't assign to a variable, this should raise
data_filter.process_sample_for_monasca(s)
except CeiloscaMappingDefinitionException as e:
except mdf.CeiloscaMappingDefinitionException as e:
self.assertEqual(
'Metadata format mismatch, value should be '
'a simple string. [\'aaa0001\', \'bbbb002\']',
@ -503,7 +501,7 @@ class TestMonUtils(base.BaseTestCase):
try:
# Don't assign to a variable as this should raise
data_filter.process_sample_for_monasca(s)
except CeiloscaMappingDefinitionException as e:
except mdf.CeiloscaMappingDefinitionException as e:
# Make sure we got the right kind of error
# Cannot check the whole message text, as python
# may reorder a dict when producing a string version

View File

@ -13,7 +13,7 @@
# 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/publisher/monclient.py
"""Tests for ceilometer/publisher/monasca.py
"""
import datetime
@ -23,9 +23,9 @@ import time
import mock
from oslotest import base
from ceilometer import monasca_ceilometer_opts
from ceilometer import monasca_client as mon_client
from ceilometer.publisher import monclient
from ceilometer import monasca_opts
from ceilometer.publisher import monasca as mon_publisher
from ceilometer import sample
from ceilometer import service
@ -111,7 +111,7 @@ class TestMonascaPublisher(base.BaseTestCase):
super(TestMonascaPublisher, self).setUp()
self.CONF = service.prepare_service([], [])
self.CONF.register_opts(list(monasca_ceilometer_opts.OPTS),
self.CONF.register_opts(list(monasca_opts.OPTS),
'monasca')
self.CONF.set_override('service_username', 'ceilometer', 'monasca')
self.CONF.set_override('service_password', 'admin', 'monasca')
@ -136,7 +136,7 @@ class TestMonascaPublisher(base.BaseTestCase):
side_effect=[field_mappings])
def test_publisher_publish(self, mapping_patch):
self.CONF.set_override('batch_mode', False, group='monasca')
publisher = monclient.MonascaPublisher(self.CONF, self.parsed_url)
publisher = mon_publisher.MonascaPublisher(self.CONF, self.parsed_url)
publisher.mon_client = mock.MagicMock()
with mock.patch.object(publisher.mon_client,
@ -154,7 +154,7 @@ class TestMonascaPublisher(base.BaseTestCase):
self.CONF.set_override('batch_count', 3, group='monasca')
self.CONF.set_override('batch_polling_interval', 1, group='monasca')
publisher = monclient.MonascaPublisher(self.CONF, self.parsed_url)
publisher = mon_publisher.MonascaPublisher(self.CONF, self.parsed_url)
publisher.mon_client = mock.MagicMock()
with mock.patch.object(publisher.mon_client,
'metrics_create') as mock_create:
@ -175,7 +175,7 @@ class TestMonascaPublisher(base.BaseTestCase):
self.CONF.set_override('retry_interval', 2, group='monasca')
self.CONF.set_override('max_retries', 1, group='monasca')
publisher = monclient.MonascaPublisher(self.CONF, self.parsed_url)
publisher = mon_publisher.MonascaPublisher(self.CONF, self.parsed_url)
publisher.mon_client = mock.MagicMock()
with mock.patch.object(publisher.mon_client,
'metrics_create') as mock_create:
@ -200,8 +200,8 @@ class TestMonascaPublisher(base.BaseTestCase):
'ceilometer.publisher.file.FilePublisher',
return_value=self.fake_publisher))
publisher = monclient.MonascaPublisher(self.CONF,
self.parsed_url)
publisher = mon_publisher.MonascaPublisher(self.CONF,
self.parsed_url)
publisher.mon_client = mock.MagicMock()
with mock.patch.object(publisher.mon_client,

View File

@ -17,8 +17,8 @@ import mock
from oslo_utils import netutils
from oslotest import base
from ceilometer import monasca_ceilometer_opts
from ceilometer import monasca_client
from ceilometer import monasca_opts
from ceilometer import service
from monascaclient import exc
@ -30,7 +30,7 @@ class TestMonascaClient(base.BaseTestCase):
super(TestMonascaClient, self).setUp()
self.CONF = service.prepare_service([], [])
self.CONF.register_opts(list(monasca_ceilometer_opts.OPTS),
self.CONF.register_opts(list(monasca_opts.OPTS),
'monasca')
self.CONF.set_override('service_username', 'ceilometer', 'monasca')
self.CONF.set_override('service_password', 'admin', 'monasca')
@ -78,7 +78,7 @@ class TestMonascaClient(base.BaseTestCase):
as create_patch:
e = self.assertRaises(tenacity.RetryError,
self.mc.metrics_create)
(original_ex, traceobj) = e.last_attempt.exception_info()
original_ex = e.last_attempt.exception()
self.assertIsInstance(original_ex,
monasca_client.MonascaServiceException)
self.assertEqual(1, create_patch.call_count)
@ -110,16 +110,6 @@ class TestMonascaClient(base.BaseTestCase):
self.assertIsNotNone(True, conf.service_username)
def test_retry_on_key_error(self):
self.CONF.set_override('database_max_retries', 2, 'monasca')
self.CONF.set_override('database_retry_interval', 1, 'monasca')
self.mc = self._get_client()
with mock.patch.object(
self.mc._mon_client.metrics, 'list',
side_effect=[KeyError, []]) as mocked_metrics_list:
list(self.mc.metrics_list())
self.assertEqual(2, mocked_metrics_list.call_count)
def test_no_retry_on_invalid_parameter(self):
self.CONF.set_override('database_max_retries', 2, 'monasca')
self.CONF.set_override('database_retry_interval', 1, 'monasca')
@ -128,16 +118,16 @@ class TestMonascaClient(base.BaseTestCase):
def _check(exception):
expected_exc = monasca_client.MonascaInvalidParametersException
with mock.patch.object(
self.mc._mon_client.metrics, 'list',
side_effect=[exception, []]
self.mc._mon_client.metrics, 'create',
side_effect=[exception, True]
) as mocked_metrics_list:
self.assertRaises(expected_exc, list, self.mc.metrics_list())
self.assertRaises(expected_exc, self.mc.metrics_create)
self.assertEqual(1, mocked_metrics_list.call_count)
_check(exc.http.UnprocessableEntity)
_check(exc.http.BadRequest)
def test_max_retris_not_too_much(self):
def test_max_retries_not_too_much(self):
def _check(configured, expected):
self.CONF.set_override('database_max_retries', configured,
'monasca')
@ -151,25 +141,27 @@ class TestMonascaClient(base.BaseTestCase):
def test_meaningful_exception_message(self):
with mock.patch.object(
self.mc._mon_client.metrics, 'list',
self.mc._mon_client.metrics, 'create',
side_effect=[exc.http.InternalServerError,
exc.http.UnprocessableEntity,
KeyError]):
e = self.assertRaises(
tenacity.RetryError,
list, self.mc.metrics_list())
(original_ex, traceobj) = e.last_attempt.exception_info()
self.mc.metrics_create)
original_ex = e.last_attempt.exception()
self.assertIn('Monasca service is unavailable',
str(original_ex))
e = self.assertRaises(
monasca_client.MonascaInvalidParametersException,
list, self.mc.metrics_list())
self.mc.metrics_create)
self.assertIn('Request cannot be handled by Monasca',
str(e))
e = self.assertRaises(
tenacity.RetryError,
list, self.mc.metrics_list())
(original_ex, traceobj) = e.last_attempt.exception_info()
self.mc.metrics_create)
original_ex = e.last_attempt.exception()
self.assertIn('An exception is raised from Monasca',
str(original_ex))
@ -181,238 +173,3 @@ class TestMonascaClient(base.BaseTestCase):
self.assertRaises(
monasca_client.MonascaInvalidParametersException,
self.mc.metrics_create)
def test_metrics_list_with_pagination(self):
metric_list_pages = [[{u'dimensions': {},
u'measurements': [
[u'2015-04-14T17:52:31Z',
1.0, {}]],
u'id': u'2015-04-14T18:42:31Z',
u'columns': [u'timestamp', u'value',
u'value_meta'],
u'name': u'test1'}],
[{u'dimensions': {},
u'measurements': [
[u'2015-04-15T17:52:31Z',
2.0, {}]],
u'id': u'2015-04-15T18:42:31Z',
u'columns': [u'timestamp', u'value',
u'value_meta'],
u'name': u'test2'}], None]
expected_page_count = len(metric_list_pages)
expected_metric_names = ["test1", "test2"]
self.CONF.set_override('enable_api_pagination',
True, group='monasca')
# get a new ceilosca mc
mc = self._get_client()
with mock.patch.object(
mc._mon_client.metrics, 'list',
side_effect=metric_list_pages) as mocked_metrics_list:
returned_metrics = mc.metrics_list()
returned_metric_names_list = [metric["name"]
for metric in returned_metrics]
self.assertListEqual(expected_metric_names,
returned_metric_names_list)
self.assertEqual(expected_page_count,
mocked_metrics_list.call_count)
self.assertEqual(True, mocked_metrics_list.called)
def test_metrics_list_without_pagination(self):
metric_list_pages = [[{u'dimensions': {},
u'measurements': [
[u'2015-04-14T17:52:31Z',
1.0, {}]],
u'id': u'2015-04-14T18:42:31Z',
u'columns': [u'timestamp', u'value',
u'value_meta'],
u'name': u'test1'}],
[{u'dimensions': {},
u'measurements': [
[u'2015-04-15T17:52:31Z',
2.0, {}]],
u'id': u'2015-04-15T18:42:31Z',
u'columns': [u'timestamp', u'value',
u'value_meta'],
u'name': u'test2'}], None]
# first page only
expected_page_count = 1
expected_metric_names = ["test1"]
self.CONF.set_override('enable_api_pagination',
False, group='monasca')
# get a new ceilosca mc
mc = self._get_client()
with mock.patch.object(
mc._mon_client.metrics, 'list',
side_effect=metric_list_pages) as mocked_metrics_list:
returned_metrics = mc.metrics_list()
returned_metric_names_list = [metric["name"]
for metric in returned_metrics]
self.assertListEqual(expected_metric_names,
returned_metric_names_list)
self.assertEqual(expected_page_count,
mocked_metrics_list.call_count)
self.assertEqual(True, mocked_metrics_list.called)
def test_measurement_list_with_pagination(self):
measurement_list_pages = [[{u'dimensions': {},
u'measurements': [
[u'2015-04-14T17:52:31Z',
1.0, {}]],
u'id': u'2015-04-14T18:42:31Z',
u'columns': [u'timestamp', u'value',
u'value_meta'],
u'name': u'test1'}],
[{u'dimensions': {},
u'measurements': [
[u'2015-04-15T17:52:31Z',
2.0, {}]],
u'id': u'2015-04-15T18:42:31Z',
u'columns': [u'timestamp', u'value',
u'value_meta'],
u'name': u'test2'}], None]
expected_page_count = len(measurement_list_pages)
expected_metric_names = ["test1", "test2"]
self.CONF.set_override('enable_api_pagination',
True, group='monasca')
# get a new ceilosca mc
mc = self._get_client()
with mock.patch.object(
mc._mon_client.metrics, 'list_measurements',
side_effect=measurement_list_pages) as mocked_metrics_list:
returned_metrics = mc.measurements_list()
returned_metric_names_list = [metric["name"]
for metric in returned_metrics]
self.assertListEqual(expected_metric_names,
returned_metric_names_list)
self.assertEqual(expected_page_count,
mocked_metrics_list.call_count)
self.assertEqual(True, mocked_metrics_list.called)
def test_measurement_list_without_pagination(self):
measurement_list_pages = [[{u'dimensions': {},
u'measurements': [
[u'2015-04-14T17:52:31Z',
1.0, {}]],
u'id': u'2015-04-14T18:42:31Z',
u'columns': [u'timestamp', u'value',
u'value_meta'],
u'name': u'test1'}],
[{u'dimensions': {},
u'measurements': [
[u'2015-04-15T17:52:31Z',
2.0, {}]],
u'id': u'2015-04-15T18:42:31Z',
u'columns': [u'timestamp', u'value',
u'value_meta'],
u'name': u'test2'}], None]
# first page only
expected_page_count = 1
expected_metric_names = ["test1"]
self.CONF.set_override('enable_api_pagination',
False, group='monasca')
# get a new ceilosca mc
mc = self._get_client()
with mock.patch.object(
mc._mon_client.metrics, 'list_measurements',
side_effect=measurement_list_pages) as mocked_metrics_list:
returned_metrics = mc.measurements_list()
returned_metric_names_list = [metric["name"]
for metric in returned_metrics]
self.assertListEqual(expected_metric_names,
returned_metric_names_list)
self.assertEqual(expected_page_count,
mocked_metrics_list.call_count)
self.assertEqual(True, mocked_metrics_list.called)
def test_statistics_list_with_pagination(self):
statistics_list_pages = [[{u'dimensions': {},
u'statistics': [
[u'2015-04-14T17:52:31Z',
1.0, 10.0],
[u'2015-04-15T17:52:31Z',
1.0, 10.0]],
u'id': u'2015-04-14T18:42:31Z',
u'columns': [u'timestamp', u'avg',
u'max'],
u'name': u'test1'}],
[{u'dimensions': {},
u'statistics': [
[u'2015-04-16T17:52:31Z',
2.0, 20.0],
[u'2015-04-17T17:52:31Z',
2.0, 20.0]],
u'id': u'2015-04-15T18:42:31Z',
u'columns': [u'timestamp', u'avg',
u'max'],
u'name': u'test2'}], None]
expected_page_count = len(statistics_list_pages)
expected_metric_names = ["test1", "test2"]
self.CONF.set_override('enable_api_pagination',
True, group='monasca')
# get a new ceilosca mc
mc = self._get_client()
with mock.patch.object(
mc._mon_client.metrics, 'list_statistics',
side_effect=statistics_list_pages) as mocked_metrics_list:
returned_metrics = mc.statistics_list()
returned_metric_names_list = [metric["name"]
for metric in returned_metrics]
self.assertListEqual(expected_metric_names,
returned_metric_names_list)
self.assertEqual(expected_page_count,
mocked_metrics_list.call_count)
self.assertEqual(True, mocked_metrics_list.called)
def test_statistics_list_without_pagination(self):
statistics_list_pages = [[{u'dimensions': {},
u'statistics': [
[u'2015-04-14T17:52:31Z',
1.0, 10.0]],
u'id': u'2015-04-14T18:42:31Z',
u'columns': [u'timestamp', u'avg',
u'max'],
u'name': u'test1'}],
[{u'dimensions': {},
u'statistics': [
[u'2015-04-15T17:52:31Z',
2.0, 20.0]],
u'id': u'2015-04-15T18:42:31Z',
u'columns': [u'timestamp', u'avg',
u'max'],
u'name': u'test2'}], None]
# first page only
expected_page_count = 1
expected_metric_names = ["test1"]
self.CONF.set_override('enable_api_pagination',
False, group='monasca')
# get a new ceilosca mc
mc = self._get_client()
with mock.patch.object(
mc._mon_client.metrics, 'list_statistics',
side_effect=statistics_list_pages) as mocked_metrics_list:
returned_metrics = mc.statistics_list()
returned_metric_names_list = [metric["name"]
for metric in returned_metrics]
self.assertListEqual(expected_metric_names,
returned_metric_names_list)
self.assertEqual(expected_page_count,
mocked_metrics_list.call_count)
self.assertEqual(True, mocked_metrics_list.called)

View File

@ -3,11 +3,14 @@
There are a few options for configuring Ceilosca on top of a Ceilometer and Monasca deployment.
Choose one:
- DevStack can be instructed through the local.conf to "enable ceilosca". Reference the included devstack/sample-local.conf for one configuration.
- DevStack can be instructed through the local.conf to "enable ceilosca". Reference the included
devstack/sample-local.conf for one example configuration.
- Use the included Vagrantfile to create and provision a VM. This will provision a new Ubuntu 16.04 VM and run the ceilosca.sh.
- Use the included Vagrantfile to create and provision a VM. This will provision a new Ubuntu 16.04 VM and run the
ceilosca.sh.
- Under certain conditions the monasca_test_setup.py may be used to set up Ceilosca for testing. This .py may also be useful reference if you choose to write your own integration scripts.
- Under certain conditions the monasca_test_setup.py may be used to set up Ceilosca for testing. This .py may also be
useful reference if you choose to write your own integration scripts.
- The devstack/ceilosca.sh script will copy Ceilosca components on top of Ceilometer.
- ceilosca.sh has been updated to the Newton release.
@ -15,5 +18,40 @@ Choose one:
- The script should be tweaked before execution, particularly the lines.
- export SERVICE_HOST=192.168.10.6
- export HOST_IP_IFACE=eth0
- The script should be run by a sudoers user with no password required. Such as is described in https://docs.openstack.org/devstack/latest/
- And did not configure Horizon
- The script should be run by a sudoers user with no password required. Such as is described in
https://docs.openstack.org/devstack/latest/
- And note ceilosca.sh does not configure Horizon
# Testing notes
Once Ceilosca is installed the gathered metrics will appear in Monasca. The `monasca` cli can be used to verify
functionality.
- Source a valid rc file to set the OS_ environment variables needed for the cli. For example:
```
export OS_PROJECT_NAME=mini-mon
export OS_IDENTITY_API_VERSION=3
export OS_PASSWORD=<the password>
export OS_AUTH_URL=http://<Your Keystone IP>/identity/v3/
export OS_USERNAME=mini-mon
```
- Run `monasca metric-list`
- Devstack includes a cirros 3.5 image by default. This will be represented in an `image.size` metric in monasca
with a datasource of `ceilometer`.
- Cause further metrics to be created by doing more OpenStack operations.
- Create another image
- wget -c http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img -o cirros-0.4.0-x86_64-disk.img
- openstack image create --disk-format qcow2 --container-format bare cirros-0.4.0 < cirros-0.4.0-x86_64-disk.img
- Create a simple vm
- openstack image list
- Choose one of the images listed, note its id
- openstack flavor list
- Choose one of the flavors, note its id
- openstack project list
- openstack security group list
- Pick the group that matches the `admin` project, note its id
- openstack security group rule create --proto tcp --dst-port 22 <security group id>
- openstack server create --flavor <flavor id> --image <image id> --security-group <security group id> mytest
- Check the progress with `openstack server list`
- Look for additional metrics with `datasource: ceilometer` in `monasca metric-list`
- Explore the samples (aka. measurements) in `monasca measurement-list image.size -120` and similar commands.

View File

@ -13,6 +13,15 @@ function configure_ceilosca {
echo "In configure_ceilosca"
sudo mkdir -p /etc/ceilometer
sudo chown $CEILOSCA_USER /etc/ceilometer
# back up Ceilometer's ceilometer.conf for reference
if [[ -e /etc/ceilometer/ceilometer.conf ]]; then
cp /etc/ceilometer/ceilometer.conf /etc/ceilometer/ceilometer.conf.preceilosca
fi
# back up existing pipeline.yaml
if [[ -e /etc/ceilometer/pipeline.yaml ]]; then
cp /etc/ceilometer/pipeline.yaml /etc/ceilometer/pipeline.yaml.preceilosca
fi
for ceilosca_conf_file in $CEILOSCA_CONF_FILES
do
# source file and dest file names are separated with :
@ -24,24 +33,48 @@ function configure_ceilosca {
cp $CEILOSCA_DIR/etc/ceilometer/$source_file /etc/ceilometer/$dest_file
done
iniset $CEILOMETER_CONF database metering_connection monasca://$MONASCA_API_URL
# For reference, these ceilometer.conf options should be checked against
# the current version and any updates made with each release.
# https://docs.openstack.org/ceilometer/latest/configuration/
# [database] removed after Pike
# iniset $CEILOMETER_CONF database metering_connection monasca://$MONASCA_API_URL
iniset $CEILOMETER_CONF notification workers $API_WORKERS
# TODO: workload_partitioning deprecated in Rocky
iniset $CEILOMETER_CONF notification workload_partitioning False
# Disable, otherwise Ceilosca won't process and store event data
iniset $CEILOMETER_CONF notification disable_non_metric_meters False
# iniset $CEILOMETER_CONF notification disable_non_metric_meters False
# Workaround: Client has a problem with the /identity auth url only in service_credentials
auth_url=$(iniget $CEILOMETER_CONF service_credentials auth_url)
if [[ -n "$auth_url" ]]; then
# Go direct to the port
auth_url=${auth_url/%\/identity/:35357\/v3}
iniset $CEILOMETER_CONF service_credentials auth_url ${auth_url}
fi
#if [[ -n "$auth_url" ]]; then
# # Go direct to the port
# auth_url=${auth_url/%\/identity/:35357\/v3}
# iniset $CEILOMETER_CONF service_credentials auth_url ${auth_url}
#fi
# Add the [monasca] section and connection values
iniset $CEILOMETER_CONF monasca service_auth_url ${auth_url}
iniset $CEILOMETER_CONF monasca service_interface internalURL
iniset $CEILOMETER_CONF monasca service_auth_type password
# default monasca user for devstack (check monasca-api/devstack/plugin.sh)
iniset $CEILOMETER_CONF monasca service_username mini-mon
iniset $CEILOMETER_CONF monasca service_password password
iniset $CEILOMETER_CONF monasca service_project_name mini-mon
iniset $CEILOMETER_CONF monasca service_domain_name Default
iniset $CEILOMETER_CONF monasca service_region_name RegionOne
# leave the defaults for retry_on_failure, retry_interval,
# enable_api_pagination, database_retry_interval, database_max_retries
# Fill in Monasca URL in pipeline.yaml
sed -i "s,monasca://INSERT-URL-HERE,monasca://${MONASCA_API_URL},g" /etc/ceilometer/pipeline.yaml
}
function preinstall_ceilosca {
# create new directory
cp -r $CEILOSCA_DIR/ceilosca/ceilometer/ceilosca_mapping $CEILOMETER_DIR/ceilometer/
# create new directory --- removed in Rocky
# cp -r $CEILOSCA_DIR/ceilosca/ceilometer/ceilosca_mapping $CEILOMETER_DIR/ceilometer/
# overlay files into existing dirs
for ceilosca_file in $CEILOSCA_FILES
@ -59,7 +92,6 @@ function preinstall_ceilosca {
if ! grep -q "python-monascaclient" $CEILOMETER_DIR/requirements.txt; then
sudo bash -c "echo python-monascaclient >> $CEILOMETER_DIR/requirements.txt"
fi
}
# check for service enabled

View File

@ -24,6 +24,7 @@ MONASCA_PERSISTER_IMPLEMENTATION_LANG=${MONASCA_PERSISTER_IMPLEMENTATION_LANG:-p
# Uncomment one of the following two lines to choose either InfluxDB or Vertica.
MONASCA_METRICS_DB=${MONASCA_METRICS_DB:-influxdb}
# MONASCA_METRICS_DB=${MONASCA_METRICS_DB:-cassandra}
# MONASCA_METRICS_DB=${MONASCA_METRICS_DB:-vertica}
# Monasca services to enable
@ -38,7 +39,7 @@ enable_service monasca-agent
enable_service monasca-cli
# This line will enable all of Monasca.
enable_plugin monasca-api git://git.openstack.org/openstack/monasca-api stable/newton
enable_plugin monasca-api git://git.openstack.org/openstack/monasca-api stable/rocky
# the following may be disabled to leave monasca transform still functional
#disable_service monasca-persister
@ -49,13 +50,12 @@ disable_service horizon
#disable_service cinder
disable_service monasca-smoke-test
# NO stable/newton branch for monasca-transform
enable_plugin monasca-transform https://git.openstack.org/openstack/monasca-transform master
# USING UPSTREAM master ceilosca but newton everything else
enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer stable/newton
enable_plugin ceilosca https://git.openstack.org/openstack/monasca-ceilometer master
# Another good Monasca service that isn't installed by monasca-api automatically
enable_plugin monasca-transform https://git.openstack.org/openstack/monasca-transform stable/rocky
# Be sure that Ceilometer is installed before Ceilosca (monasca-ceilometer repo is not stand-alone installable)
enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer stable/rocky
enable_plugin ceilosca https://git.openstack.org/openstack/monasca-ceilometer stable/rocky
enable_service ceilosca

View File

@ -4,8 +4,11 @@ source $DEST/ceilometer/devstack/settings
enable_service ceilosca
MONASCA_API_URL=http://$SERVICE_HOST:8070/v2.0
# Should be getting the same value as in the Keystone endpoint list!
# Note: to clarify, Monasca is at /metrics/v2.0, Gnocchi is at /metric (no s)
MONASCA_API_URL=http://$SERVICE_HOST/metrics/v2.0
CEILOSCA_DIR=$DEST/ceilosca
CEILOSCA_FILES='ceilometer/monasca_client.py ceilometer/publisher/monasca_data_filter.py ceilometer/publisher/monclient.py ceilometer/storage/impl_monasca.py ../devstack/setup.cfg:.'
CEILOSCA_FILES='ceilometer/monasca_client.py ceilometer/publisher/monasca_data_filter.py ceilometer/publisher/monasca.py ceilometer/opts.py ceilometer/monasca_opts.py ../devstack/setup.cfg:.'
CEILOSCA_CONF_FILES='ceilosca_pipeline.yaml:pipeline.yaml monasca_field_definitions.yaml'
CEILOSCA_USER=$USER

View File

@ -1,5 +1,6 @@
[metadata]
name = ceilometer
url = http://launchpad.net/ceilometer
summary = OpenStack Telemetry
description-file =
README.rst
@ -15,6 +16,8 @@ classifier =
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
Topic :: System :: Monitoring
[global]
@ -24,41 +27,39 @@ setup-hooks =
[files]
packages =
ceilometer
data_files =
etc/ceilometer = etc/ceilometer/*
[extras]
gnocchi =
oslo.cache>=1.5.0 # Apache-2.0
gnocchiclient>=7.0.0 # Apache-2.0
zaqar =
python-zaqarclient>=1.0.0 # Apache-2.0
monasca =
python-monascaclient>=1.7.0 # Apache-2.0
[entry_points]
ceilometer.notification =
instance = ceilometer.compute.notifications.instance:Instance
instance_scheduled = ceilometer.compute.notifications.instance:InstanceScheduled
network = ceilometer.network.notifications:Network
subnet = ceilometer.network.notifications:Subnet
port = ceilometer.network.notifications:Port
router = ceilometer.network.notifications:Router
floatingip = ceilometer.network.notifications:FloatingIP
ceilometer.notification.pipeline =
meter = ceilometer.pipeline.sample:SamplePipelineManager
event = ceilometer.pipeline.event:EventPipelineManager
ceilometer.sample.endpoint =
http.request = ceilometer.middleware:HTTPRequest
http.response = ceilometer.middleware:HTTPResponse
hardware.ipmi.temperature = ceilometer.ipmi.notifications.ironic:TemperatureSensorNotification
hardware.ipmi.voltage = ceilometer.ipmi.notifications.ironic:VoltageSensorNotification
hardware.ipmi.current = ceilometer.ipmi.notifications.ironic:CurrentSensorNotification
hardware.ipmi.fan = ceilometer.ipmi.notifications.ironic:FanSensorNotification
network.services.lb.pool = ceilometer.network.notifications:Pool
network.services.lb.vip = ceilometer.network.notifications:Vip
network.services.lb.member = ceilometer.network.notifications:Member
network.services.lb.health_monitor = ceilometer.network.notifications:HealthMonitor
network.services.firewall = ceilometer.network.notifications:Firewall
network.services.firewall.policy = ceilometer.network.notifications:FirewallPolicy
network.services.firewall.rule = ceilometer.network.notifications:FirewallRule
network.services.vpn = ceilometer.network.notifications:VPNService
network.services.vpn.ipsecpolicy = ceilometer.network.notifications:IPSecPolicy
network.services.vpn.ikepolicy = ceilometer.network.notifications:IKEPolicy
network.services.vpn.connections = ceilometer.network.notifications:IPSecSiteConnection
_sample = ceilometer.telemetry.notifications:TelemetryIpc
meter = ceilometer.meter.notifications:ProcessMeterNotifications
ceilometer.discover =
ceilometer.discover.compute =
local_instances = ceilometer.compute.discovery:InstanceDiscovery
endpoint = ceilometer.agent.discovery.endpoint:EndpointDiscovery
tenant = ceilometer.agent.discovery.tenant:TenantDiscovery
local_node = ceilometer.agent.discovery.localnode:LocalNodeDiscovery
ceilometer.discover.central =
endpoint = ceilometer.polling.discovery.endpoint:EndpointDiscovery
tenant = ceilometer.polling.discovery.tenant:TenantDiscovery
lb_pools = ceilometer.network.services.discovery:LBPoolsDiscovery
lb_vips = ceilometer.network.services.discovery:LBVipsDiscovery
lb_members = ceilometer.network.services.discovery:LBMembersDiscovery
@ -70,6 +71,14 @@ ceilometer.discover =
fw_services = ceilometer.network.services.discovery:FirewallDiscovery
fw_policy = ceilometer.network.services.discovery:FirewallPolicyDiscovery
tripleo_overcloud_nodes = ceilometer.hardware.discovery:NodesDiscoveryTripleO
fip_services = ceilometer.network.services.discovery:FloatingIPDiscovery
images = ceilometer.image.discovery:ImagesDiscovery
volumes = ceilometer.volume.discovery:VolumeDiscovery
volume_snapshots = ceilometer.volume.discovery:VolumeSnapshotsDiscovery
volume_backups = ceilometer.volume.discovery:VolumeBackupsDiscovery
ceilometer.discover.ipmi =
local_node = ceilometer.polling.discovery.localnode:LocalNodeDiscovery
ceilometer.poll.compute =
disk.read.requests = ceilometer.compute.pollsters.disk:ReadRequestsPollster
@ -88,27 +97,41 @@ ceilometer.poll.compute =
disk.device.write.requests.rate = ceilometer.compute.pollsters.disk:PerDeviceWriteRequestsRatePollster
disk.device.read.bytes.rate = ceilometer.compute.pollsters.disk:PerDeviceReadBytesRatePollster
disk.device.write.bytes.rate = ceilometer.compute.pollsters.disk:PerDeviceWriteBytesRatePollster
disk.device.read.latency = ceilometer.compute.pollsters.disk:PerDeviceDiskReadLatencyPollster
disk.device.write.latency = ceilometer.compute.pollsters.disk:PerDeviceDiskWriteLatencyPollster
disk.latency = ceilometer.compute.pollsters.disk:DiskLatencyPollster
disk.device.latency = ceilometer.compute.pollsters.disk:PerDeviceDiskLatencyPollster
disk.iops = ceilometer.compute.pollsters.disk:DiskIOPSPollster
disk.device.iops = ceilometer.compute.pollsters.disk:PerDeviceDiskIOPSPollster
cpu = ceilometer.compute.pollsters.cpu:CPUPollster
cpu_util = ceilometer.compute.pollsters.cpu:CPUUtilPollster
cpu = ceilometer.compute.pollsters.instance_stats:CPUPollster
cpu_util = ceilometer.compute.pollsters.instance_stats:CPUUtilPollster
cpu_l3_cache = ceilometer.compute.pollsters.instance_stats:CPUL3CachePollster
network.incoming.bytes = ceilometer.compute.pollsters.net:IncomingBytesPollster
network.incoming.packets = ceilometer.compute.pollsters.net:IncomingPacketsPollster
network.outgoing.bytes = ceilometer.compute.pollsters.net:OutgoingBytesPollster
network.outgoing.packets = ceilometer.compute.pollsters.net:OutgoingPacketsPollster
network.incoming.bytes.rate = ceilometer.compute.pollsters.net:IncomingBytesRatePollster
network.outgoing.bytes.rate = ceilometer.compute.pollsters.net:OutgoingBytesRatePollster
instance = ceilometer.compute.pollsters.instance:InstancePollster
memory.usage = ceilometer.compute.pollsters.memory:MemoryUsagePollster
memory.resident = ceilometer.compute.pollsters.memory:MemoryResidentPollster
network.incoming.packets.drop = ceilometer.compute.pollsters.net:IncomingDropPollster
network.outgoing.packets.drop = ceilometer.compute.pollsters.net:OutgoingDropPollster
network.incoming.packets.error = ceilometer.compute.pollsters.net:IncomingErrorsPollster
network.outgoing.packets.error = ceilometer.compute.pollsters.net:OutgoingErrorsPollster
memory.usage = ceilometer.compute.pollsters.instance_stats:MemoryUsagePollster
memory.resident = ceilometer.compute.pollsters.instance_stats:MemoryResidentPollster
memory.swap.in = ceilometer.compute.pollsters.instance_stats:MemorySwapInPollster
memory.swap.out = ceilometer.compute.pollsters.instance_stats:MemorySwapOutPollster
memory.bandwidth.total = ceilometer.compute.pollsters.instance_stats:MemoryBandwidthTotalPollster
memory.bandwidth.local = ceilometer.compute.pollsters.instance_stats:MemoryBandwidthLocalPollster
disk.capacity = ceilometer.compute.pollsters.disk:CapacityPollster
disk.allocation = ceilometer.compute.pollsters.disk:AllocationPollster
disk.usage = ceilometer.compute.pollsters.disk:PhysicalPollster
disk.device.capacity = ceilometer.compute.pollsters.disk:PerDeviceCapacityPollster
disk.device.allocation = ceilometer.compute.pollsters.disk:PerDeviceAllocationPollster
disk.device.usage = ceilometer.compute.pollsters.disk:PerDevicePhysicalPollster
perf.cpu.cycles = ceilometer.compute.pollsters.instance_stats:PerfCPUCyclesPollster
perf.instructions = ceilometer.compute.pollsters.instance_stats:PerfInstructionsPollster
perf.cache.references = ceilometer.compute.pollsters.instance_stats:PerfCacheReferencesPollster
perf.cache.misses = ceilometer.compute.pollsters.instance_stats:PerfCacheMissesPollster
ceilometer.poll.ipmi =
hardware.ipmi.node.power = ceilometer.ipmi.pollsters.node:PowerPollster
@ -126,22 +149,28 @@ ceilometer.poll.ipmi =
ceilometer.poll.central =
ip.floating = ceilometer.network.floatingip:FloatingIPPollster
image = ceilometer.image.glance:ImagePollster
image.size = ceilometer.image.glance:ImageSizePollster
rgw.containers.objects = ceilometer.objectstore.rgw:ContainersObjectsPollster
rgw.containers.objects.size = ceilometer.objectstore.rgw:ContainersSizePollster
rgw.objects = ceilometer.objectstore.rgw:ObjectsPollster
rgw.objects.size = ceilometer.objectstore.rgw:ObjectsSizePollster
rgw.objects.containers = ceilometer.objectstore.rgw:ObjectsContainersPollster
rgw.usage = ceilometer.objectstore.rgw:UsagePollster
port = ceilometer.network.statistics.port_v2:PortPollster
port.uptime = ceilometer.network.statistics.port_v2:PortPollsterUptime
port.receive.packets = ceilometer.network.statistics.port_v2:PortPollsterReceivePackets
port.transmit.packets = ceilometer.network.statistics.port_v2:PortPollsterTransmitPackets
port.receive.bytes = ceilometer.network.statistics.port_v2:PortPollsterReceiveBytes
port.transmit.bytes = ceilometer.network.statistics.port_v2:PortPollsterTransmitBytes
port.receive.drops = ceilometer.network.statistics.port_v2:PortPollsterReceiveDrops
port.receive.errors = ceilometer.network.statistics.port_v2:PortPollsterReceiveErrors
radosgw.containers.objects = ceilometer.objectstore.rgw:ContainersObjectsPollster
radosgw.containers.objects.size = ceilometer.objectstore.rgw:ContainersSizePollster
radosgw.objects = ceilometer.objectstore.rgw:ObjectsPollster
radosgw.objects.size = ceilometer.objectstore.rgw:ObjectsSizePollster
radosgw.objects.containers = ceilometer.objectstore.rgw:ObjectsContainersPollster
radosgw.usage = ceilometer.objectstore.rgw:UsagePollster
storage.containers.objects = ceilometer.objectstore.swift:ContainersObjectsPollster
storage.containers.objects.size = ceilometer.objectstore.swift:ContainersSizePollster
storage.objects = ceilometer.objectstore.swift:ObjectsPollster
storage.objects.size = ceilometer.objectstore.swift:ObjectsSizePollster
storage.objects.containers = ceilometer.objectstore.swift:ObjectsContainersPollster
energy = ceilometer.energy.kwapi:EnergyPollster
power = ceilometer.energy.kwapi:PowerPollster
switch.port = ceilometer.network.statistics.port:PortPollster
switch.port.uptime = ceilometer.network.statistics.port:PortPollsterUptime
switch.port.receive.packets = ceilometer.network.statistics.port:PortPollsterReceivePackets
switch.port.transmit.packets = ceilometer.network.statistics.port:PortPollsterTransmitPackets
switch.port.receive.bytes = ceilometer.network.statistics.port:PortPollsterReceiveBytes
@ -159,6 +188,7 @@ ceilometer.poll.central =
switch.table.lookup.packets = ceilometer.network.statistics.table:TablePollsterLookupPackets
switch.table.matched.packets = ceilometer.network.statistics.table:TablePollsterMatchedPackets
switch = ceilometer.network.statistics.switch:SWPollster
switch.ports = ceilometer.network.statistics.switch:SwitchPollsterPorts
switch.flow = ceilometer.network.statistics.flow:FlowPollster
switch.flow.bytes = ceilometer.network.statistics.flow:FlowPollsterBytes
switch.flow.duration.nanoseconds = ceilometer.network.statistics.flow:FlowPollsterDurationNanoseconds
@ -178,30 +208,13 @@ ceilometer.poll.central =
network.services.vpn.connections = ceilometer.network.services.vpnaas:IPSecConnectionsPollster
network.services.firewall = ceilometer.network.services.fwaas:FirewallPollster
network.services.firewall.policy = ceilometer.network.services.fwaas:FirewallPolicyPollster
volume.size = ceilometer.volume.cinder:VolumeSizePollster
volume.snapshot.size = ceilometer.volume.cinder:VolumeSnapshotSize
volume.backup.size = ceilometer.volume.cinder:VolumeBackupSize
ceilometer.builder.poll.central =
hardware.snmp = ceilometer.hardware.pollsters.generic:GenericHardwareDeclarativePollster
ceilometer.event.storage =
es = ceilometer.event.storage.impl_elasticsearch:Connection
log = ceilometer.event.storage.impl_log:Connection
mongodb = ceilometer.event.storage.impl_mongodb:Connection
mysql = ceilometer.event.storage.impl_sqlalchemy:Connection
postgresql = ceilometer.event.storage.impl_sqlalchemy:Connection
sqlite = ceilometer.event.storage.impl_sqlalchemy:Connection
hbase = ceilometer.event.storage.impl_hbase:Connection
db2 = ceilometer.event.storage.impl_db2:Connection
ceilometer.metering.storage =
log = ceilometer.storage.impl_log:Connection
mongodb = ceilometer.storage.impl_mongodb:Connection
mysql = ceilometer.storage.impl_sqlalchemy:Connection
postgresql = ceilometer.storage.impl_sqlalchemy:Connection
sqlite = ceilometer.storage.impl_sqlalchemy:Connection
hbase = ceilometer.storage.impl_hbase:Connection
db2 = ceilometer.storage.impl_db2:Connection
monasca = ceilometer.storage.impl_monasca:Connection
ceilometer.compute.virt =
libvirt = ceilometer.compute.virt.libvirt.inspector:LibvirtInspector
hyperv = ceilometer.compute.virt.hyperv.inspector:HyperVInspector
@ -219,47 +232,37 @@ ceilometer.transformer =
aggregator = ceilometer.transformer.conversions:AggregatorTransformer
arithmetic = ceilometer.transformer.arithmetic:ArithmeticTransformer
ceilometer.publisher =
ceilometer.sample.publisher =
test = ceilometer.publisher.test:TestPublisher
notifier = ceilometer.publisher.messaging:SampleNotifierPublisher
udp = ceilometer.publisher.udp:UDPPublisher
file = ceilometer.publisher.file:FilePublisher
direct = ceilometer.publisher.direct:DirectPublisher
kafka = ceilometer.publisher.kafka_broker:KafkaBrokerPublisher
monasca = ceilometer.publisher.monclient:MonascaPublisher
http = ceilometer.publisher.http:HttpPublisher
prometheus = ceilometer.publisher.prometheus:PrometheusPublisher
https = ceilometer.publisher.http:HttpPublisher
monasca = ceilometer.publisher.monasca:MonascaPublisher
gnocchi = ceilometer.publisher.gnocchi:GnocchiPublisher
zaqar = ceilometer.publisher.zaqar:ZaqarPublisher
ceilometer.event.publisher =
test = ceilometer.publisher.test:TestPublisher
direct = ceilometer.publisher.direct:DirectPublisher
notifier = ceilometer.publisher.messaging:EventNotifierPublisher
kafka = ceilometer.publisher.kafka_broker:KafkaBrokerPublisher
http = ceilometer.publisher.http:HttpPublisher
https = ceilometer.publisher.http:HttpPublisher
gnocchi = ceilometer.publisher.gnocchi:GnocchiPublisher
zaqar = ceilometer.publisher.zaqar:ZaqarPublisher
ceilometer.event.trait_plugin =
split = ceilometer.event.trait_plugins:SplitterTraitPlugin
bitfield = ceilometer.event.trait_plugins:BitfieldTraitPlugin
timedelta = ceilometer.event.trait_plugins:TimedeltaPlugin
console_scripts =
ceilometer-api = ceilometer.cmd.api:main
ceilometer-polling = ceilometer.cmd.polling:main
ceilometer-agent-notification = ceilometer.cmd.agent_notification:main
ceilometer-send-sample = ceilometer.cmd.sample:send_sample
ceilometer-dbsync = ceilometer.cmd.storage:dbsync
ceilometer-expirer = ceilometer.cmd.storage:expirer
ceilometer-upgrade = ceilometer.cmd.storage:upgrade
ceilometer-rootwrap = oslo_rootwrap.cmd:main
ceilometer-collector = ceilometer.cmd.collector:main
ceilometer.dispatcher.meter =
database = ceilometer.dispatcher.database:DatabaseDispatcher
file = ceilometer.dispatcher.file:FileDispatcher
http = ceilometer.dispatcher.http:HttpDispatcher
gnocchi = ceilometer.dispatcher.gnocchi:GnocchiDispatcher
ceilometer.dispatcher.event =
database = ceilometer.dispatcher.database:DatabaseDispatcher
file = ceilometer.dispatcher.file:FileDispatcher
http = ceilometer.dispatcher.http:HttpDispatcher
network.statistics.drivers =
opendaylight = ceilometer.network.statistics.opendaylight.driver:OpenDayLightDriver
@ -267,15 +270,7 @@ network.statistics.drivers =
oslo.config.opts =
ceilometer = ceilometer.opts:list_opts
oslo.config.opts.defaults =
ceilometer = ceilometer.conf.defaults:set_cors_middleware_defaults
keystoneauth1.plugin =
password-ceilometer-legacy = ceilometer.keystone_client:LegacyCeilometerKeystoneLoader
tempest.test_plugins =
ceilometer_tests = ceilometer.tests.tempest.plugin:CeilometerTempestPlugin
ceilometer-auth = ceilometer.opts:list_keystoneauth_opts
[build_sphinx]
all_files = 1

View File

@ -19,4 +19,4 @@ sinks:
- name: meter_sink
transformers:
publishers:
- monasca://http://192.168.10.6:8070/v2.0
- monasca://INSERT-URL-HERE

View File

@ -23,9 +23,9 @@ ceilosca_files = {
for file in
[
'monasca_client.py',
'monasca_ceilometer_opts.py',
'monasca_opts.py',
'publisher/monasca_data_filter.py',
'publisher/monclient.py'
'publisher/monasca.py'
]
}
@ -34,11 +34,6 @@ ceilosca_files = {
for src, dest in ceilosca_files.items():
shutil.copyfile(src, dest)
# Include new module
shutil.rmtree(ceilo_dir + "/ceilosca_mapping/", True)
shutil.copytree('ceilosca/ceilometer/ceilosca_mapping',
ceilo_dir + "/ceilosca_mapping/")
ceilo_parent_dir = os.path.dirname(os.path.abspath(
os.path.dirname(ceilometer.__file__)))

View File

@ -1,7 +1,7 @@
git+https://github.com/openstack/ceilometer.git@master#egg=ceilometer
mock>=1.2
testscenarios>=0.4
testtools>=1.4.0
mock>=1.2 # BSD
testscenarios>=0.4 # Apache-2.0/BSD
testtools>=1.4.0 # MIT
oslotest>=2.15.0 # Apache-2.0
oslo.vmware>=1.16.0,<2.17.0 # Apache-2.0
# Use lower versions of config and utils since
@ -9,6 +9,6 @@ oslo.vmware>=1.16.0,<2.17.0 # Apache-2.0
oslo.config>=3.22.0 # Apache-2.0
oslo.log>=1.14.0 # Apache-2.0
os-testr>=1.0.0 # Apache-2.0
python-monascaclient<=1.7.1
python-monascaclient<=1.12.0 # Apache-2.0
docutils>=0.11 # OSI-Approved Open Source, Public Domain
hacking<0.11,>=0.10.0

View File

@ -1,6 +1,6 @@
[tox]
envlist = py34,pep8,py27
minversion = 1.6
envlist = py35,pep8,py27
minversion = 1.8
skipsdist = True
[testenv]