Got a nice API shell working

* uses devstacks install for nova/keystone/et al
* talks to nova via novaclient.
* adds a few extensions to show how its done
* has a single call to list instances
* found a few minor bugs to discuss w/ nova crew
** Note in order to run this you have to mod the code downloaded by devstack
    or have local symlinks to nova & novaclient in your src tree running trunk
    This will get dealt with soon (it is a weekend!)
This commit is contained in:
Michael Basnight 2012-02-19 10:41:59 -06:00 committed by jenkins-reddwarf
parent f66d94b3a1
commit dc5a1bb8c3
15 changed files with 1219 additions and 4 deletions

70
bin/reddwarf-api-os-database Executable file
View File

@ -0,0 +1,70 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Starter script for Nova OS API."""
import eventlet
eventlet.monkey_patch()
import os
import sys
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(
sys.argv[0]), os.pardir, os.pardir))
if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")):
sys.path.insert(0, possible_topdir)
from nova import flags
from nova import log as logging
from nova import service
from nova import utils
from nova import wsgi
from nova.openstack.common import cfg
SERVICE_NAME="reddwarfapi_database"
reddwarf_opts = [
cfg.StrOpt('reddwarf_api_paste_config',
default='reddwarf-api-paste.ini',
help='Reddwarf API paste config'),
# port magic in service.WSGIService the name of the app has to be the same
# as the name of the service
cfg.IntOpt('%s_listen_port' % SERVICE_NAME,
default=8779,
help='Reddwarf API default port'),
cfg.MultiStrOpt('reddwarf_api_extension',
default=[
'reddwarf.api.database.contrib.standard_extensions'
],
help='osapi compute extension to load'),
]
FLAGS = flags.FLAGS
FLAGS.register_opts(reddwarf_opts)
if __name__ == '__main__':
utils.default_flagfile()
flags.FLAGS(sys.argv)
logging.setup()
utils.monkey_patch()
loader = wsgi.Loader(config_path=FLAGS.reddwarf_api_paste_config)
server = service.WSGIService(SERVICE_NAME, loader=loader)
service.serve(server)
service.wait()

304
bin/reddwarf-cli Executable file
View File

@ -0,0 +1,304 @@
#!/usr/bin/env python
# Copyright 2011 OpenStack 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.
"""
Reddwarf Command line tool
"""
import json
import optparse
import os
import sys
# If ../reddwarf/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'reddwarfclient',
'__init__.py')):
sys.path.insert(0, possible_topdir)
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir)
from reddwarfclient import common
oparser = None
def _pretty_print(info):
print json.dumps(info, sort_keys=True, indent=4)
class InstanceCommands(object):
"""Commands to perform various instances operations and actions"""
def __init__(self):
pass
def create(self, name, volume_size,
flavorRef="http://localhost:8775/v1.0/flavors/1"):
"""Create a new instance"""
dbaas = common.get_client()
volume = {"size": volume_size}
try:
result = dbaas.instances.create(name, flavorRef, volume)
_pretty_print(result._info)
except:
print sys.exc_info()[1]
def delete(self, id):
"""Delete the specified instance"""
dbaas = common.get_client()
try:
result = dbaas.instances.delete(id)
if result:
print result
except:
print sys.exc_info()[1]
def get(self, id):
"""Get details for the specified instance"""
dbaas = common.get_client()
try:
_pretty_print(dbaas.instances.get(id)._info)
except:
print sys.exc_info()[1]
def list(self):
"""List all instances for account"""
dbaas = common.get_client()
try:
for instance in dbaas.instances.list():
_pretty_print(instance._info)
except:
print sys.exc_info()[1]
def resize(self, id, size):
"""Resize an instance volume"""
dbaas = common.get_client()
try:
result = dbaas.instances.resize(id, size)
if result:
print result
except:
print sys.exc_info()[1]
def restart(self, id):
"""Restart the database"""
dbaas = common.get_client()
try:
result = dbaas.instances.restart(id)
if result:
print result
except:
print sys.exc_info()[1]
class FlavorsCommands(object):
"""Commands for listing Flavors"""
def __init__(self):
pass
def list(self):
"""List the available flavors"""
dbaas = common.get_client()
try:
for flavor in dbaas.flavors.list():
_pretty_print(flavor._info)
except:
print sys.exc_info()[1]
class DatabaseCommands(object):
"""Database CRUD operations on an instance"""
def __init__(self):
pass
def create(self, id, dbname):
"""Create a database"""
dbaas = common.get_client()
try:
databases = [{'name': dbname}]
dbaas.databases.create(id, databases)
except:
print sys.exc_info()[1]
def delete(self, id, dbname):
"""Delete a database"""
dbaas = common.get_client()
try:
dbaas.databases.delete(id, dbname)
except:
print sys.exc_info()[1]
def list(self, id):
"""List the databases"""
dbaas = common.get_client()
try:
for database in dbaas.databases.list(id):
_pretty_print(database._info)
except:
print sys.exc_info()[1]
class UserCommands(object):
"""User CRUD operations on an instance"""
def __init__(self):
pass
def create(self, id, username, password, dbname, *args):
"""Create a user in instance, with access to one or more databases"""
dbaas = common.get_client()
try:
databases = [{'name': dbname}]
[databases.append({"name": db}) for db in args]
users = [{'name': username, 'password': password,
'databases': databases}]
dbaas.users.create(id, users)
except:
print sys.exc_info()[1]
def delete(self, id, user):
"""Delete the specified user"""
dbaas = common.get_client()
try:
dbaas.users.delete(id, user)
except:
print sys.exc_info()[1]
def list(self, id):
"""List all the users for an instance"""
dbaas = common.get_client()
try:
for user in dbaas.users.list(id):
_pretty_print(user._info)
except:
print sys.exc_info()[1]
class RootCommands(object):
"""Root user related operations on an instance"""
def __init__(self):
pass
def create(self, id):
"""Enable the instance's root user."""
dbaas = common.get_client()
try:
user, password = dbaas.root.create(id)
print "User:\t\t%s\nPassword:\t%s" % (user, password)
except:
print sys.exc_info()[1]
def enabled(self, id):
"""Check the instance for root access"""
dbaas = common.get_client()
try:
_pretty_print(dbaas.root.is_root_enabled(id))
except:
print sys.exc_info()[1]
class VersionCommands(object):
"""List available versions"""
def __init__(self):
pass
def list(self, url):
"""List all the supported versions"""
dbaas = common.get_client()
try:
versions = dbaas.versions.index(url)
for version in versions:
_pretty_print(version._info)
except:
print sys.exc_info()[1]
def config_options():
global oparser
oparser.add_option("-u", "--url", default="http://localhost:5000/v1.1",
help="Auth API endpoint URL with port and version. \
Default: http://localhost:5000/v1.1")
COMMANDS = {'auth': common.Auth,
'instance': InstanceCommands,
'flavor': FlavorsCommands,
'database': DatabaseCommands,
'user': UserCommands,
'root': RootCommands,
'version': VersionCommands}
def main():
# Parse arguments
global oparser
oparser = optparse.OptionParser("%prog [options] <cmd> <action> <args>",
version='1.0')
config_options()
(options, args) = oparser.parse_args()
if not args:
common.print_commands(COMMANDS)
# Pop the command and check if it's in the known commands
cmd = args.pop(0)
if cmd in COMMANDS:
fn = COMMANDS.get(cmd)
command_object = fn()
# Get a list of supported actions for the command
actions = common.methods_of(command_object)
if len(args) < 1:
common.print_actions(cmd, actions)
# Check for a valid action and perform that action
action = args.pop(0)
if action in actions:
fn = actions.get(action)
try:
fn(*args)
sys.exit(0)
except TypeError as err:
print "Possible wrong number of arguments supplied."
print "%s %s: %s" % (cmd, action, fn.__doc__)
print "\t\t", [fn.func_code.co_varnames[i] for i in
range(fn.func_code.co_argcount)]
print "ERROR: %s" % err
except Exception:
print "Command failed, please check the log for more info."
raise
else:
common.print_actions(cmd, actions)
else:
common.print_commands(COMMANDS)
if __name__ == '__main__':
main()

