Implement initial draft of a Pecan-based API.

This commit is contained in:
Devananda van der Veen 2013-05-06 20:07:49 -07:00
parent 73f81e029f
commit 8ac57c720c
25 changed files with 780 additions and 39 deletions

6
.gitignore vendored
View File

@ -14,7 +14,6 @@ dist
build
eggs
parts
bin
var
sdist
develop-eggs
@ -24,12 +23,9 @@ develop-eggs
*.DS_Store
.testrepository
.tox
.venv
.*.swp
.coverage
cover
AUTHORS
ChangeLog
.testrepository/
.tox
.venv

37
ironic/api/__init__.py Normal file
View File

@ -0,0 +1,37 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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 flask.helpers
from oslo.config import cfg
from ironic.openstack.common import jsonutils
flask.helpers.json = jsonutils
API_SERVICE_OPTS = [
cfg.StrOpt('ironic_api_bind_ip',
default='0.0.0.0',
help='IP for the Ironic API server to bind to',
),
cfg.IntOpt('ironic_api_port',
default=6385,
help='The port for the Ironic API server',
),
]
CONF = cfg.CONF
CONF.register_opts(API_SERVICE_OPTS)

56
ironic/api/acl.py Normal file
View File

@ -0,0 +1,56 @@
# -*- 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.
"""Access Control Lists (ACL's) control access the API server."""
from keystoneclient.middleware import auth_token
from oslo.config import cfg
from pecan import hooks
from webob import exc
from ironic.common import policy
OPT_GROUP_NAME = 'keystone_authtoken'
def register_opts(conf):
"""Register keystoneclient middleware options
"""
conf.register_opts(auth_token.opts,
group=OPT_GROUP_NAME)
auth_token.CONF = conf
register_opts(cfg.CONF)
def install(app, conf):
"""Install ACL check on application."""
return auth_token.AuthProtocol(app,
conf=dict(conf.get(OPT_GROUP_NAME)))
class AdminAuthHook(hooks.PecanHook):
"""Verify that the user has admin rights
"""
def before(self, state):
headers = state.request.headers
if not policy.check_is_admin(headers.get('X-Roles', "").split(",")):
raise exc.HTTPUnauthorized()

81
ironic/api/app.py Normal file
View File

@ -0,0 +1,81 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright © 2012 New Dream Network, LLC (DreamHost)
# 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 oslo.config import cfg
import pecan
from ironic.api import acl
from ironic.api import config
from ironic.api import hooks
auth_opts = [
cfg.StrOpt('auth_strategy',
default='noauth',
help='Method to use for auth: noauth or keystone.'),
]
CONF = cfg.CONF
CONF.register_opts(auth_opts)
def get_pecan_config():
# Set up the pecan configuration
filename = config.__file__.replace('.pyc', '.py')
return pecan.configuration.conf_from_file(filename)
def setup_app(pecan_config=None, extra_hooks=None):
# FIXME: Replace DBHook with a hooks.TransactionHook
app_hooks = [hooks.ConfigHook()]
# hooks.DBHook()]
if extra_hooks:
app_hooks.extend(extra_hooks)
if not pecan_config:
pecan_config = get_pecan_config()
if pecan_config.app.enable_acl:
app_hooks.append(acl.AdminAuthHook())
pecan.configuration.set_config(dict(pecan_config), overwrite=True)
app = pecan.make_app(
pecan_config.app.root,
static_root=pecan_config.app.static_root,
template_path=pecan_config.app.template_path,
logging=getattr(pecan_config, 'logging', {}),
debug=getattr(pecan_config.app, 'debug', False),
force_canonical=getattr(pecan_config.app, 'force_canonical', True),
hooks=app_hooks,
)
# wrap_app=middleware.ParsableErrorMiddleware,
if pecan_config.app.enable_acl:
return acl.install(app, cfg.CONF)
return app
class VersionSelectorApplication(object):
def __init__(self):
pc = get_pecan_config()
pc.app.debug = CONF.debug
pc.app.enable_acl = (CONF.auth_strategy == 'keystone')
self.v1 = setup_app(pecan_config=pc)
def __call__(self, environ, start_response):
return self.v1(environ, start_response)

43
ironic/api/config.py Normal file
View File

