Implement initial draft of a Pecan-based API.
This commit is contained in:
parent
73f81e029f
commit
8ac57c720c
6
.gitignore
vendored
6
.gitignore
vendored
@ -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
37
ironic/api/__init__.py
Normal 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
56
ironic/api/acl.py
Normal 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
81
ironic/api/app.py
Normal 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
43
ironic/api/config.py
Normal 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
|
16
ironic/api/controllers/__init__.py
Normal file
16
ironic/api/controllers/__init__.py
Normal 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.
|
31
ironic/api/controllers/root.py
Normal file
31
ironic/api/controllers/root.py
Normal 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 ''
|
165
ironic/api/controllers/v1.py
Normal file
165
ironic/api/controllers/v1.py
Normal 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
45
ironic/api/hooks.py
Normal 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
|
21
ironic/api/model/__init__.py
Normal file
21
ironic/api/model/__init__.py
Normal 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
|
12
ironic/api/templates/index.html
Normal file
12
ironic/api/templates/index.html
Normal 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
54
ironic/cmd/api.py
Normal 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
46
ironic/cmd/manager.py
Normal 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()
|
@ -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
92
ironic/common/service.py
Normal 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')
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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
28
ironic/doc/api/v1.rst
Normal 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
|
@ -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
|
||||
|
@ -1,6 +1,4 @@
|
||||
[DEFAULT]
|
||||
|
||||
# The list of modules to copy from oslo-incubator.git
|
||||
module=cliutils
|
||||
module=context
|
||||
module=db
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user