50
development/FunkyBugs.txt Normal file
View File

@ -0,0 +1,50 @@
ONE:
Looks like the on-behalf-of functionality in python novaclient is borked in association w/ keystone
*There are 2 different calls to keystone to get token info, and they return different hash's
*The service catalog borks out if 'access' is not the main key, but in the 2nd case 'token' is the main key.
Modified Files
* novaclient/service_catalog.py
Until this is fixed you need to mod python-novaclient after it gets downloaded via devstack
###### BEGIN PATCH
@@ -44,12 +44,17 @@ class ServiceCatalog(object):
raise novaclient.exceptions.EndpointNotFound()
# We don't always get a service catalog back ...
- if not 'serviceCatalog' in self.catalog['access']:
+ try:
+ if 'serviceCatalog' in self.catalog['access']:
+ # Full catalog ...
+ catalog = self.catalog['access']['serviceCatalog']
+ except KeyError:
+ if 'serviceCatalog' in self.catalog['token']:
+ # Full catalog ...
+ catalog = self.catalog['token']['serviceCatalog']
+ if catalog is None:
return None
- # Full catalog ...
- catalog = self.catalog['access']['serviceCatalog']
-
for service in catalog:
if service.get("type") != service_type:
continue
###### END PATCH
TWO:
Looks like there is a missing import in nova/api/openstack/auth.py
from nova.auth import manager
Until this is fixed you need to mod nova after its gets downloaded via devstack
###### BEGIN PATCH
@@ -80,6 +80,7 @@ class AuthMiddleware(base_wsgi.Middleware):
if not db_driver:
db_driver = FLAGS.db_driver
self.db = utils.import_object(db_driver)
+ from nova.auth import manager
self.auth = auth.manager.AuthManager()
super(AuthMiddleware, self).__init__(application)
###### END PATCH