@ -0,0 +1,43 @@
# Server Specific Configurations
server = {
'port': '6382',
'host': '0.0.0.0'
}
# Pecan Application Configurations
app = {
'root': 'ironic.api.controllers.root.RootController',
'modules': ['ironic.api'],
'static_root': '%(confdir)s/public',
'template_path': '%(confdir)s/ironic/api/templates',
'debug': False,
'enable_acl': False,
}
logging = {
'loggers': {
'root': {'level': 'INFO', 'handlers': ['console']},
'ironic': {'level': 'DEBUG', 'handlers': ['console']},
'wsme': {'level': 'DEBUG', 'handlers': ['console']}
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'simple'
}
},
'formatters': {
'simple': {
'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]'
'[%(threadName)s] %(message)s')
}
},
}
# Custom Configurations must be in Python dictionary format::
#
# foo = {'bar':'baz'}
#
# All configurations are accessible at::
# pecan.conf

View File

@ -0,0 +1,16 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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.

View File

@ -0,0 +1,31 @@
# -*- 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 pecan
from ironic.api.controllers import v1
class RootController(object):
v1 = v1.Controller()
@pecan.expose(generic=True)
def index(self):
# FIXME: GET / should return more than just ''
return ''

View File

@ -0,0 +1,165 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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.
"""
Version 1 of the Ironic API
Should maintain feature parity with Nova Baremetal Extension.
Specification in ironic/doc/api/v1.rst
"""
import pecan
from pecan import rest
import wsme
import wsmeext.pecan as wsme_pecan
from wsme import types as wtypes
from ironic import db
class Base(wtypes.Base):
# TODO: all the db bindings
@classmethod
def from_db_model(cls, m):
return cls(**(m.as_dict()))
@classmethod
def from_db_and_links(cls, m, links):
return cls(links=links, **(m.as_dict()))
def as_dict(self, db_model):
valid_keys = inspect.getargspec(db_model.__init__)[0]
if 'self' in valid_keys:
valid_keys.remove('self')
return dict((k, getattr(self, k))
for k in valid_keys
if hasattr(self, k) and
getattr(self, k) != wsme.Unset)
class Interface(Base):
"""A representation of a network interface for a baremetal node"""
node_id = int
address = wtypes.text
def __init__(self, node_id=None, address=None):
self.node_id = node_id
self.address = address
@classmethod
def sample(cls):
return cls(node_id=1,
address='52:54:00:cf:2d:31',
)
class InterfacesController(rest.RestController):
"""REST controller for Interfaces"""
@wsme_pecan.wsexpose(Interface, unicode)
def post(self, iface):
"""Ceate a new interface."""
return Interface.sample()
@wsme_pecan.wsexpose()
def get_all(self):
"""Retrieve a list of all interfaces."""
ifaces = [Interface.sample()]
return [(i.node_id, i.address) for i in ifaces]
@wsme_pecan.wsexpose(Interface, unicode)
def get_one(self, address):
"""Retrieve information about the given interface."""
one = Interface.sample()
one.address = address
return one
@wsme_pecan.wsexpose()
def delete(self, iface_id):
"""Delete an interface"""
pass
@wsme_pecan.wsexpose()
def put(self, iface_id):
"""Update an interface"""
pass
class Node(Base):
"""A representation of a bare metal node"""
uuid = wtypes.text
cpus = int
memory_mb = int
def __init__(self, uuid=None, cpus=None, memory_mb=None):
self.uuid = uuid
self.cpus = cpus
self.memory_mb = memory_mb
@classmethod
def sample(cls):
return cls(uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
cpus=2,
memory_mb=1024,
)
class NodesController(rest.RestController):
"""REST controller for Nodes"""
@wsme_pecan.wsexpose(Node, unicode)
def post(self, node):
"""Ceate a new node."""
return Node.sample()
@wsme_pecan.wsexpose()
def get_all(self):
"""Retrieve a list of all nodes."""
nodes = [Node.sample()]
return [n.uuid for n in nodes]
@wsme_pecan.wsexpose(Node, unicode)
def get_one(self, node_id):
"""Retrieve information about the given node."""
one = Node.sample()
one.uuid = node_id
return one
@wsme_pecan.wsexpose()
def delete(self, node_id):
"""Delete a node"""
pass
@wsme_pecan.wsexpose()
def put(self, node_id):
"""Update a node"""
pass
class Controller(object):
"""Version 1 API controller root."""
# TODO: _default and index
nodes = NodesController()
interfaces = InterfacesController()

