gnocchiclient 2.2.0
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABCAAGBQJWzrRuAAoJEBiStC/OquvIeGYP/iV4Kmt7avOsr6SvhlRQz1V3 uYEHSEBNNZTaRwscPcGP0nI8mA2uLdrxncydTy9DD7m7xE5iTLqKwt5Gsa1OwewG v/68pWAUPzGi67Ntk23Udb/n0RaDyo9YAHjZ+XZZjtU7L/6V96kbBi++0KnUwfgs I4woIU4+UZvdTC3N5x1Oz1D8VvjTrp0SvBDBNy+33xV7/weNnNQwaWPpfmBdiN5M 29IgYxrDSHu7/eX1e13gMt8jYbRWxPVJhw/r7nrVcHrftrxIRTLk5lf0ziRDDhyt e8gHW811LwsONQvnd+5OjqdijDQBMwmc9h4VLhqc1A9yTa6F1yucJd0WM9Vsvaxr DbVJwV6fIcPDqoRk/O5L4uwFijezQmHsM1bBDqdVPTnJdLLoU7u+aX6pkfVzXT+u nJEVAFz4gENbObCIbrQpIM20NLHqqTchTmSC+wolqrGWM6JLDfADJGViJqKYuC2V 2kU8FHmDspFdPHwdRptUnBWOalZ0/VvJbbbiPNbk3TW0dU5WQH8xMp+JFq8pdL16 tZFWeNKQEMI218qktqIoYQR4wq/O8ctcWfPdid1c81O3AaAg0L39N892WbDihLnw faSBD2YXrEe3DsBP5CIr3A/VxsqKCinnhUAN5EvUWDIn0qNu/8cLUrQm2nX4ECGF MIHmKtbKFFkqBQUDnvv9 =4gno -----END PGP SIGNATURE----- Merge tag '2.2.0' into debian/mitaka gnocchiclient 2.2.0
This commit is contained in:
commit
a54ab30782
|
@ -54,4 +54,4 @@ ChangeLog
|
|||
.*sw?
|
||||
|
||||
# generated docs
|
||||
doc/source/ref/
|
||||
doc/source/api
|
||||
|
|
5
.mailmap
5
.mailmap
|
@ -1,3 +1,2 @@
|
|||
# Format is:
|
||||
# <preferred e-mail> <other e-mail 1>
|
||||
# <preferred e-mail> <other e-mail 2>
|
||||
<sileht@sileht.net> <sileht@redhat.com>
|
||||
<sileht@sileht.net> <sileht@sileh.net>
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
|
@ -21,7 +21,7 @@ Reference
|
|||
For more information, see the reference:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 2
|
||||
|
||||
ref/v1/index
|
||||
api/autoindex
|
||||
|
||||
|
|
|
@ -21,51 +21,13 @@ ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", ".."))
|
|||
sys.path.insert(0, ROOT)
|
||||
sys.path.insert(0, BASE_DIR)
|
||||
|
||||
|
||||
def gen_ref(ver, title, names):
|
||||
refdir = os.path.join(BASE_DIR, "ref")
|
||||
pkg = "gnocchiclient"
|
||||
if ver:
|
||||
pkg = "%s.%s" % (pkg, ver)
|
||||
refdir = os.path.join(refdir, ver)
|
||||
if not os.path.exists(refdir):
|
||||
os.makedirs(refdir)
|
||||
idxpath = os.path.join(refdir, "index.rst")
|
||||
with open(idxpath, "w") as idx:
|
||||
idx.write(("%(title)s\n"
|
||||
"%(signs)s\n"
|
||||
"\n"
|
||||
".. toctree::\n"
|
||||
" :maxdepth: 1\n"
|
||||
"\n") % {"title": title, "signs": "=" * len(title)})
|
||||
for name in names:
|
||||
idx.write(" %s\n" % name)
|
||||
rstpath = os.path.join(refdir, "%s.rst" % name)
|
||||
with open(rstpath, "w") as rst:
|
||||
rst.write(("%(title)s\n"
|
||||
"%(signs)s\n"
|
||||
"\n"
|
||||
".. automodule:: %(pkg)s.%(name)s\n"
|
||||
" :members:\n"
|
||||
" :undoc-members:\n"
|
||||
" :show-inheritance:\n"
|
||||
" :noindex:\n")
|
||||
% {"title": " ".join([n.capitalize()
|
||||
for n in name.split("_")]),
|
||||
"signs": "=" * len(name),
|
||||
"pkg": pkg, "name": name})
|
||||
|
||||
gen_ref("v1", "Version 1 API", ["client", "resource", "archive_policy",
|
||||
"archive_policy_rule", "metric"])
|
||||
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
#'sphinx.ext.intersphinx',
|
||||
'oslosphinx'
|
||||
#'sphinx.ext.intersphinx'
|
||||
]
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
|
@ -99,6 +61,15 @@ pygments_style = 'sphinx'
|
|||
# html_theme_path = ["."]
|
||||
# html_theme = '_theme'
|
||||
# html_static_path = ['static']
|
||||
html_theme = os.getenv("SPHINX_HTML_THEME", 'openstack')
|
||||
|
||||
if html_theme == "sphinx_rtd_theme":
|
||||
import sphinx_rtd_theme
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
else:
|
||||
import oslosphinx
|
||||
html_theme_path = [os.path.join(os.path.dirname(oslosphinx.__file__),
|
||||
'theme')]
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
|
|
|
@ -37,7 +37,7 @@ For example, in Bash you would use::
|
|||
|
||||
The command line tool will attempt to reauthenticate using your provided credentials
|
||||
for every request. You can override this behavior by manually supplying an auth
|
||||
token using :option:`--gnocchi-endpoint` and :option:`--os-auth-token`. You can alternatively
|
||||
token using :option:`--endpoint` and :option:`--os-auth-token`. You can alternatively
|
||||
set these environment variables::
|
||||
|
||||
export GNOCCHI_ENDPOINT=http://gnocchi.example.org:8041
|
||||
|
@ -45,8 +45,9 @@ set these environment variables::
|
|||
export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
|
||||
|
||||
Also, if the server doesn't support authentication, you can provide
|
||||
:option:`--os-auth-plugon` gnocchi-noauth, :option:`--gnocchi-endpoint`, :option:`--user-id`
|
||||
and :option:`--project-id`. You can alternatively set these environment variables::
|
||||
:option:`--os-auth-plugin` gnocchi-noauth, :option:`--endpoint`,
|
||||
:option:`--user-id` and :option:`--project-id`. You can alternatively set these
|
||||
environment variables::
|
||||
|
||||
export OS_AUTH_PLUGIN=gnocchi-noauth
|
||||
export GNOCCHI_ENDPOINT=http://gnocchi.example.org:8041
|
||||
|
@ -74,4 +75,4 @@ List resources::
|
|||
|
||||
Search of resources::
|
||||
|
||||
gnocchi resource search --query "project_id=5a301761-f78b-46e2-8900-8b4f6fe6675a and type=instance"
|
||||
gnocchi resource search "project_id='5a301761-f78b-46e2-8900-8b4f6fe6675a' and type=instance"
|
||||
|
|
|
@ -35,5 +35,5 @@ class SessionClient(adapter.Adapter):
|
|||
**kwargs)
|
||||
|
||||
if raise_exc and resp.status_code >= 400:
|
||||
raise exceptions.from_response(resp, url, method)
|
||||
raise exceptions.from_response(resp, method)
|
||||
return resp
|
||||
|
|
|
@ -114,7 +114,7 @@ class Conflict(ClientException):
|
|||
message = "Conflict"
|
||||
|
||||
|
||||
class NamedMetricAreadyExists(Conflict, MutipleMeaningException):
|
||||
class NamedMetricAlreadyExists(Conflict, MutipleMeaningException):
|
||||
message = "Named metric already exists"
|
||||
match = re.compile("Named metric .* does not exist")
|
||||
|
||||
|
@ -131,7 +131,7 @@ class ArchivePolicyAlreadyExists(Conflict, MutipleMeaningException):
|
|||
|
||||
class ArchivePolicyRuleAlreadyExists(Conflict, MutipleMeaningException):
|
||||
message = "Archive policy rule already exists"
|
||||
match = re.compile("Archive policy Rule .* already exists")
|
||||
match = re.compile("Archive policy rule .* already exists")
|
||||
|
||||
|
||||
class OverLimit(RetryAfterException):
|
||||
|
@ -165,9 +165,9 @@ _error_classes = [BadRequest, Unauthorized, Forbidden, NotFound,
|
|||
MethodNotAllowed, NotAcceptable, Conflict, OverLimit,
|
||||
RateLimit, NotImplemented]
|
||||
_error_classes_enhanced = {
|
||||
NotFound: [MetricNotFound, ResourceNotFound, ArchivePolicyNotFound,
|
||||
ArchivePolicyRuleNotFound],
|
||||
Conflict: [NamedMetricAreadyExists, ResourceAlreadyExists,
|
||||
NotFound: [MetricNotFound, ResourceNotFound, ArchivePolicyRuleNotFound,
|
||||
ArchivePolicyNotFound],
|
||||
Conflict: [NamedMetricAlreadyExists, ResourceAlreadyExists,
|
||||
ArchivePolicyAlreadyExists,
|
||||
ArchivePolicyRuleAlreadyExists]
|
||||
}
|
||||
|
@ -176,14 +176,14 @@ _code_map = dict(
|
|||
for c in _error_classes)
|
||||
|
||||
|
||||
def from_response(response, url, method=None):
|
||||
def from_response(response, method=None):
|
||||
"""Return an instance of one of the ClientException on an requests response.
|
||||
|
||||
Usage::
|
||||
|
||||
resp, body = requests.request(...)
|
||||
if resp.status_code != 200:
|
||||
raise exception_from_response(resp)
|
||||
raise from_response(resp)
|
||||
"""
|
||||
|
||||
if response.status_code:
|
||||
|
@ -196,7 +196,7 @@ def from_response(response, url, method=None):
|
|||
kwargs = {
|
||||
'code': response.status_code,
|
||||
'method': method,
|
||||
'url': url,
|
||||
'url': response.url,
|
||||
'request_id': req_id,
|
||||
}
|
||||
|
||||
|
@ -209,12 +209,21 @@ def from_response(response, url, method=None):
|
|||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
desc = body.get('description')
|
||||
for enhanced_cls in enhanced_classes:
|
||||
if enhanced_cls.match.match(desc):
|
||||
cls = enhanced_cls
|
||||
break
|
||||
kwargs['message'] = desc
|
||||
if 'description' in body:
|
||||
# Gnocchi json
|
||||
desc = body.get('description')
|
||||
if desc:
|
||||
for enhanced_cls in enhanced_classes:
|
||||
if enhanced_cls.match.match(desc):
|
||||
cls = enhanced_cls
|
||||
break
|
||||
kwargs['message'] = desc
|
||||
elif isinstance(body, dict) and isinstance(body.get("error"),
|
||||
dict):
|
||||
# Keystone json
|
||||
kwargs['message'] = body["error"]["message"]
|
||||
else:
|
||||
kwargs['message'] = response.text
|
||||
elif content_type.startswith("text/"):
|
||||
kwargs['message'] = response.text
|
||||
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
# 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.
|
||||
|
||||
import oslo_i18n as i18n
|
||||
|
||||
_translators = i18n.TranslatorFactory(domain='gnocchiclient')
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
# Translators for log levels.
|
||||
#
|
||||
# The abbreviated names are meant to reflect the usual use of a short
|
||||
# name like '_'. The "L" is for "log" and the other letter comes from
|
||||
# the level.
|
||||
_LI = _translators.log_info
|
||||
_LW = _translators.log_warning
|
||||
_LE = _translators.log_error
|
||||
_LC = _translators.log_critical
|
|
@ -69,10 +69,17 @@ class GnocchiNoAuthLoader(loading.BaseLoader):
|
|||
def get_options(self):
|
||||
options = super(GnocchiNoAuthLoader, self).get_options()
|
||||
options.extend([
|
||||
GnocchiOpt('user-id', help='User ID', required=True),
|
||||
GnocchiOpt('project-id', help='Project ID', required=True),
|
||||
GnocchiOpt('roles', help='Roles', default="admin"),
|
||||
GnocchiOpt('gnocchi-endpoint', help='Gnocchi endpoint',
|
||||
dest="endpoint", required=True),
|
||||
GnocchiOpt('user-id', help='User ID', required=True,
|
||||
metavar="<gnocchi user id>"),
|
||||
GnocchiOpt('project-id', help='Project ID', required=True,
|
||||
metavar="<gnocchi project id>"),
|
||||
GnocchiOpt('roles', help='Roles', default="admin",
|
||||
metavar="<gnocchi roles>"),
|
||||
GnocchiOpt('endpoint', help='Gnocchi endpoint',
|
||||
deprecated=[
|
||||
GnocchiOpt('gnocchi-endpoint'),
|
||||
],
|
||||
dest="endpoint", required=True,
|
||||
metavar="<gnocchi endpoint>"),
|
||||
])
|
||||
return options
|
||||
|
|
|
@ -45,6 +45,7 @@ class GnocchiCommandManager(commandmanager.CommandManager):
|
|||
"resource create": resource_cli.CliResourceCreate,
|
||||
"resource update": resource_cli.CliResourceUpdate,
|
||||
"resource delete": resource_cli.CliResourceDelete,
|
||||
"resource list-types": resource_cli.CliResourceTypeList,
|
||||
"archive-policy list": archive_policy_cli.CliArchivePolicyList,
|
||||
"archive-policy show": archive_policy_cli.CliArchivePolicyShow,
|
||||
"archive-policy create": archive_policy_cli.CliArchivePolicyCreate,
|
||||
|
@ -59,6 +60,9 @@ class GnocchiCommandManager(commandmanager.CommandManager):
|
|||
"metric delete": metric_cli.CliMetricDelete,
|
||||
"measures show": metric_cli.CliMeasuresShow,
|
||||
"measures add": metric_cli.CliMeasuresAdd,
|
||||
"measures batch-metrics": metric_cli.CliMetricsMeasuresBatch,
|
||||
"measures batch-resources-metrics":
|
||||
metric_cli.CliResourcesMetricsMeasuresBatch,
|
||||
"measures aggregation": metric_cli.CliMeasuresAggregation,
|
||||
"capabilities list": capabilities_cli.CliCapabilitiesList,
|
||||
"benchmark metric create": benchmark.CliBenchmarkMetricCreate,
|
||||
|
@ -123,9 +127,7 @@ class GnocchiShell(app.App):
|
|||
|
||||
if not isinstance(plugin, noauth.GnocchiNoAuthLoader):
|
||||
parser.add_argument(
|
||||
'--gnocchi-endpoint',
|
||||
metavar='<endpoint>',
|
||||
dest='endpoint',
|
||||
'--endpoint',
|
||||
default=os.environ.get('GNOCCHI_ENDPOINT'),
|
||||
help='Gnocchi endpoint (Env: GNOCCHI_ENDPOINT)')
|
||||
|
||||
|
|
|
@ -34,12 +34,12 @@ class GnocchiClient(object):
|
|||
self.project_id = str(uuid.uuid4())
|
||||
|
||||
def gnocchi(self, action, flags='', params='',
|
||||
fail_ok=False, merge_stderr=False):
|
||||
fail_ok=False, merge_stderr=False, input=None):
|
||||
creds = ("--os-auth-plugin gnocchi-noauth "
|
||||
"--user-id %s --project-id %s "
|
||||
"--gnocchi-endpoint %s") % (self.user_id,
|
||||
self.project_id,
|
||||
self.endpoint)
|
||||
"--endpoint %s") % (self.user_id,
|
||||
self.project_id,
|
||||
self.endpoint)
|
||||
|
||||
flags = creds + ' ' + flags
|
||||
|
||||
|
@ -58,10 +58,11 @@ class GnocchiClient(object):
|
|||
cmd = shlex.split(cmd)
|
||||
result = ''
|
||||
result_err = ''
|
||||
stdin = None if input is None else subprocess.PIPE
|
||||
stdout = subprocess.PIPE
|
||||
stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
|
||||
proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
|
||||
result, result_err = proc.communicate()
|
||||
proc = subprocess.Popen(cmd, stdin=stdin, stdout=stdout, stderr=stderr)
|
||||
result, result_err = proc.communicate(input=input)
|
||||
if not fail_ok and proc.returncode != 0:
|
||||
raise exceptions.CommandFailed(proc.returncode,
|
||||
cmd,
|
||||
|
|
|
@ -25,7 +25,7 @@ class ArchivePolicyRuleClientTest(base.ClientTestBase):
|
|||
# CREATE
|
||||
result = self.gnocchi(
|
||||
u'archive-policy-rule', params=u"create test"
|
||||
u" --archive-policy %s"
|
||||
u" --archive-policy-name %s"
|
||||
u" --metric-pattern 'disk.io.*'" % apname)
|
||||
policy_rule = self.details_multiple(result)[0]
|
||||
self.assertEqual('test', policy_rule["name"])
|
||||
|
@ -33,7 +33,7 @@ class ArchivePolicyRuleClientTest(base.ClientTestBase):
|
|||
# CREATE FAIL
|
||||
result = self.gnocchi(
|
||||
u'archive-policy-rule', params=u"create test"
|
||||
u" --archive-policy high"
|
||||
u" --archive-policy-name high"
|
||||
u" --metric-pattern 'disk.io.*'",
|
||||
fail_ok=True, merge_stderr=True)
|
||||
self.assertFirstLineStartsWith(
|
||||
|
|
|
@ -23,7 +23,7 @@ class BenchmarkMetricTest(base.ClientTestBase):
|
|||
|
||||
def test_benchmark_metric_create(self):
|
||||
apname = str(uuid.uuid4())
|
||||
# PREPARE AN ACHIVE POLICY
|
||||
# PREPARE AN ARCHIVE POLICY
|
||||
self.gnocchi("archive-policy", params="create %s "
|
||||
"--back-window 0 -d granularity:1s,points:86400" % apname)
|
||||
|
||||
|
@ -44,7 +44,7 @@ class BenchmarkMetricTest(base.ClientTestBase):
|
|||
|
||||
def test_benchmark_metric_get(self):
|
||||
apname = str(uuid.uuid4())
|
||||
# PREPARE AN ACHIVE POLICY
|
||||
# PREPARE AN ARCHIVE POLICY
|
||||
self.gnocchi("archive-policy", params="create %s "
|
||||
"--back-window 0 -d granularity:1s,points:86400" % apname)
|
||||
|
||||
|
@ -60,7 +60,7 @@ class BenchmarkMetricTest(base.ClientTestBase):
|
|||
|
||||
def test_benchmark_measures_add(self):
|
||||
apname = str(uuid.uuid4())
|
||||
# PREPARE AN ACHIVE POLICY
|
||||
# PREPARE AN ARCHIVE POLICY
|
||||
self.gnocchi("archive-policy", params="create %s "
|
||||
"--back-window 0 -d granularity:1s,points:86400" % apname)
|
||||
|
||||
|
@ -84,7 +84,7 @@ class BenchmarkMetricTest(base.ClientTestBase):
|
|||
|
||||
def test_benchmark_measures_show(self):
|
||||
apname = str(uuid.uuid4())
|
||||
# PREPARE AN ACHIVE POLICY
|
||||
# PREPARE AN ARCHIVE POLICY
|
||||
self.gnocchi("archive-policy", params="create %s "
|
||||
"--back-window 0 -d granularity:1s,points:86400" % apname)
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import uuid
|
||||
|
||||
from gnocchiclient.tests.functional import base
|
||||
|
@ -17,7 +20,7 @@ from gnocchiclient.tests.functional import base
|
|||
class MetricClientTest(base.ClientTestBase):
|
||||
def test_delete_several_metrics(self):
|
||||
apname = str(uuid.uuid4())
|
||||
# PREPARE AN ACHIVE POLICY
|
||||
# PREPARE AN ARCHIVE POLICY
|
||||
self.gnocchi("archive-policy", params="create %s "
|
||||
"--back-window 0 -d granularity:1s,points:86400" % apname)
|
||||
# Create 2 metrics
|
||||
|
@ -49,7 +52,7 @@ class MetricClientTest(base.ClientTestBase):
|
|||
metric2["id"])
|
||||
|
||||
def test_metric_scenario(self):
|
||||
# PREPARE AN ACHIVE POLICY
|
||||
# PREPARE AN ARCHIVE POLICY
|
||||
self.gnocchi("archive-policy", params="create metric-test "
|
||||
"--back-window 0 -d granularity:1s,points:86400")
|
||||
|
||||
|
@ -63,7 +66,7 @@ class MetricClientTest(base.ClientTestBase):
|
|||
metric["created_by_project_id"])
|
||||
self.assertEqual(self.clients.user_id, metric["created_by_user_id"])
|
||||
self.assertEqual('some-name', metric["name"])
|
||||
self.assertEqual('None', metric["resource"])
|
||||
self.assertEqual('None', metric["resource/id"])
|
||||
self.assertIn("metric-test", metric["archive_policy/name"])
|
||||
|
||||
# CREATE WITHOUT NAME
|
||||
|
@ -76,7 +79,7 @@ class MetricClientTest(base.ClientTestBase):
|
|||
metric["created_by_project_id"])
|
||||
self.assertEqual(self.clients.user_id, metric["created_by_user_id"])
|
||||
self.assertEqual('None', metric["name"])
|
||||
self.assertEqual('None', metric["resource"])
|
||||
self.assertEqual('None', metric["resource/id"])
|
||||
self.assertIn("metric-test", metric["archive_policy/name"])
|
||||
|
||||
# GET
|
||||
|
@ -96,6 +99,7 @@ class MetricClientTest(base.ClientTestBase):
|
|||
result = self.retry_gnocchi(
|
||||
5, 'measures', params=("show %s "
|
||||
"--aggregation mean "
|
||||
"--granularity 1 "
|
||||
"--start 2015-03-06T14:32:00 "
|
||||
"--stop 2015-03-06T14:36:00"
|
||||
) % metric["id"])
|
||||
|
@ -123,6 +127,18 @@ class MetricClientTest(base.ClientTestBase):
|
|||
'timestamp': '2015-03-06T14:34:12+00:00',
|
||||
'value': '12.0'}], measures)
|
||||
|
||||
# BATCHING
|
||||
measures = json.dumps({
|
||||
metric['id']: [{'timestamp': '2015-03-06T14:34:12',
|
||||
'value': 12}]})
|
||||
tmpfile = tempfile.NamedTemporaryFile(delete=False)
|
||||
self.addCleanup(os.remove, tmpfile.name)
|
||||
with tmpfile as f:
|
||||
f.write(measures.encode('utf8'))
|
||||
self.gnocchi('measures', params=("batch-metrics %s" % tmpfile.name))
|
||||
self.gnocchi('measures', params=("batch-metrics -"),
|
||||
input=measures.encode('utf8'))
|
||||
|
||||
# LIST
|
||||
result = self.gnocchi('metric', params="list")
|
||||
metrics = self.parser.listing(result)
|
||||
|
@ -167,7 +183,7 @@ class MetricClientTest(base.ClientTestBase):
|
|||
metric["created_by_project_id"])
|
||||
self.assertEqual(self.clients.user_id, metric["created_by_user_id"])
|
||||
self.assertEqual('metric-name', metric["name"])
|
||||
self.assertNotEqual('None', metric["resource"])
|
||||
self.assertNotEqual('None', metric["resource/id"])
|
||||
self.assertIn("metric-test", metric["archive_policy/name"])
|
||||
|
||||
# CREATE FAIL
|
||||
|
@ -209,7 +225,7 @@ class MetricClientTest(base.ClientTestBase):
|
|||
|
||||
# MEASURES AGGREGATION
|
||||
result = self.gnocchi(
|
||||
'measures', params=("--debug aggregation "
|
||||
'measures', params=("aggregation "
|
||||
"--query \"id='metric-res'\" "
|
||||
"--resource-type \"generic\" "
|
||||
"-m metric-name "
|
||||
|
@ -225,6 +241,20 @@ class MetricClientTest(base.ClientTestBase):
|
|||
'timestamp': '2015-03-06T14:34:12+00:00',
|
||||
'value': '12.0'}], measures)
|
||||
|
||||
# BATCHING
|
||||
measures = json.dumps({'metric-res': {'metric-name': [{
|
||||
'timestamp': '2015-03-06T14:34:12', 'value': 12
|
||||
}]}})
|
||||
tmpfile = tempfile.NamedTemporaryFile(delete=False)
|
||||
self.addCleanup(os.remove, tmpfile.name)
|
||||
with tmpfile as f:
|
||||
f.write(measures.encode('utf8'))
|
||||
|
||||
self.gnocchi('measures', params=("batch-resources-metrics %s" %
|
||||
tmpfile.name))
|
||||
self.gnocchi('measures', params=("batch-resources-metrics -"),
|
||||
input=measures.encode('utf8'))
|
||||
|
||||
# LIST
|
||||
result = self.gnocchi('metric', params="list")
|
||||
metrics = self.parser.listing(result)
|
||||
|
|
|
@ -13,11 +13,13 @@
|
|||
import uuid
|
||||
|
||||
from gnocchiclient.tests.functional import base
|
||||
from gnocchiclient import utils
|
||||
|
||||
|
||||
class ResourceClientTest(base.ClientTestBase):
|
||||
RESOURCE_ID = str(uuid.uuid4())
|
||||
RESOURCE_ID2 = str(uuid.uuid4())
|
||||
RAW_RESOURCE_ID2 = str(uuid.uuid4()) + "/foo"
|
||||
RESOURCE_ID2 = utils.encode_resource_id(RAW_RESOURCE_ID2)
|
||||
PROJECT_ID = str(uuid.uuid4())
|
||||
|
||||
def test_help(self):
|
||||
|
@ -36,7 +38,6 @@ class ResourceClientTest(base.ClientTestBase):
|
|||
result = self.gnocchi(
|
||||
u'resource', params=u"create %s --type generic" %
|
||||
self.RESOURCE_ID)
|
||||
resource = self.details_multiple(result)
|
||||
resource = self.details_multiple(result)[0]
|
||||
self.assertEqual(self.RESOURCE_ID, resource["id"])
|
||||
self.assertEqual('None', resource["project_id"])
|
||||
|
@ -95,20 +96,30 @@ class ResourceClientTest(base.ClientTestBase):
|
|||
# Search
|
||||
result = self.gnocchi('resource',
|
||||
params=("search --type generic "
|
||||
"--query 'project_id=%s'"
|
||||
"'project_id=%s'"
|
||||
) % self.PROJECT_ID)
|
||||
resource_list = self.parser.listing(result)[0]
|
||||
self.assertEqual(self.RESOURCE_ID, resource_list["id"])
|
||||
self.assertEqual(self.PROJECT_ID, resource_list["project_id"])
|
||||
self.assertEqual(resource["started_at"], resource_list["started_at"])
|
||||
|
||||
# UPDATE with Delete metric
|
||||
result = self.gnocchi(
|
||||
'resource', params=("update -t generic %s -a project_id:%s "
|
||||
"-d temperature" %
|
||||
(self.RESOURCE_ID, self.PROJECT_ID)))
|
||||
resource_updated = self.details_multiple(result)[0]
|
||||
self.assertNotIn("temperature", resource_updated["metrics"])
|
||||
|
||||
# CREATE 2
|
||||
result = self.gnocchi(
|
||||
'resource', params=("create %s -t generic "
|
||||
"-a project_id:%s"
|
||||
) % (self.RESOURCE_ID2, self.PROJECT_ID))
|
||||
) % (self.RAW_RESOURCE_ID2, self.PROJECT_ID))
|
||||
resource2 = self.details_multiple(result)[0]
|
||||
self.assertEqual(self.RESOURCE_ID2, resource2["id"])
|
||||
self.assertEqual(self.RAW_RESOURCE_ID2,
|
||||
resource2["original_resource_id"])
|
||||
self.assertEqual(self.PROJECT_ID, resource2["project_id"])
|
||||
self.assertNotEqual('None', resource2["started_at"])
|
||||
|
||||
|
@ -116,7 +127,7 @@ class ResourceClientTest(base.ClientTestBase):
|
|||
result = self.gnocchi('resource',
|
||||
params=("search "
|
||||
"-t generic "
|
||||
"--query 'project_id=%s' "
|
||||
"'project_id=%s' "
|
||||
"--sort started_at:asc "
|
||||
"--marker %s "
|
||||
"--limit 1"
|
||||
|
@ -156,3 +167,14 @@ class ResourceClientTest(base.ClientTestBase):
|
|||
resource_ids = [r['id'] for r in self.parser.listing(result)]
|
||||
self.assertNotIn(self.RESOURCE_ID, resource_ids)
|
||||
self.assertNotIn(self.RESOURCE_ID2, resource_ids)
|
||||
|
||||
# LIST THE RESOUCES TYPES
|
||||
resource_type = ('instance', 'generic', 'volume',
|
||||
'instance_disk', 'stack', 'identity')
|
||||
|
||||
result = self.gnocchi(
|
||||
'resource', params="list-types")
|
||||
result_list = self.parser.listing(result)
|
||||
type_from_list = [t['resource_type'] for t in result_list]
|
||||
for one_type in resource_type:
|
||||
self.assertIn(one_type, type_from_list)
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# 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.
|
||||
import json
|
||||
|
||||
from oslotest import base
|
||||
from requests import models
|
||||
|
||||
from gnocchiclient import exceptions
|
||||
|
||||
|
||||
class ExceptionsTest(base.BaseTestCase):
|
||||
def test_from_response_404(self):
|
||||
r = models.Response()
|
||||
r.status_code = 404
|
||||
r.headers['Content-Type'] = "application/json"
|
||||
r._content = json.dumps(
|
||||
{"description": "Archive policy rule foobar does not exist"}
|
||||
).encode('utf-8')
|
||||
exc = exceptions.from_response(r)
|
||||
self.assertIsInstance(exc, exceptions.ArchivePolicyRuleNotFound)
|
||||
|
||||
def test_from_response_keystone_401(self):
|
||||
r = models.Response()
|
||||
r.status_code = 401
|
||||
r.headers['Content-Type'] = "application/json"
|
||||
r._content = json.dumps({"error": {
|
||||
"message": "The request you have made requires authentication.",
|
||||
"code": 401, "title": "Unauthorized"}}
|
||||
).encode('utf-8')
|
||||
exc = exceptions.from_response(r)
|
||||
self.assertIsInstance(exc, exceptions.Unauthorized)
|
||||
self.assertEqual("The request you have made requires authentication.",
|
||||
exc.message)
|
||||
|
||||
def test_from_response_unknown_middleware(self):
|
||||
r = models.Response()
|
||||
r.status_code = 400
|
||||
r.headers['Content-Type'] = "application/json"
|
||||
r._content = json.dumps(
|
||||
{"unknown": "random message"}
|
||||
).encode('utf-8')
|
||||
exc = exceptions.from_response(r)
|
||||
self.assertIsInstance(exc, exceptions.ClientException)
|
||||
self.assertEqual('{"unknown": "random message"}', exc.message)
|
|
@ -23,6 +23,10 @@ class SearchQueryBuilderTest(base.BaseTestCase):
|
|||
self.assertEqual(expected, req)
|
||||
|
||||
def test_search_query_builder(self):
|
||||
self._do_test('foo=7EED6CC3-EDC8-48C9-8EF6-8A36B9ACC91C',
|
||||
{"=": {"foo": "7EED6CC3-EDC8-48C9-8EF6-8A36B9ACC91C"}})
|
||||
self._do_test('foo=7EED6CC3EDC848C98EF68A36B9ACC91C',
|
||||
{"=": {"foo": "7EED6CC3EDC848C98EF68A36B9ACC91C"}})
|
||||
self._do_test('foo=bar', {"=": {"foo": "bar"}})
|
||||
self._do_test('foo!=1', {"!=": {"foo": 1.0}})
|
||||
self._do_test('foo=True', {"=": {"foo": True}})
|
||||
|
@ -81,3 +85,14 @@ class SearchQueryBuilderTest(base.BaseTestCase):
|
|||
]},
|
||||
{"=": {"foo": "quote"}},
|
||||
]})
|
||||
|
||||
def test_dict_to_querystring(self):
|
||||
expected = ["start=2016-02-10T13%3A54%3A53%2B00%3A00"
|
||||
"&stop=2016-02-10T13%3A56%3A42%2B02%3A00",
|
||||
"stop=2016-02-10T13%3A56%3A42%2B02%3A00"
|
||||
"&start=2016-02-10T13%3A54%3A53%2B00%3A00"]
|
||||
|
||||
self.assertIn(utils.dict_to_querystring(
|
||||
{"start": "2016-02-10T13:54:53+00:00",
|
||||
"stop": "2016-02-10T13:56:42+02:00"}),
|
||||
expected)
|
||||
|
|
|
@ -12,8 +12,11 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import uuid
|
||||
|
||||
import pyparsing as pp
|
||||
import six
|
||||
from six.moves.urllib import parse as urllib_parse
|
||||
|
||||
uninary_operators = ("not", )
|
||||
binary_operator = (u">=", u"<=", u"!=", u">", u"<", u"=", u"==", u"eq", u"ne",
|
||||
|
@ -26,8 +29,9 @@ null = pp.Regex("None|none|null").setParseAction(pp.replaceWith(None))
|
|||
boolean = "False|True|false|true"
|
||||
boolean = pp.Regex(boolean).setParseAction(lambda t: t[0].lower() == "true")
|
||||
hex_string = lambda n: pp.Word(pp.hexnums, exact=n)
|
||||
uuid = pp.Combine(hex_string(8) + ("-" + hex_string(4)) * 3 +
|
||||
"-" + hex_string(12))
|
||||
uuid_string = pp.Combine(hex_string(8) +
|
||||
(pp.Optional("-") + hex_string(4)) * 3 +
|
||||
pp.Optional("-") + hex_string(12))
|
||||
number = r"[+-]?\d+(:?\.\d*)?(:?[eE][+-]?\d+)?"
|
||||
number = pp.Regex(number).setParseAction(lambda t: float(t[0]))
|
||||
identifier = pp.Word(pp.alphas, pp.alphanums + "_")
|
||||
|
@ -36,7 +40,7 @@ comparison_term = pp.Forward()
|
|||
in_list = pp.Group(pp.Suppress('[') +
|
||||
pp.Optional(pp.delimitedList(comparison_term)) +
|
||||
pp.Suppress(']'))("list")
|
||||
comparison_term << (null | boolean | uuid | identifier | number |
|
||||
comparison_term << (null | boolean | uuid_string | identifier | number |
|
||||
quoted_string | in_list)
|
||||
condition = pp.Group(comparison_term + operator + comparison_term)
|
||||
|
||||
|
@ -80,8 +84,17 @@ def _parsed_query2dict(parsed_query):
|
|||
return result
|
||||
|
||||
|
||||
class MalformedQuery(Exception):
|
||||
def __init__(self, reason):
|
||||
super(MalformedQuery, self).__init__(
|
||||
"Malformed Query: %s" % reason)
|
||||
|
||||
|
||||
def search_query_builder(query):
|
||||
parsed_query = expr.parseString(query)[0]
|
||||
try:
|
||||
parsed_query = expr.parseString(query, parseAll=True)[0]
|
||||
except pp.ParseException as e:
|
||||
raise MalformedQuery(six.text_type(e))
|
||||
return _parsed_query2dict(parsed_query)
|
||||
|
||||
|
||||
|
@ -112,6 +125,18 @@ def format_archive_policy(ap):
|
|||
format_string_list(ap, "aggregation_methods")
|
||||
|
||||
|
||||
def format_resource_for_metric(metric):
|
||||
# NOTE(sileht): Gnocchi < 2.0
|
||||
if 'resource' not in metric:
|
||||
return
|
||||
|
||||
if not metric['resource']:
|
||||
metric['resource/id'] = None
|
||||
del metric['resource']
|
||||
else:
|
||||
format_move_dict_to_root(metric, "resource")
|
||||
|
||||
|
||||
def dict_from_parsed_args(parsed_args, attrs):
|
||||
d = {}
|
||||
for attr in attrs:
|
||||
|
@ -122,6 +147,27 @@ def dict_from_parsed_args(parsed_args, attrs):
|
|||
|
||||
|
||||
def dict_to_querystring(objs):
|
||||
return "&".join(["%s=%s" % (k, v)
|
||||
return "&".join(["%s=%s" % (k, urllib_parse.quote(six.text_type(v)))
|
||||
for k, v in objs.items()
|
||||
if v is not None])
|
||||
|
||||
|
||||
# uuid5 namespace for id transformation.
|
||||
# NOTE(chdent): This UUID must stay the same, forever, across all
|
||||
# of gnocchi to preserve its value as a URN namespace.
|
||||
RESOURCE_ID_NAMESPACE = uuid.UUID('0a7a15ff-aa13-4ac2-897c-9bdf30ce175b')
|
||||
|
||||
|
||||
def encode_resource_id(value):
|
||||
try:
|
||||
try:
|
||||
return str(uuid.UUID(value))
|
||||
except ValueError:
|
||||
if len(value) <= 255:
|
||||
if six.PY2:
|
||||
value = value.encode('utf-8')
|
||||
return str(uuid.uuid5(RESOURCE_ID_NAMESPACE, value))
|
||||
raise ValueError(
|
||||
'transformable resource id >255 max allowed characters')
|
||||
except Exception as e:
|
||||
raise ValueError(e)
|
||||
|
|
|
@ -23,6 +23,8 @@ from gnocchiclient.v1 import base
|
|||
class MetricManager(base.Manager):
|
||||
metric_url = "v1/metric/"
|
||||
resource_url = "v1/resource/generic/%s/metric/"
|
||||
metric_batch_url = "v1/batch/metrics/measures"
|
||||
resources_batch_url = "v1/batch/resources/metrics/measures"
|
||||
|
||||
def list(self):
|
||||
"""List archive metrics
|
||||
|
@ -51,6 +53,7 @@ class MetricManager(base.Manager):
|
|||
self._ensure_metric_is_uuid(metric)
|
||||
url = self.metric_url + metric
|
||||
else:
|
||||
resource_id = utils.encode_resource_id(resource_id)
|
||||
url = (self.resource_url % resource_id) + metric
|
||||
return self._get(url).json()
|
||||
|
||||
|
@ -79,6 +82,7 @@ class MetricManager(base.Manager):
|
|||
raise TypeError("metric_name is required if resource_id is set")
|
||||
|
||||
del metric['resource_id']
|
||||
resource_id = utils.encode_resource_id(resource_id)
|
||||
metric = {metric_name: metric}
|
||||
metric = self._post(
|
||||
self.resource_url % resource_id,
|
||||
|
@ -99,6 +103,7 @@ class MetricManager(base.Manager):
|
|||
self._ensure_metric_is_uuid(metric)
|
||||
url = self.metric_url + metric
|
||||
else:
|
||||
resource_id = utils.encode_resource_id(resource_id)
|
||||
url = self.resource_url % resource_id + metric
|
||||
self._delete(url)
|
||||
|
||||
|
@ -117,13 +122,39 @@ class MetricManager(base.Manager):
|
|||
self._ensure_metric_is_uuid(metric)
|
||||
url = self.metric_url + metric + "/measures"
|
||||
else:
|
||||
resource_id = utils.encode_resource_id(resource_id)
|
||||
url = self.resource_url % resource_id + metric + "/measures"
|
||||
return self._post(
|
||||
url, headers={'Content-Type': "application/json"},
|
||||
data=jsonutils.dumps(measures))
|
||||
|
||||
def batch_metrics_measures(self, measures):
|
||||
"""Add measurements to metrics
|
||||
|
||||
:param measures: measurements
|
||||
:type dict(metric_id: list of dict(timestamp=timestamp, value=float))
|
||||
"""
|
||||
|
||||
return self._post(
|
||||
self.metric_batch_url,
|
||||
headers={'Content-Type': "application/json"},
|
||||
data=jsonutils.dumps(measures))
|
||||
|
||||
def batch_resources_metrics_measures(self, measures):
|
||||
"""Add measurements to named metrics if resources
|
||||
|
||||
:param measures: measurements
|
||||
:type dict(resource_id: dict(metric_name:
|
||||
list of dict(timestamp=timestamp, value=float)))
|
||||
"""
|
||||
|
||||
return self._post(
|
||||
self.resources_batch_url,
|
||||
headers={'Content-Type': "application/json"},
|
||||
data=jsonutils.dumps(measures))
|
||||
|
||||
def get_measures(self, metric, start=None, stop=None, aggregation=None,
|
||||
resource_id=None, **kwargs):
|
||||
granularity=None, resource_id=None, **kwargs):
|
||||
"""Get measurements of a metric
|
||||
|
||||
:param metric: ID or Name of the metric
|
||||
|
@ -134,6 +165,8 @@ class MetricManager(base.Manager):
|
|||
:type stop: timestamp
|
||||
:param aggregation: aggregation to retrieve
|
||||
:type aggregation: str
|
||||
:param granularity: granularity to retrieve (in seconds)
|
||||
:type granularity: int
|
||||
:param resource_id: ID of the resource (required
|
||||
to get a metric by name)
|
||||
:type resource_id: str
|
||||
|
@ -147,12 +180,14 @@ class MetricManager(base.Manager):
|
|||
if isinstance(stop, datetime.datetime):
|
||||
stop = stop.isoformat()
|
||||
|
||||
params = dict(start=start, stop=stop, aggregation=aggregation)
|
||||
params = dict(start=start, stop=stop, aggregation=aggregation,
|
||||
granularity=granularity)
|
||||
params.update(kwargs)
|
||||
if resource_id is None:
|
||||
self._ensure_metric_is_uuid(metric)
|
||||
url = self.metric_url + metric + "/measures"
|
||||
else:
|
||||
resource_id = utils.encode_resource_id(resource_id)
|
||||
url = self.resource_url % resource_id + metric + "/measures"
|
||||
return self._get(url, params=params).json()
|
||||
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
from cliff import command
|
||||
from cliff import lister
|
||||
from cliff import show
|
||||
|
@ -54,6 +57,7 @@ class CliMetricShow(CliMetricWithResourceID, show.ShowOne):
|
|||
resource_id=parsed_args.resource_id)
|
||||
utils.format_archive_policy(metric["archive_policy"])
|
||||
utils.format_move_dict_to_root(metric, "archive_policy")
|
||||
utils.format_resource_for_metric(metric)
|
||||
return self.dict2columns(metric)
|
||||
|
||||
|
||||
|
@ -88,6 +92,7 @@ class CliMetricCreate(CliMetricCreateBase):
|
|||
metric = self.app.client.metric.create(metric)
|
||||
utils.format_archive_policy(metric["archive_policy"])
|
||||
utils.format_move_dict_to_root(metric, "archive_policy")
|
||||
utils.format_resource_for_metric(metric)
|
||||
return self.dict2columns(metric)
|
||||
|
||||
|
||||
|
@ -121,6 +126,8 @@ class CliMeasuresShow(CliMetricWithResourceID, lister.Lister):
|
|||
help="beginning of the period")
|
||||
parser.add_argument("--stop",
|
||||
help="end of the period")
|
||||
parser.add_argument("--granularity",
|
||||
help="granularity to retrieve (in seconds)")
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
|
@ -130,6 +137,7 @@ class CliMeasuresShow(CliMetricWithResourceID, lister.Lister):
|
|||
aggregation=parsed_args.aggregation,
|
||||
start=parsed_args.start,
|
||||
stop=parsed_args.stop,
|
||||
granularity=parsed_args.granularity,
|
||||
)
|
||||
return self.COLS, measures
|
||||
|
||||
|
@ -164,6 +172,35 @@ class CliMeasuresAdd(CliMeasuresAddBase):
|
|||
)
|
||||
|
||||
|
||||
class CliMeasuresBatch(command.Command):
|
||||
def stdin_or_file(self, value):
|
||||
if value == "-":
|
||||
return sys.stdin
|
||||
else:
|
||||
return open(value, 'r')
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliMeasuresBatch, self).get_parser(prog_name)
|
||||
parser.add_argument("file", type=self.stdin_or_file,
|
||||
help=("File containing measurements to batch or "
|
||||
"- for stdin (see Gnocchi REST API docs for "
|
||||
"the format"))
|
||||
return parser
|
||||
|
||||
|
||||
class CliMetricsMeasuresBatch(CliMeasuresBatch):
|
||||
def take_action(self, parsed_args):
|
||||
with parsed_args.file as f:
|
||||
self.app.client.metric.batch_metrics_measures(json.load(f))
|
||||
|
||||
|
||||
class CliResourcesMetricsMeasuresBatch(CliMeasuresBatch):
|
||||
def take_action(self, parsed_args):
|
||||
with parsed_args.file as f:
|
||||
self.app.client.metric.batch_resources_metrics_measures(
|
||||
json.load(f))
|
||||
|
||||
|
||||
class CliMeasuresAggregation(lister.Lister):
|
||||
"""Get measurements of aggregated metrics"""
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
from oslo_serialization import jsonutils
|
||||
from six.moves.urllib import parse as urllib_parse
|
||||
|
||||
from gnocchiclient import utils
|
||||
from gnocchiclient.v1 import base
|
||||
|
||||
|
||||
|
@ -51,8 +52,9 @@ class ResourceManager(base.Manager):
|
|||
:type history: bool
|
||||
:param limit: maximum number of resources to return
|
||||
:type limit: int
|
||||
:param marker: the last item of the previous page; we returns the next
|
||||
:param marker: the last item of the previous page; we return the next
|
||||
results after this value.
|
||||
:type marker: str
|
||||
:param sorts: list of resource attributes to order by. (example
|
||||
["user_id:desc-nullslast", "project_id:asc"]
|
||||
:type sorts: list of str
|
||||
|
@ -72,6 +74,7 @@ class ResourceManager(base.Manager):
|
|||
:type history: bool
|
||||
"""
|
||||
history = "/history" if history else ""
|
||||
resource_id = utils.encode_resource_id(resource_id)
|
||||
url = self.url + "%s/%s%s" % (resource_type, resource_id, history)
|
||||
return self._get(url).json()
|
||||
|
||||
|
@ -89,11 +92,13 @@ class ResourceManager(base.Manager):
|
|||
:type limit: int
|
||||
:param marker: the last item of the previous page; we returns the next
|
||||
results after this value.
|
||||
:type marker: str
|
||||
:param sorts: list of resource attributes to order by. (example
|
||||
["user_id:desc-nullslast", "project_id:asc"]
|
||||
:type sorts: list of str
|
||||
"""
|
||||
qs = _get_pagination_options(details, False, limit, marker, sorts)
|
||||
resource_id = utils.encode_resource_id(resource_id)
|
||||
url = "%s%s/%s/history?%s" % (self.url, resource_type, resource_id, qs)
|
||||
return self._get(url).json()
|
||||
|
||||
|
@ -121,6 +126,7 @@ class ResourceManager(base.Manager):
|
|||
:type resource: dict
|
||||
"""
|
||||
|
||||
resource_id = utils.encode_resource_id(resource_id)
|
||||
return self._patch(
|
||||
self.url + resource_type + "/" + resource_id,
|
||||
headers={'Content-Type': "application/json"},
|
||||
|
@ -132,6 +138,7 @@ class ResourceManager(base.Manager):
|
|||
:param resource_id: ID of the resource
|
||||
:type resource_id: str
|
||||
"""
|
||||
resource_id = utils.encode_resource_id(resource_id)
|
||||
self._delete(self.url + "generic/" + resource_id)
|
||||
|
||||
def search(self, resource_type="generic", query=None, details=False,
|
||||
|
@ -139,7 +146,7 @@ class ResourceManager(base.Manager):
|
|||
"""List resources
|
||||
|
||||
:param resource_type: Type of the resource
|
||||
:param resource_type: str
|
||||
:type resource_type: str
|
||||
:param query: The query dictionary
|
||||
:type query: dict
|
||||
:param details: Show all attributes of resources
|
||||
|
@ -150,6 +157,7 @@ class ResourceManager(base.Manager):
|
|||
:type limit: int
|
||||
:param marker: the last item of the previous page; we returns the next
|
||||
results after this value.
|
||||
:type marker: str
|
||||
:param sorts: list of resource attributes to order by. (example
|
||||
["user_id:desc-nullslast", "project_id:asc"]
|
||||
:type sorts: list of str
|
||||
|
@ -165,3 +173,13 @@ class ResourceManager(base.Manager):
|
|||
return self._post(
|
||||
url, headers={'Content-Type': "application/json"},
|
||||
data=jsonutils.dumps(query)).json()
|
||||
|
||||
def list_types(self):
|
||||
"""List the resource types supported by gnocchi"""
|
||||
# (Note/jzl)Based on the discussion result, keep the keyword
|
||||
# 'resource-type' and use the command 'resource list-types' to
|
||||
# list the types supported by gnocchi.
|
||||
# Opened a reminding bug to me to handle with it,
|
||||
# when resource-type is ready
|
||||
# https://bugs.launchpad.net/python-gnocchiclient/+bug/1535176
|
||||
return self._get(self.url).json()
|
||||
|
|
|
@ -22,6 +22,7 @@ class CliResourceList(lister.Lister):
|
|||
|
||||
COLS = ('id', 'type',
|
||||
'project_id', 'user_id',
|
||||
'original_resource_id',
|
||||
'started_at', 'ended_at',
|
||||
'revision_start', 'revision_end')
|
||||
|
||||
|
@ -87,7 +88,7 @@ class CliResourceSearch(CliResourceList):
|
|||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliResourceSearch, self).get_parser(prog_name)
|
||||
parser.add_argument("--query", help="Query"),
|
||||
parser.add_argument("query", help="Query")
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
|
@ -140,11 +141,8 @@ class CliResourceCreate(show.ShowOne):
|
|||
default=[],
|
||||
help="name:id of a metric to add"),
|
||||
parser.add_argument(
|
||||
"-n", "--create-metric", action='append',
|
||||
"-n", "--create-metric", action='append', default=[],
|
||||
help="name:archive_policy_name of a metric to create"),
|
||||
parser.add_argument("-d", "--delete-metric", action='append',
|
||||
default=[],
|
||||
help="Name of a metric to delete"),
|
||||
return parser
|
||||
|
||||
def _resource_from_args(self, parsed_args, update=False):
|
||||
|
@ -157,19 +155,19 @@ class CliResourceCreate(show.ShowOne):
|
|||
resource[attr] = value
|
||||
if (parsed_args.add_metric
|
||||
or parsed_args.create_metric
|
||||
or parsed_args.delete_metric):
|
||||
or (update and parsed_args.delete_metric)):
|
||||
if update:
|
||||
r = self.app.client.resource.get(parsed_args.resource_type,
|
||||
parsed_args.resource_id)
|
||||
default = r['metrics']
|
||||
for metric_name in parsed_args.delete_metric:
|
||||
default.pop(metric_name, None)
|
||||
else:
|
||||
default = {}
|
||||
resource['metrics'] = default
|
||||
for metric in parsed_args.add_metric:
|
||||
name, _, value = metric.partition(":")
|
||||
resource['metrics'][name] = value
|
||||
for metric in parsed_args.delete_metric:
|
||||
resource['metrics'].pop(name, None)
|
||||
for metric in parsed_args.create_metric:
|
||||
name, _, value = metric.partition(":")
|
||||
if value is "":
|
||||
|
@ -190,6 +188,13 @@ class CliResourceCreate(show.ShowOne):
|
|||
class CliResourceUpdate(CliResourceCreate):
|
||||
"""Update a resource"""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliResourceUpdate, self).get_parser(prog_name)
|
||||
parser.add_argument("-d", "--delete-metric", action='append',
|
||||
default=[],
|
||||
help="Name of a metric to delete"),
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resource = self._resource_from_args(parsed_args, update=True)
|
||||
res = self.app.client.resource.update(
|
||||
|
@ -211,3 +216,14 @@ class CliResourceDelete(command.Command):
|
|||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.resource.delete(parsed_args.resource_id)
|
||||
|
||||
|
||||
class CliResourceTypeList(lister.Lister):
|
||||
"""List the resource types that gnocchi supports"""
|
||||
|
||||
COLS = ('resource_type',
|
||||
'resource_controller_url')
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resources = self.app.client.resource.list_types()
|
||||
return self.COLS, list(resources.items())
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
# process, which may cause wedges in the gate later.
|
||||
|
||||
pbr<2.0,>=1.4
|
||||
Babel>=1.3
|
||||
cliff>=1.14.0 # Apache-2.0
|
||||
oslo.i18n>=1.5.0 # Apache-2.0
|
||||
oslo.serialization>=1.4.0 # Apache-2.0
|
||||
oslo.utils>=2.0.0 # Apache-2.0
|
||||
keystoneauth1>=1.0.0
|
||||
|
|
|
@ -18,36 +18,23 @@ clean_exit () {
|
|||
}
|
||||
|
||||
GNOCCHI_DATA=`mktemp -d /tmp/gnocchi-data-XXXXX`
|
||||
MYSQL_DATA=`mktemp -d /tmp/gnocchi-mysql-XXXXX`
|
||||
trap "clean_exit \"$GNOCCHI_DATA\" \"$MYSQL_DATA\"" EXIT
|
||||
|
||||
mkfifo ${MYSQL_DATA}/out
|
||||
PATH=$PATH:/usr/libexec
|
||||
mysqld --no-defaults --datadir=${MYSQL_DATA} --pid-file=${MYSQL_DATA}/mysql.pid --socket=${MYSQL_DATA}/mysql.socket --skip-networking --skip-grant-tables &> ${MYSQL_DATA}/out &
|
||||
# Wait for MySQL to start listening to connections
|
||||
wait_for_line "mysqld: ready for connections." ${MYSQL_DATA}/out
|
||||
export GNOCCHI_TEST_INDEXER_URL="mysql+pymysql://root@localhost/test?unix_socket=${MYSQL_DATA}/mysql.socket&charset=utf8"
|
||||
mysql --no-defaults -S ${MYSQL_DATA}/mysql.socket -e 'CREATE DATABASE test;'
|
||||
trap "clean_exit \"$GNOCCHI_DATA\"" EXIT
|
||||
|
||||
source $(which overtest) mysql
|
||||
|
||||
mkfifo ${GNOCCHI_DATA}/out
|
||||
echo '{"default": ""}' > ${GNOCCHI_DATA}/policy.json
|
||||
cat > ${GNOCCHI_DATA}/gnocchi.conf <<EOF
|
||||
[oslo_policy]
|
||||
policy_file = ${GNOCCHI_DATA}/policy.json
|
||||
policy_file = ${VIRTUAL_ENV}/etc/gnocchi/policy.json
|
||||
[api]
|
||||
paste_config = ${VIRTUAL_ENV}/etc/gnocchi/api-paste.ini
|
||||
[storage]
|
||||
metric_processing_delay = 1
|
||||
file_basepath = ${GNOCCHI_DATA}
|
||||
driver = file
|
||||
coordination_url = file://${GNOCCHI_DATA}
|
||||
[indexer]
|
||||
url = mysql+pymysql://root@localhost/test?unix_socket=${MYSQL_DATA}/mysql.socket&charset=utf8
|
||||
EOF
|
||||
cat <<EOF > ${GNOCCHI_DATA}/api-paste.ini
|
||||
[pipeline:main]
|
||||
pipeline = gnocchi
|
||||
[app:gnocchi]
|
||||
paste.app_factory = gnocchi.rest.app:app_factory
|
||||
url = ${OVERTEST_URL/#mysql:/mysql+pymysql:}
|
||||
EOF
|
||||
gnocchi-upgrade --config-file ${GNOCCHI_DATA}/gnocchi.conf
|
||||
gnocchi-metricd --config-file ${GNOCCHI_DATA}/gnocchi.conf &>/dev/null &
|
||||
|
|
31
setup.cfg
31
setup.cfg
|
@ -29,6 +29,19 @@ console_scripts =
|
|||
keystoneauth1.plugin =
|
||||
gnocchi-noauth = gnocchiclient.noauth:GnocchiNoAuthLoader
|
||||
|
||||
[extras]
|
||||
test =
|
||||
coverage>=3.6
|
||||
python-subunit>=0.0.18
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
tempest-lib>=0.6.1
|
||||
testrepository>=0.0.18
|
||||
testtools>=1.4.0
|
||||
|
||||
doc =
|
||||
sphinx!=1.2.0,!=1.3b1,>=1.1.2
|
||||
oslosphinx>=2.5.0 # Apache-2.0
|
||||
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
|
@ -38,16 +51,10 @@ all_files = 1
|
|||
[upload_sphinx]
|
||||
upload-dir = doc/build/html
|
||||
|
||||
[compile_catalog]
|
||||
directory = gnocchiclient/locale
|
||||
domain = gnocchiclient
|
||||
[pbr]
|
||||
autodoc_index_modules = true
|
||||
autodoc_exclude_modules =
|
||||
gnocchiclient.tests.*
|
||||
|
||||
[update_catalog]
|
||||
domain = gnocchiclient
|
||||
output_dir = gnocchiclient/locale
|
||||
input_file = gnocchiclient/locale/gnocchiclient.pot
|
||||
|
||||
[extract_messages]
|
||||
keywords = _ gettext ngettext l_ lazy_gettext
|
||||
mapping_file = babel.cfg
|
||||
output_file = gnocchiclient/locale/gnocchiclient.pot
|
||||
[wheel]
|
||||
universal = 1
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
hacking<0.11,>=0.10.0
|
||||
|
||||
coverage>=3.6
|
||||
discover
|
||||
python-subunit>=0.0.18
|
||||
sphinx!=1.2.0,!=1.3b1,>=1.1.2
|
||||
oslosphinx>=2.5.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
tempest-lib>=0.6.1
|
||||
testrepository>=0.0.18
|
||||
testscenarios>=0.4
|
||||
testtools>=1.4.0
|
||||
http://tarballs.openstack.org/gnocchi/gnocchi-master.tar.gz#egg=gnocchi
|
||||
# FIXME(sileht): should be in gnocchi ?
|
||||
keystonemiddleware
|
22
tox.ini
22
tox.ini
|
@ -5,36 +5,42 @@ skipsdist = True
|
|||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
install_command = pip install -U --allow-external gnocchi --allow-insecure gnocchi {opts} {packages}
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
GNOCCHI_CLIENT_EXEC_DIR={envdir}/bin
|
||||
passenv = GNOCCHI_* OS_TEST_TIMEOUT OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_LOG_CAPTURE
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
deps = .[test]
|
||||
http://tarballs.openstack.org/gnocchi/gnocchi-master.tar.gz#egg=gnocchi[mysql,file]
|
||||
overtest
|
||||
commands = {toxinidir}/setup-tests.sh python setup.py test --slowest --testr-args='{posargs}'
|
||||
|
||||
[testenv:pep8]
|
||||
deps = hacking<0.11,>=0.10.0
|
||||
commands = flake8
|
||||
|
||||
[testenv:venv]
|
||||
deps = .[test,doc]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:cover]
|
||||
commands = python setup.py test --coverage --testr-args='{posargs}'
|
||||
|
||||
[testenv:docs]
|
||||
deps = .[test,doc]
|
||||
commands =
|
||||
rm -rf doc/source/ref
|
||||
python setup.py build_sphinx
|
||||
|
||||
[testenv:docs-gnocchi.xyz]
|
||||
deps = .[test,doc]
|
||||
sphinx_rtd_theme
|
||||
setenv = SPHINX_HTML_THEME=sphinx_rtd_theme
|
||||
commands = python setup.py build_sphinx
|
||||
|
||||
[testenv:debug]
|
||||
commands = oslo_debug_helper {posargs}
|
||||
commands = {toxinidir}/setup-tests.sh oslo_debug_helper {posargs}
|
||||
|
||||
[flake8]
|
||||
# E123, E125 skipped as they are invalid PEP-8.
|
||||
|
||||
show-source = True
|
||||
ignore = E123,E125
|
||||
builtins = _
|
||||
ignore =
|
||||
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build
|
||||
|
|
Loading…
Reference in New Issue