View File

@ -0,0 +1,40 @@
# Steps
# 1 install nova via devstack
# 2 install reddwarf via this (or eventually mod devstack)
# 3 run tempest tests
cd ~
git clone git://github.com/openstack-dev/devstack.git
cd devstack
# Make sure every devstack instance on a new vm will get the default params for novaclient, paste, etc..
# We can change these to external flags in the future
echo "MYSQL_PASSWORD=e1a2c042c828d3566d0a
RABBIT_PASSWORD=f7999d1955c5014aa32c
SERVICE_TOKEN=be19c524ddc92109a224
ADMIN_PASSWORD=3de4922d8b6ac5a1aad9" > localrc
./stack.sh
# Now add a user to keystone that is reddwarf specific. This is what we will use in dev/test to authenticate against keystone
# the get_id is stolen from devstack :D
function get_id () {
echo `$@ | grep id | awk '{print $4}'`
}
# NOTE THIS AUTH TOKEN NEEDS TO BE CHANGED
REDDWARF_TENANT=`get_id keystone --endpoint http://localhost:35357/v2.0 --token be19c524ddc92109a224 tenant-create --name=reddwarf`
REDDWARF_USER=`get_id keystone --endpoint http://localhost:35357/v2.0 --token be19c524ddc92109a224 user-create \
--name=reddwarf --pass="REDDWARF-PASS" --email=reddwarf@example.com`
REDDWARF_ROLE=`get_id keystone --endpoint http://localhost:35357/v2.0 --token be19c524ddc92109a224 role-create --name=reddwarf`
keystone --endpoint http://localhost:35357/v2.0 --token be19c524ddc92109a224 add-user-role $REDDWARF_USER $REDDWARF_ROLE $REDDWARF_TENANT
# Now attempt a login
curl -d '{"auth":{"passwordCredentials":{"username": "reddwarf", "password": "REDDWARF-PASS"},"tenantName":"reddwarf"}}' \
-H "Content-type: application/json" http://localhost:35357/v2.0/tokens
# now get a list of instances, which connects over python-novaclient to nova
# NOTE THIS AUTH TOKEN NEEDS TO BE CHANGED
# Also note that keystone uses the tenant id now and _not_ the name
#curl -H"content-type:application/xml" -H"X-Auth-Project-Id:$REDDWARF_TENANT" -H"X-Auth-User:reddwarf" \
# -H'X-Auth-Key:2a2c89c6a7284d32bcb94b4e56f0411c' http://0.0.0.0:8779/v2/$REDDWARF_TENANT/instances
# Also, you should start up the api node like this
# bin/reddwarf-api-os-database --flagfile=etc/nova/nova.conf.template