45
ironic/api/hooks.py Normal file
View File

@ -0,0 +1,45 @@
# -*- 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.
from oslo.config import cfg
from pecan import hooks
from ironic import db
class ConfigHook(hooks.PecanHook):
"""Attach the configuration object to the request
so controllers can get to it.
"""
def before(self, state):
state.request.cfg = cfg.CONF
class DBHook(hooks.PecanHook):
def before(self, state):
# FIXME
storage_engine = storage.get_engine(state.request.cfg)
state.request.storage_engine = storage_engine
state.request.storage_conn = storage_engine.get_connection(
state.request.cfg)
# def after(self, state):
# print 'method:', state.request.method
# print 'response:', state.response.status

View File

@ -0,0 +1,21 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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 pecan import conf
def init_model():
pass

View File

@ -0,0 +1,12 @@
<%def name="title()">
Ironic API v1
</%def>
<header>
</header>
<div id="content">
<p> TODO </p>
</div>

54
ironic/cmd/api.py Normal file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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.
"""
The Ironic Service API
"""
import sys
from oslo.config import cfg
from wsgiref import simple_server
from ironic.api import app
from ironic.common.service import prepare_service
from ironic.openstack.common import service
from ironic.openstack.common.rpc import service as rpc_service
CONF = cfg.CONF
def main():
# Pase config file and command line options, then start logging
prepare_service(sys.argv)
# Build and start the WSGI app
host = CONF.ironic_api_bind_ip
port = CONF.ironic_api_port
wsgi = simple_server.make_server(
host, port,
app.VersionSelectorApplication())
print "Serving on http://%s:%s" % (host, port)
try:
wsgi.serve_forever()
except KeyboardInterrupt:
pass

46
ironic/cmd/manager.py Normal file
View File

@ -0,0 +1,46 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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.
"""
The Ironic Management Service
"""
import sys
from oslo.config import cfg
from wsgiref import simple_server
from ironic.manager import manager
from ironic.common.service import prepare_service
from ironic.openstack.common import service
from ironic.openstack.common.rpc import service as rpc_service
CONF = cfg.CONF
def main():
# Pase config file and command line options, then start logging
prepare_service(sys.argv)
mgr = manager.AgentManager()
topic = 'ironic.manager'
ironic = rcp_service.Service(CONF.host, topic, mgr)
launcher = service.launch(ironic)
launcher.wait()

View File

