Skeleton for API server
This changeset introduces a framework for the API service, including the dependency list, a couple of simple API methods, a test suite, and documentation for starting the development server. Change-Id: I4a496c600b7e6a0a8c70113b1d099614febd899d Signed-off-by: Doug Hellmann <doug.hellmann@dreamhost.com>
This commit is contained in:
parent
ab1437fbbc
commit
2eebd4a8bd
36
ceilometer/api/__init__.py
Normal file
36
ceilometer/api/__init__.py
Normal file
@ -0,0 +1,36 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import flask.helpers
|
||||
|
||||
from ceilometer.openstack.common import cfg
|
||||
from ceilometer.openstack.common import jsonutils
|
||||
|
||||
# Replace the json module used by flask with the one from
|
||||
# openstack.common so we can take advantage of the fact that it knows
|
||||
# how to serialize more complex objects.
|
||||
flask.helpers.json = jsonutils
|
||||
|
||||
# Register options for the service
|
||||
API_SERVICE_OPTS = [
|
||||
cfg.IntOpt('metering_api_port',
|
||||
default=9000,
|
||||
help='The port for the ceilometer API server',
|
||||
),
|
||||
]
|
||||
cfg.CONF.register_opts(API_SERVICE_OPTS)
|
27
ceilometer/api/__main__.py
Normal file
27
ceilometer/api/__main__.py
Normal file
@ -0,0 +1,27 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Set up the development API server.
|
||||
"""
|
||||
|
||||
from ceilometer.api.app import app
|
||||
from ceilometer.openstack.common import cfg
|
||||
|
||||
if __name__ == '__main__':
|
||||
cfg.CONF()
|
||||
app.debug = True
|
||||
app.run(host='0.0.0.0', port=cfg.CONF.metering_api_port)
|
43
ceilometer/api/app.py
Normal file
43
ceilometer/api/app.py
Normal file
@ -0,0 +1,43 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Set up the API server application instance
|
||||
"""
|
||||
|
||||
import flask
|
||||
|
||||
from ceilometer.openstack.common import cfg
|
||||
from ceilometer import storage
|
||||
from ceilometer.api import v1
|
||||
|
||||
app = flask.Flask('ceilometer.api')
|
||||
app.register_blueprint(v1.blueprint, url_prefix='/v1')
|
||||
|
||||
storage.register_opts(cfg.CONF)
|
||||
|
||||
|
||||
@app.before_request
|
||||
def attach_config():
|
||||
flask.request.cfg = cfg.CONF
|
||||
storage_engine = storage.get_engine(cfg.CONF)
|
||||
flask.request.storage_engine = storage_engine
|
||||
flask.request.storage_conn = storage_engine.get_connection(cfg.CONF)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def hello():
|
||||
return 'Hello World!'
|
42
ceilometer/api/v1.py
Normal file
42
ceilometer/api/v1.py
Normal file
@ -0,0 +1,42 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Blueprint for version 1 of API.
|
||||
"""
|
||||
|
||||
import flask
|
||||
|
||||
|
||||
blueprint = flask.Blueprint('v1', __name__)
|
||||
|
||||
## APIs for working with resources.
|
||||
|
||||
|
||||
@blueprint.route('/resources', defaults={'source': None})
|
||||
@blueprint.route('/sources/<source>/resources')
|
||||
def list_resources(source):
|
||||
resources = list(flask.request.storage_conn.get_resources(source=source))
|
||||
return flask.jsonify(resources=resources)
|
||||
|
||||
## APIs for working with users.
|
||||
|
||||
|
||||
@blueprint.route('/users', defaults={'source': None})
|
||||
@blueprint.route('/sources/<source>/users')
|
||||
def list_users(source):
|
||||
users = list(flask.request.storage_conn.get_users(source=source))
|
||||
return flask.jsonify(users=users)
|
@ -20,8 +20,8 @@ from nova import exception
|
||||
|
||||
from ceilometer.openstack.common import log
|
||||
|
||||
from .. import counter
|
||||
from .. import plugin
|
||||
from ceilometer import counter
|
||||
from ceilometer import plugin
|
||||
|
||||
|
||||
class FloatingIPPollster(plugin.PollsterBase):
|
||||
|
0
ceilometer/tests/__init__.py
Normal file
0
ceilometer/tests/__init__.py
Normal file
77
ceilometer/tests/api.py
Normal file
77
ceilometer/tests/api.py
Normal file
@ -0,0 +1,77 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Base classes for API tests.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import flask
|
||||
from ming import mim
|
||||
import mock
|
||||
|
||||
from ceilometer.api import v1
|
||||
from ceilometer.storage import impl_mongodb
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Connection(impl_mongodb.Connection):
|
||||
|
||||
def _get_connection(self, conf):
|
||||
# Use a real MongoDB server if we can connect, but fall back
|
||||
# to a Mongo-in-memory connection if we cannot.
|
||||
self.force_mongo = bool(int(os.environ.get('CEILOMETER_TEST_LIVE', 0)))
|
||||
if self.force_mongo:
|
||||
try:
|
||||
return super(Connection, self)._get_connection(conf)
|
||||
except:
|
||||
LOG.debug('Unable to connect to mongod')
|
||||
raise
|
||||
else:
|
||||
LOG.debug('Unable to connect to mongod, falling back to MIM')
|
||||
return mim.Connection()
|
||||
|
||||
|
||||
class TestBase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestBase, self).setUp()
|
||||
self.app = flask.Flask('test')
|
||||
self.app.register_blueprint(v1.blueprint)
|
||||
self.test_app = self.app.test_client()
|
||||
self.conf = mock.Mock()
|
||||
self.conf.metering_storage_engine = 'mongodb'
|
||||
self.conf.mongodb_host = 'localhost'
|
||||
self.conf.mongodb_port = 27017
|
||||
self.conf.mongodb_dbname = 'testdb'
|
||||
self.conn = Connection(self.conf)
|
||||
self.conn.conn.drop_database('testdb')
|
||||
self.conn.conn['testdb']
|
||||
|
||||
@self.app.before_request
|
||||
def attach_storage_connection():
|
||||
flask.request.storage_conn = self.conn
|
||||
return
|
||||
|
||||
def get(self, path):
|
||||
rv = self.test_app.get(path)
|
||||
data = json.loads(rv.data)
|
||||
return data
|
@ -19,12 +19,12 @@
|
||||
Installing and Running the Development Version
|
||||
================================================
|
||||
|
||||
Ceilometer has two daemons. The :term:`agent` runs on the Nova compute
|
||||
node(s) and the :term:`collector` runs on the cloud's management
|
||||
node(s). In a development environment created by devstack_, these two
|
||||
are typically the same server. They do not have to be, though, so some
|
||||
of the instructions below are duplicated. Skip the steps you have
|
||||
already done.
|
||||
Ceilometer has three daemons. The :term:`agent` runs on the Nova
|
||||
compute node(s). The :term:`collector` and API server run on the
|
||||
cloud's management node(s). In a development environment created by
|
||||
devstack_, these two are typically the same server. They do not have
|
||||
to be, though, so some of the instructions below are duplicated. Skip
|
||||
the steps you have already done.
|
||||
|
||||
.. _devstack: http://www.devstack.org/
|
||||
|
||||
@ -145,3 +145,33 @@ Installing the Compute Agent
|
||||
stderr, so you may want to run this step using a screen session
|
||||
or other tool for maintaining a long-running program in the
|
||||
background.
|
||||
|
||||
|
||||
Installing the API Server
|
||||
=========================
|
||||
|
||||
.. index::
|
||||
double: installing; API
|
||||
|
||||
1. Clone the ceilometer git repository to the server::
|
||||
|
||||
$ cd /opt/stack
|
||||
$ git clone https://github.com/stackforge/ceilometer.git
|
||||
|
||||
2. As a user with ``root`` permissions or ``sudo`` privileges, run the
|
||||
ceilometer installer::
|
||||
|
||||
$ cd ceilometer
|
||||
$ sudo python setup.py install
|
||||
|
||||
3. Start the API server.
|
||||
|
||||
::
|
||||
|
||||
$ python -m ceilometer.api
|
||||
|
||||
.. note::
|
||||
|
||||
The development version of the API server logs to stderr, so you
|
||||
may want to run this step using a screen session or other tool
|
||||
for maintaining a long-running program in the background.
|
||||
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
0
tests/api/__init__.py
Normal file
0
tests/api/__init__.py
Normal file
0
tests/api/v1/__init__.py
Normal file
0
tests/api/v1/__init__.py
Normal file
113
tests/api/v1/test_list_resources.py
Normal file
113
tests/api/v1/test_list_resources.py
Normal file
@ -0,0 +1,113 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Test listing resources.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from ceilometer import counter
|
||||
from ceilometer import meter
|
||||
|
||||
from ceilometer.tests import api as tests_api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TestListResources(tests_api.TestBase):
|
||||
|
||||
def test_empty(self):
|
||||
data = self.get('/resources')
|
||||
self.assertEquals({'resources': []}, data)
|
||||
|
||||
def test_instances(self):
|
||||
counter1 = counter.Counter(
|
||||
'test',
|
||||
'instance',
|
||||
'cumulative',
|
||||
1,
|
||||
'user-id',
|
||||
'project-id',
|
||||
'resource-id',
|
||||
timestamp=datetime.datetime(2012, 7, 2, 10, 40),
|
||||
duration=0,
|
||||
resource_metadata={'display_name': 'test-server',
|
||||
'tag': 'self.counter',
|
||||
}
|
||||
)
|
||||
msg = meter.meter_message_from_counter(counter1)
|
||||
self.conn.record_metering_data(msg)
|
||||
|
||||
counter2 = counter.Counter(
|
||||
'test',
|
||||
'instance',
|
||||
'cumulative',
|
||||
1,
|
||||
'user-id',
|
||||
'project-id',
|
||||
'resource-id-alternate',
|
||||
timestamp=datetime.datetime(2012, 7, 2, 10, 41),
|
||||
duration=0,
|
||||
resource_metadata={'display_name': 'test-server',
|
||||
'tag': 'self.counter2',
|
||||
}
|
||||
)
|
||||
msg2 = meter.meter_message_from_counter(counter2)
|
||||
self.conn.record_metering_data(msg2)
|
||||
|
||||
data = self.get('/resources')
|
||||
self.assertEquals(2, len(data['resources']))
|
||||
|
||||
def test_with_source(self):
|
||||
counter1 = counter.Counter(
|
||||
'test_list_resources',
|
||||
'instance',
|
||||
'cumulative',
|
||||
1,
|
||||
'user-id',
|
||||
'project-id',
|
||||
'resource-id',
|
||||
timestamp=datetime.datetime(2012, 7, 2, 10, 40),
|
||||
duration=0,
|
||||
resource_metadata={'display_name': 'test-server',
|
||||
'tag': 'self.counter',
|
||||
}
|
||||
)
|
||||
msg = meter.meter_message_from_counter(counter1)
|
||||
self.conn.record_metering_data(msg)
|
||||
|
||||
counter2 = counter.Counter(
|
||||
'not-test',
|
||||
'instance',
|
||||
'cumulative',
|
||||
1,
|
||||
'user-id2',
|
||||
'project-id',
|
||||
'resource-id-alternate',
|
||||
timestamp=datetime.datetime(2012, 7, 2, 10, 41),
|
||||
duration=0,
|
||||
resource_metadata={'display_name': 'test-server',
|
||||
'tag': 'self.counter2',
|
||||
}
|
||||
)
|
||||
msg2 = meter.meter_message_from_counter(counter2)
|
||||
self.conn.record_metering_data(msg2)
|
||||
|
||||
data = self.get('/sources/test_list_resources/resources')
|
||||
ids = [r['resource_id'] for r in data['resources']]
|
||||
self.assertEquals(['resource-id'], ids)
|
112
tests/api/v1/test_list_users.py
Normal file
112
tests/api/v1/test_list_users.py
Normal file
@ -0,0 +1,112 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Test listing users.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from ceilometer import counter
|
||||
from ceilometer import meter
|
||||
|
||||
from ceilometer.tests import api as tests_api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TestListUsers(tests_api.TestBase):
|
||||
|
||||
def test_empty(self):
|
||||
data = self.get('/users')
|
||||
self.assertEquals({'users': []}, data)
|
||||
|
||||
def test_users(self):
|
||||
counter1 = counter.Counter(
|
||||
'test_list_users',
|
||||
'instance',
|
||||
'cumulative',
|
||||
1,
|
||||
'user-id',
|
||||
'project-id',
|
||||
'resource-id',
|
||||
timestamp=datetime.datetime(2012, 7, 2, 10, 40),
|
||||
duration=0,
|
||||
resource_metadata={'display_name': 'test-server',
|
||||
'tag': 'self.counter',
|
||||
}
|
||||
)
|
||||
msg = meter.meter_message_from_counter(counter1)
|
||||
self.conn.record_metering_data(msg)
|
||||
|
||||
counter2 = counter.Counter(
|
||||
'test_list_users',
|
||||
'instance',
|
||||
'cumulative',
|
||||
1,
|
||||
'user-id2',
|
||||
'project-id',
|
||||
'resource-id-alternate',
|
||||
timestamp=datetime.datetime(2012, 7, 2, 10, 41),
|
||||
duration=0,
|
||||
resource_metadata={'display_name': 'test-server',
|
||||
'tag': 'self.counter2',
|
||||
}
|
||||
)
|
||||
msg2 = meter.meter_message_from_counter(counter2)
|
||||
self.conn.record_metering_data(msg2)
|
||||
|
||||
data = self.get('/users')
|
||||
self.assertEquals(['user-id', 'user-id2'], data['users'])
|
||||
|
||||
def test_with_source(self):
|
||||
counter1 = counter.Counter(
|
||||
'test_list_users',
|
||||
'instance',
|
||||
'cumulative',
|
||||
1,
|
||||
'user-id',
|
||||
'project-id',
|
||||
'resource-id',
|
||||
timestamp=datetime.datetime(2012, 7, 2, 10, 40),
|
||||
duration=0,
|
||||
resource_metadata={'display_name': 'test-server',
|
||||
'tag': 'self.counter',
|
||||
}
|
||||
)
|
||||
msg = meter.meter_message_from_counter(counter1)
|
||||
self.conn.record_metering_data(msg)
|
||||
|
||||
counter2 = counter.Counter(
|
||||
'not-test',
|
||||
'instance',
|
||||
'cumulative',
|
||||
1,
|
||||
'user-id2',
|
||||
'project-id',
|
||||
'resource-id-alternate',
|
||||
timestamp=datetime.datetime(2012, 7, 2, 10, 41),
|
||||
duration=0,
|
||||
resource_metadata={'display_name': 'test-server',
|
||||
'tag': 'self.counter2',
|
||||
}
|
||||
)
|
||||
msg2 = meter.meter_message_from_counter(counter2)
|
||||
self.conn.record_metering_data(msg2)
|
||||
|
||||
data = self.get('/sources/test_list_users/users')
|
||||
self.assertEquals(['user-id'], data['users'])
|
@ -8,3 +8,4 @@ argparse
|
||||
sqlalchemy
|
||||
eventlet
|
||||
anyjson==0.3.1
|
||||
Flask==0.9
|
||||
|
@ -8,4 +8,5 @@ lockfile
|
||||
netaddr
|
||||
argparse
|
||||
sqlalchemy
|
||||
anyjson==0.3.1
|
||||
anyjson==0.3.1
|
||||
Flask==0.9
|
||||
|
2
tox.ini
2
tox.ini
@ -14,7 +14,7 @@ commands = {toxinidir}/run_tests.sh
|
||||
sitepackages = True
|
||||
|
||||
[testenv:py27]
|
||||
commands = {toxinidir}/run_tests.sh --with-coverage --cover-erase --cover-package=ceilometer --cover-inclusive []
|
||||
commands = {toxinidir}/run_tests.sh --no-path-adjustment --with-coverage --cover-erase --cover-package=ceilometer --cover-inclusive []
|
||||
|
||||
[testenv:pep8]
|
||||
deps = pep8==1.1
|
||||
|
Loading…
x
Reference in New Issue
Block a user