View File

@ -1,4 +0,0 @@
# Steps
# 1 install nova via devstack
# 2 install reddwarf via this (or eventually mod devstack)
# 3 run tempest tests

View File

@ -0,0 +1,68 @@
--reddwarf_api_paste_config=etc/nova/reddwarf-api-paste.ini
--osapi_compute_listen_port=9000
--sql_connection=mysql://root:e1a2c042c828d3566d0a@127.0.0.1/nova
# Glance
#--image_service=nova.image.glance.GlanceImageService
#
# Nova Network
#--network_manager=reddwarf.network.manager.FlatManager
#--fixed_range=10.0.0.0/26
#--network_size=1
#--flat_network_bridge=br100
# Nova Scheduler
#--scheduler_driver=reddwarf.scheduler.simple.UnforgivingMemoryScheduler
#--max_instance_memory_mb=6144
#
# Nova Volume
# Volume configuration for vagrant vm
#--volume_manager=reddwarf.volume.manager.ReddwarfVolumeManager
#--volume_driver=reddwarf.tests.volume.driver.ISCSITestDriver
#--use_local_volumes=False
#--san_thin_provision=False
#--san_ip=33.33.33.11
#--san_login=vagrant
#--san_privatekey=/home/vagrant/.ssh/id_rsa
#
# Reddwarf Compute Manager
#--compute_manager=reddwarf.compute.manager.ReddwarfComputeManager
#
# Nova Compute
#--connection_type=openvz
#--ovz_ve_private_dir=/var/lib/vz/private/
#--lock_path=/tmp
#--resume_guests_state_on_host_boot=True
##--start_guests_on_host_boot=True
#
## API
#--api_paste_config=/home/vagrant/api-paste_keystone.ini
#--enabled_apis=osapi
#--allow_admin_api=True
#--reddwarf_auth_cache_expire_time=300
#
## Quota Limits
#--quota_instances=10
#--quota_cores=20
#--quota_ram=51200
#--quota_volumes=10
#--quota_gigabytes=10000
#--quota_floating_ips=10
#
## Infrastructure Services
#--sql_connection=mysql://nova:novapass@10.0.4.15/nova
#--sql_min_pool_size=1
#--rabbit_host=10.0.4.15
#
## Logging
#--logfile=/vagrant/nova.log
#--verbose
#--notification_driver=reddwarf.notifier.logfile_notifier
#--notifier_logfile=/vagrant/notification.log
#
## Reaper config
#--reaper_driver=reddwarf.reaper.driver.ReddwarfReaperDriver
#Extra ending line needed so that other conf files can be appended to this one.

View File