@ -92,7 +92,7 @@ def enforce(context, action, target, do_raise=True):
"""
init()
credentials = context.to_dict()
credentials = ironic_context.to_dict()
# Add the exception arguments if asked to do a raise
extra = {}
@ -102,17 +102,19 @@ def enforce(context, action, target, do_raise=True):
return policy.check(action, target, credentials, **extra)
def check_is_admin(context):
def check_is_admin(roles):
"""Whether or not roles contains 'admin' role according to policy setting.
"""
init()
#the target is user-self
credentials = context.to_dict()
target = credentials
return policy.check('context_is_admin', target, credentials)
if isinstance(roles, RequestContext):
# the target is user-self
credentials = roles.to_dict()
target = credentials
return policy.check('context_is_admin', target, credentials)
else:
return policy.check('context_is_admin', {}, {'roles': roles})
@policy.register('is_admin')

92
ironic/common/service.py Normal file
View File

@ -0,0 +1,92 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# Copyright © 2012 eNovance <licensing@enovance.com>
#
# Author: Julien Danjou <julien@danjou.info>
#
# 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 os
import socket
from oslo.config import cfg
from ironic.openstack.common import context
from ironic.openstack.common import log
from ironic.openstack.common import rpc
from ironic.openstack.common.rpc import service as rpc_service
cfg.CONF.register_opts([
cfg.IntOpt('periodic_interval',
default=600,
help='seconds between running periodic tasks'),
cfg.StrOpt('host',
default=socket.getfqdn(),
help='Name of this node. This can be an opaque identifier. '
'It is not necessarily a hostname, FQDN, or IP address. '
'However, the node name must be valid within '
'an AMQP key, and if using ZeroMQ, a valid '
'hostname, FQDN, or IP address'),
])
CLI_OPTIONS = [
cfg.StrOpt('os-username',
default=os.environ.get('OS_USERNAME', 'ironic'),
help='Username to use for openstack service access'),
cfg.StrOpt('os-password',
default=os.environ.get('OS_PASSWORD', 'admin'),
help='Password to use for openstack service access'),
cfg.StrOpt('os-tenant-id',
default=os.environ.get('OS_TENANT_ID', ''),
help='Tenant ID to use for openstack service access'),
cfg.StrOpt('os-tenant-name',
default=os.environ.get('OS_TENANT_NAME', 'admin'),
help='Tenant name to use for openstack service access'),
cfg.StrOpt('os-auth-url',
default=os.environ.get('OS_AUTH_URL',
'http://localhost:5000/v2.0'),
help='Auth URL to use for openstack service access'),
]
cfg.CONF.register_cli_opts(CLI_OPTIONS)
class PeriodicService(rpc_service.Service):
def start(self):
super(PeriodicService, self).start()
admin_context = context.RequestContext('admin', 'admin', is_admin=True)
self.tg.add_timer(cfg.CONF.periodic_interval,
self.manager.periodic_tasks,
context=admin_context)
def _sanitize_cmd_line(argv):
"""Remove non-nova CLI options from argv."""
cli_opt_names = ['--%s' % o.name for o in CLI_OPTIONS]
return [a for a in argv if a in cli_opt_names]
def prepare_service(argv=[]):
rpc.set_defaults(control_exchange='ironic')
cfg.set_defaults(log.log_opts,
default_log_levels=['amqplib=WARN',
'qpid.messaging=INFO',
'sqlalchemy=WARN',
'keystoneclient=INFO',
'stevedore=INFO',
'eventlet.wsgi.server=WARN'
])
cfg.CONF(argv[1:], project='ironic')
log.setup('ironic')

View File

@ -1,6 +1,8 @@
# Copyright (c) 2012 NTT DOCOMO, INC.
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
# flake8: noqa
#
# 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

View File

@ -45,19 +45,12 @@ these objects be simple dictionaries.
from oslo.config import cfg
from ironic.common import utils
from ironic.openstack.common.db import api as db_api
db_opts = [
cfg.StrOpt('db_backend',
default='sqlalchemy',
help='The backend to use for the ironic database'),
]
CONF = cfg.CONF
CONF.register_opts(db_opts)
_BACKEND_MAPPING = {'sqlalchemy': 'ironic.db.sqlalchemy.api'}
IMPL = utils.LazyPluggable(
'db_backend',
sqlalchemy='ironic.db.sqlalchemy.api')
IMPL = db_api.DBAPI(backend_mapping=_BACKEND_MAPPING)
def bm_node_get_all(context, service_host=None):

View File

@ -20,6 +20,7 @@
"""Implementation of SQLAlchemy backend."""
import sys
import uuid
from sqlalchemy.sql.expression import asc
@ -40,6 +41,11 @@ get_engine = db_session.get_engine
get_session = db_session.get_session
def get_backend():
"""The backend is this module itself."""
return sys.modules[__name__]
def model_query(context, model, *args, **kwargs):
"""Query helper that accounts for context's `read_deleted` field.

28
ironic/doc/api/v1.rst Normal file
View File

@ -0,0 +1,28 @@
GET / - information about this API
GET /node - list all nodes
GET /node/<id> - get node info
POST /node - insert a new node
DELETE /node/<id> - delete a node (and any associated interfaces)
GET /iface - list all interfaces
GET /iface/<id> - information about the interface
GET /iface/node/<id> - list interfaces for this node
POST /iface - insert a new interface
DELETE /iface/<id> - delete an interface
GET /node/image/<id> - get deployment driver info
PUT /node/image/<id> - set deployment driver info
GET /node/image/pxe/<id> - get PXE info
PUT /node/image/pxe/<id> - set PXE info
GET /node/power/<id> - get power driver info
PUT /node/power/<id> - set power driver info
GET /node/power/ipmi/<id> - get IPMI info (sanitised pw)
PUT /node/power/ipmi/<id> - set IPMI info
GET /node/power/state/<id> - get the power state
PUT /node/power/state/<id> - set the power state
GET /find/node - search for node based on query string
GET /find/iface - search for iface based on query string