@ -0,0 +1,133 @@
############
# Metadata #
############
[composite:metadata]
use = egg:Paste#urlmap
/: metaversions
/latest: meta
/2007-01-19: meta
/2007-03-01: meta
/2007-08-29: meta
/2007-10-10: meta
/2007-12-15: meta
/2008-02-01: meta
/2008-09-01: meta
/2009-04-04: meta
[pipeline:metaversions]
pipeline = ec2faultwrap logrequest metaverapp
[pipeline:meta]
pipeline = ec2faultwrap logrequest metaapp
[app:metaverapp]
paste.app_factory = nova.api.metadata.handler:Versions.factory
[app:metaapp]
paste.app_factory = nova.api.metadata.handler:MetadataRequestHandler.factory
#######
# EC2 #
#######
[composite:ec2]
use = egg:Paste#urlmap
/services/Cloud: ec2cloud
[pipeline:ec2cloud]
pipeline = ec2faultwrap logrequest ec2noauth cloudrequest authorizer validator ec2executor
# NOTE(vish): use the following pipeline for deprecated auth
# pipeline = ec2faultwrap logrequest authenticate cloudrequest authorizer validator ec2executor
# NOTE(vish): use the following pipeline for keystone auth
# pipeline = ec2faultwrap logrequest ec2keystoneauth cloudrequest authorizer validator ec2executor
[filter:ec2faultwrap]
paste.filter_factory = nova.api.ec2:FaultWrapper.factory
[filter:logrequest]
paste.filter_factory = nova.api.ec2:RequestLogging.factory
[filter:ec2lockout]
paste.filter_factory = nova.api.ec2:Lockout.factory
[filter:totoken]
paste.filter_factory = nova.api.ec2:EC2Token.factory
[filter:ec2keystoneauth]
paste.filter_factory = nova.api.ec2:EC2KeystoneAuth.factory
[filter:ec2noauth]
paste.filter_factory = nova.api.ec2:NoAuth.factory
[filter:authenticate]
paste.filter_factory = nova.api.ec2:Authenticate.factory
[filter:cloudrequest]
controller = nova.api.ec2.cloud.CloudController
paste.filter_factory = nova.api.ec2:Requestify.factory
[filter:authorizer]
paste.filter_factory = nova.api.ec2:Authorizer.factory
[filter:validator]
paste.filter_factory = nova.api.ec2:Validator.factory
[app:ec2executor]
paste.app_factory = nova.api.ec2:Executor.factory
#############
# Openstack #
#############
[composite:reddwarfapi_database]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: reddwarfdatabaseversions
#/v1.1: reddwarf_database_api_v2
/v2: reddwarf_database_api_v2
[pipeline:reddwarf_database_api_v2]
pipeline = faultwrap authtoken keystonecontext reddwarf_database_app_v2
#pipeline = faultwrap noauth ratelimit reddwarf_database_app_v2
# NOTE(vish): use the following pipeline for deprecated auth
# pipeline = faultwrap auth ratelimit reddwarf_database_app_v2
# NOTE(vish): use the following pipeline for keystone auth
# pipeline = faultwrap authtoken keystonecontext ratelimit reddwarf_database_app_v2
[filter:faultwrap]
paste.filter_factory = nova.api.openstack:FaultWrapper.factory
[filter:auth]
paste.filter_factory = nova.api.openstack.auth:AuthMiddleware.factory
[filter:noauth]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory
[filter:ratelimit]
paste.filter_factory = nova.api.openstack.compute.limits:RateLimitingMiddleware.factory
[app:reddwarf_database_app_v2]
paste.app_factory = reddwarf.api.database:APIRouter.factory
[pipeline:reddwarfdatabaseversions]
pipeline = faultwrap reddwarfdatabaseversionapp
[app:reddwarfdatabaseversionapp]
paste.app_factory = reddwarf.api.database.versions:Versions.factory
##########
# Shared #
##########
[filter:keystonecontext]
paste.filter_factory = nova.api.auth:NovaKeystoneContext.factory
[filter:authtoken]
paste.filter_factory = keystone.middleware.auth_token:filter_factory
service_protocol = http
service_host = 127.0.0.1
service_port = 5000
auth_host = 127.0.0.1
auth_port = 35357
auth_protocol = http
auth_uri = http://127.0.0.1:5000/
admin_token = be19c524ddc92109a224

32
reddwarf/__init__.py Normal file
View File

@ -0,0 +1,32 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
:mod:`reddwarf` -- Cloud PaaS Database Platform
===================================
.. automodule:: reddwarf
:platform: Unix
:synopsis: Platform-As-A-Service Database Cloud
.. moduleauthor:: Michael Basnight <mbasnight@gmail.com>
"""
import gettext
gettext.install("reddwarf", unicode=1)

21
reddwarf/api/__init__.py Normal file
View File

@ -0,0 +1,21 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
WSGI middleware for OpenStack API controllers.
"""

View File

@ -0,0 +1,55 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
WSGI middleware for OpenStack Compute API.
"""
import nova.api.openstack
from reddwarf.api.database import extensions
from reddwarf.api.database import instances
from reddwarf.api.database import versions
from nova import flags
from nova import log as logging
LOG = logging.getLogger('reddwarf.api.database')
FLAGS = flags.FLAGS
class APIRouter(nova.api.openstack.APIRouter):
"""
Routes requests on the OpenStack API to the appropriate controller
and method.
"""
ExtensionManager = extensions.ExtensionManager
def _setup_routes(self, mapper):
self.resources['versions'] = versions.create_resource()
mapper.connect("versions", "/",
controller=self.resources['versions'],
action='show')
mapper.redirect("", "/")
self.resources['instances'] = instances.create_resource()
mapper.resource("instance", "instances",
controller=self.resources['instances'],
collection={'detail': 'GET'},
member={'action': 'POST'})

View File

@ -0,0 +1,39 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Justin Santa Barbara
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Contrib contains extensions that are shipped with nova.
It can't be called 'extensions' because that causes namespacing problems.
"""
from nova import flags
from nova import log as logging
from nova.api.openstack import extensions
FLAGS = flags.FLAGS
LOG = logging.getLogger('reddwarf.api.database.contrib')
def standard_extensions(ext_mgr):
extensions.load_standard_extensions(ext_mgr, LOG, __path__, __package__)
def select_extensions(ext_mgr):
extensions.load_standard_extensions(ext_mgr, LOG, __path__, __package__,
FLAGS.osapi_compute_ext_list)

View File

@ -0,0 +1,79 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import webob.exc
from nova.api.openstack import extensions
from nova import log as logging
LOG = logging.getLogger('reddwarf.api.database.contrib.databases')
class DatabasesController(object):
def index(self, req):
LOG.info("index call databases")
return "This is a index of databases"
class UsersController(object):
def index(self, req):
LOG.info("index call users")
return "This is a index of users"
#class DatabasesControllerExtension(wsgi.Controller):
# @wsgi.action('test_func')
# def _test_func(self, req, id, body):
#
# return "Test Func called."
class Databases(extensions.ExtensionDescriptor):
"""The Databases Extension"""
name = "Databases"
alias = "DATABASES"
namespace = "http://TBD"
updated = "2011-01-22T13:25:27-06:00"
def __init__(self, ext_mgr):
ext_mgr.register(self)
def get_resources(self):
resources = []
resource = extensions.ResourceExtension('databases',
DatabasesController())
resources.append(resource)
resource = extensions.ResourceExtension('users',
UsersController())
resources.append(resource)
return resources
def get_controller_extensions(self):
extension_list = []
extension_set = [
# (DatabasesControllerExtension, 'instances'),
# (FoxInSocksFlavorGooseControllerExtension, 'flavors'),
# (FoxInSocksFlavorBandsControllerExtension, 'flavors'),
]
for klass, collection in extension_set:
controller = klass()
ext = extensions.ControllerExtension(self, collection, controller)
extension_list.append(ext)
return extension_list

View File