View File

@ -297,5 +297,11 @@ def _get_impl():
"""Delay import of rpc_backend until configuration is loaded."""
global _RPCIMPL
if _RPCIMPL is None:
_RPCIMPL = importutils.import_module(CONF.rpc_backend)
try:
_RPCIMPL = importutils.import_module(CONF.rpc_backend)
except ImportError:
# For backwards compatibility with older nova config.
impl = CONF.rpc_backend.replace('nova.rpc',
'nova.openstack.common.rpc')
_RPCIMPL = importutils.import_module(impl)
return _RPCIMPL

View File

@ -1,6 +1,4 @@
[DEFAULT]
# The list of modules to copy from oslo-incubator.git
module=cliutils
module=context
module=db

View File

@ -22,6 +22,7 @@ pyasn1
Babel>=0.9.6
iso8601>=0.1.4
httplib2
setuptools_git>=0.4
python-cinderclient>=1.0.1
python-quantumclient>=2.2.0,<3.0.0
python-glanceclient>=0.5.0,<2
@ -29,3 +30,6 @@ python-keystoneclient>=0.2.0
stevedore>=0.7
websockify<0.4
oslo.config>=1.1.0
Flask==0.9
pecan>=0.2.0
wsme>=0.5b1

View File

@ -28,8 +28,8 @@ packages =
[entry_points]
console_scripts =
ironic-baremetal-deploy-helper = ironic.cmd.baremetal_deploy_helper:main
ironic-baremetal-manage = ironic.cmd.baremetal_manage:main
ironic-api = ironic.cmd.api:main
ironic-manager = ironic.cmd.manager:main
[build_sphinx]
all_files = 1

View File

@ -24,6 +24,8 @@ environment, it should be kept strictly compatible with Python 2.6.
Synced in from openstack-common
"""
from __future__ import print_function
import optparse
import os
import subprocess
@ -42,7 +44,7 @@ class InstallVenv(object):
self.project = project
def die(self, message, *args):
print >> sys.stderr, message % args
print(message % args, file=sys.stderr)
sys.exit(1)
def check_python_version(self):
@ -89,20 +91,20 @@ class InstallVenv(object):
virtual environment.
"""
if not os.path.isdir(self.venv):
print 'Creating venv...',
print('Creating venv...', end=' ')
if no_site_packages:
self.run_command(['virtualenv', '-q', '--no-site-packages',
self.venv])
else:
self.run_command(['virtualenv', '-q', self.venv])
print 'done.'
print 'Installing pip in venv...',
print('done.')
print('Installing pip in venv...', end=' ')
if not self.run_command(['tools/with_venv.sh', 'easy_install',
'pip>1.0']).strip():
self.die("Failed to install pip.")
print 'done.'
print('done.')
else:
print "venv already exists..."
print("venv already exists...")
pass
def pip_install(self, *args):
@ -111,7 +113,7 @@ class InstallVenv(object):
redirect_output=False)
def install_dependencies(self):
print 'Installing dependencies with pip (this can take a while)...'
print('Installing dependencies with pip (this can take a while)...')
# First things first, make sure our venv has the latest pip and
# distribute.
@ -153,12 +155,12 @@ class Distro(InstallVenv):
return
if self.check_cmd('easy_install'):
print 'Installing virtualenv via easy_install...',
print('Installing virtualenv via easy_install...', end=' ')
if self.run_command(['easy_install', 'virtualenv']):
print 'Succeeded'
print('Succeeded')
return
else:
print 'Failed'
print('Failed')
self.die('ERROR: virtualenv not found.\n\n%s development'
' requires virtualenv, please install it using your'

View File

@ -1,3 +1,5 @@
d2to1>=0.2.10,<0.3
pbr>=0.5,<0.6
SQLAlchemy>=0.7.8,<0.7.99
Cheetah>=2.4.4
amqplib>=0.6.1
@ -28,3 +30,6 @@ python-keystoneclient>=0.2.0
stevedore>=0.7
websockify<0.4
oslo.config>=1.1.0
Flask==0.9
pecan>=0.2.0
wsme>=0.5b1