@ -0,0 +1,33 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nova.api.openstack import extensions as base_extensions
from nova import flags
from nova import log as logging
LOG = logging.getLogger('reddwarf.api.database.extensions')
FLAGS = flags.FLAGS
class ExtensionManager(base_extensions.ExtensionManager):
def __init__(self):
LOG.audit(_('Initializing extension manager.'))
self.cls_list = FLAGS.reddwarf_api_extension
self.extensions = {}
self._load_extensions()

View File

@ -0,0 +1,59 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nova import flags
from nova import log as logging
from nova.api.openstack import wsgi
from novaclient.v1_1.client import Client
from nova.openstack.common import cfg
LOG = logging.getLogger('reddwarf.api.database.instances')
reddwarf_opts = [
cfg.StrOpt('reddwarf_proxy_admin_user',
default='admin',
help='User by which you make proxy requests to the nova api with'),
cfg.StrOpt('reddwarf_proxy_admin_pass',
default='3de4922d8b6ac5a1aad9',
help='Password for the admin user defined in reddwarf_proxy_admin_user'),
cfg.StrOpt('reddwarf_proxy_admin_tenant_name',
default='admin',
help='Tenant name fro teh admin user defined in reddwarf_proxy_admin_user'),
]
FLAGS = flags.FLAGS
FLAGS.register_opts(reddwarf_opts)
class Controller(wsgi.Controller):
#_view_builder_class = views_servers.ViewBuilder
def __init__(self, **kwargs):
super(Controller, self).__init__(**kwargs)
# @wsgi.response(202)
def index(self, req):
"""Return all servers."""
client = Client(FLAGS.reddwarf_proxy_admin_user, FLAGS.reddwarf_proxy_admin_pass,
FLAGS.reddwarf_proxy_admin_tenant_name, "http://0.0.0.0:5000/v2.0", token=req.headers["X-Auth-Token"])
client.authenticate()
servers = client.servers.list()
LOG.info(servers)
return "got the list of servers back! %s" % servers
def create_resource():
return wsgi.Resource(Controller())

View File

@ -0,0 +1,236 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
from lxml import etree
from nova.api.openstack.compute.views import versions as views_versions
from nova.api.openstack import wsgi
from nova.api.openstack import xmlutil
VERSIONS = {
"v2.0": {
"id": "v2.0",
"status": "CURRENT",
"updated": "2012-01-01T11:33:21Z",
"links": [
{
"rel": "describedby",
"type": "application/pdf",
"href": "http://docs.rackspacecloud.com/"
"clouddatabase/api/v1.1/cs-devguide-20110125.pdf",
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": "http://docs.rackspacecloud.com/"
"clouddatabase/api/v1.1/application.wadl",
},
],
"media-types": [
{
"base": "application/xml",
"type": "application/vnd.openstack.reddwarf+xml;version=2",
},
{
"base": "application/json",
"type": "application/vnd.openstack.reddwarf+json;version=2",
}
],
}
}
class MediaTypesTemplateElement(xmlutil.TemplateElement):
def will_render(self, datum):
return 'media-types' in datum
def make_version(elem):
elem.set('id')
elem.set('status')
elem.set('updated')
mts = MediaTypesTemplateElement('media-types')
elem.append(mts)
mt = xmlutil.SubTemplateElement(mts, 'media-type', selector='media-types')
mt.set('base')
mt.set('type')
xmlutil.make_links(elem, 'links')
version_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
class VersionTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('version', selector='version')
make_version(root)
return xmlutil.MasterTemplate(root, 1, nsmap=version_nsmap)
class VersionsTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('versions')
elem = xmlutil.SubTemplateElement(root, 'version', selector='versions')
make_version(elem)
return xmlutil.MasterTemplate(root, 1, nsmap=version_nsmap)
class ChoicesTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('choices')
elem = xmlutil.SubTemplateElement(root, 'version', selector='choices')
make_version(elem)
return xmlutil.MasterTemplate(root, 1, nsmap=version_nsmap)
class AtomSerializer(wsgi.XMLDictSerializer):
NSMAP = {None: xmlutil.XMLNS_ATOM}
def __init__(self, metadata=None, xmlns=None):
self.metadata = metadata or {}
if not xmlns:
self.xmlns = wsgi.XMLNS_ATOM
else:
self.xmlns = xmlns
def _get_most_recent_update(self, versions):
recent = None
for version in versions:
updated = datetime.datetime.strptime(version['updated'],
'%Y-%m-%dT%H:%M:%SZ')
if not recent:
recent = updated
elif updated > recent:
recent = updated
return recent.strftime('%Y-%m-%dT%H:%M:%SZ')
def _get_base_url(self, link_href):
# Make sure no trailing /
link_href = link_href.rstrip('/')
return link_href.rsplit('/', 1)[0] + '/'
def _create_feed(self, versions, feed_title, feed_id):
feed = etree.Element('feed', nsmap=self.NSMAP)
title = etree.SubElement(feed, 'title')
title.set('type', 'text')
title.text = feed_title
# Set this updated to the most recently updated version
recent = self._get_most_recent_update(versions)
etree.SubElement(feed, 'updated').text = recent
etree.SubElement(feed, 'id').text = feed_id
link = etree.SubElement(feed, 'link')
link.set('rel', 'self')
link.set('href', feed_id)
author = etree.SubElement(feed, 'author')
etree.SubElement(author, 'name').text = 'Rackspace'
etree.SubElement(author, 'uri').text = 'http://www.rackspace.com/'
for version in versions:
feed.append(self._create_version_entry(version))
return feed
def _create_version_entry(self, version):
entry = etree.Element('entry')
etree.SubElement(entry, 'id').text = version['links'][0]['href']
title = etree.SubElement(entry, 'title')
title.set('type', 'text')
title.text = 'Version %s' % version['id']
etree.SubElement(entry, 'updated').text = version['updated']
for link in version['links']:
link_elem = etree.SubElement(entry, 'link')
link_elem.set('rel', link['rel'])
link_elem.set('href', link['href'])
if 'type' in link:
link_elem.set('type', link['type'])
content = etree.SubElement(entry, 'content')
content.set('type', 'text')
content.text = 'Version %s %s (%s)' % (version['id'],
version['status'],
version['updated'])
return entry
class VersionsAtomSerializer(AtomSerializer):
def default(self, data):
versions = data['versions']
feed_id = self._get_base_url(versions[0]['links'][0]['href'])
feed = self._create_feed(versions, 'Available API Versions', feed_id)
return self._to_xml(feed)
class VersionAtomSerializer(AtomSerializer):
def default(self, data):
version = data['version']
feed_id = version['links'][0]['href']
feed = self._create_feed([version], 'About This Version', feed_id)
return self._to_xml(feed)
class Versions(wsgi.Resource):
def __init__(self):
super(Versions, self).__init__(None)
@wsgi.serializers(xml=VersionsTemplate,
atom=VersionsAtomSerializer)
def index(self, req):
"""Return all versions."""
builder = views_versions.get_view_builder(req)
return builder.build_versions(VERSIONS)
@wsgi.serializers(xml=ChoicesTemplate)
@wsgi.response(300)
def multi(self, req):
"""Return multiple choices."""
builder = views_versions.get_view_builder(req)
return builder.build_choices(VERSIONS, req)
def get_action_args(self, request_environment):
"""Parse dictionary created by routes library."""
args = {}
if request_environment['PATH_INFO'] == '/':
args['action'] = 'index'
else:
args['action'] = 'multi'
return args
class VersionV2(object):
@wsgi.serializers(xml=VersionTemplate,
atom=VersionAtomSerializer)
def show(self, req):
builder = views_versions.get_view_builder(req)
return builder.build_version(VERSIONS['v2.0'])
def create_resource():
return wsgi.Resource(VersionV2())