Updated initial version of portas-api

This commit is contained in:
Serg Melikyan 2013-03-04 14:12:47 +04:00
parent b9d60d50bd
commit 0e8c965b88
29 changed files with 370 additions and 1864 deletions

View File

@ -1,90 +0,0 @@
#!/usr/bin/python
# 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 os
import subprocess
from setuptools import setup, find_packages
from setuptools.command.sdist import sdist
from windc import version
if os.path.isdir('.bzr'):
with open("windc/vcsversion.py", 'w') as version_file:
vcs_cmd = subprocess.Popen(["bzr", "version-info", "--python"],
stdout=subprocess.PIPE)
vcsversion = vcs_cmd.communicate()[0]
version_file.write(vcsversion)
class local_sdist(sdist):
"""Customized sdist hook - builds the ChangeLog file from VC first"""
def run(self):
if os.path.isdir('.bzr'):
# We're in a bzr branch
log_cmd = subprocess.Popen(["bzr", "log", "--gnu"],
stdout=subprocess.PIPE)
changelog = log_cmd.communicate()[0]
with open("ChangeLog", "w") as changelog_file:
changelog_file.write(changelog)
sdist.run(self)
cmdclass = {'sdist': local_sdist}
# If Sphinx is installed on the box running setup.py,
# enable setup.py to build the documentation, otherwise,
# just ignore it
try:
from sphinx.setup_command import BuildDoc
class local_BuildDoc(BuildDoc):
def run(self):
for builder in ['html', 'man']:
self.builder = builder
self.finalize_options()
BuildDoc.run(self)
cmdclass['build_sphinx'] = local_BuildDoc
except:
pass
setup(
name='windc',
version=version.canonical_version_string(),
description='The WinDC project provides a simple WSGI server for Windows Environment Management',
license='Apache License (2.0)',
author='OpenStack',
author_email='openstack@lists.launchpad.net',
url='http://windc.openstack.org/',
packages=find_packages(exclude=['tests', 'bin']),
test_suite='nose.collector',
cmdclass=cmdclass,
include_package_data=True,
classifiers=[
'Development Status :: 4 - Beta',
'License :: OSI Approved :: Apache Software License',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python :: 2.6',
'Environment :: No Input/Output (Daemon)',
],
scripts=['bin/windc',
'bin/windc-api'])

2
portas/.gitignore vendored
View File

@ -1,4 +1,4 @@
##IntelJ Idea #IntelJ Idea
.idea/ .idea/
#virtualenv #virtualenv

View File

@ -19,29 +19,32 @@ import gettext
import os import os
import sys import sys
# If ../portas/__init__.py exists, add ../ to Python search path, so that # If ../portas/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python... # 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]), possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir, os.pardir,
os.pardir)) os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'portas', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'portas', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
gettext.install('portas', './portas/locale', unicode=1)
from portas.common import config from portas.common import config
from portas.openstack.common import log from portas.openstack.common import log
from portas.openstack.common import wsgi from portas.openstack.common import wsgi
from portas.openstack.common import service
gettext.install('portas', './portas/locale', unicode=1)
if __name__ == '__main__': if __name__ == '__main__':
try: try:
config.parse_args() config.parse_args()
log.setup('portas')
server = wsgi.Server() api_service = wsgi.Service(config.load_paste_app(),
server.start(config.load_paste_app(), default_port=8181) port=config.CONF.bind_port,
server.wait() host=config.CONF.bind_host)
launcher = service.Launcher()
launcher.run_service(api_service)
except RuntimeError, e: except RuntimeError, e:
sys.stderr.write("ERROR: %s\n" % e) sys.stderr.write("ERROR: %s\n" % e)
sys.exit(1) sys.exit(1)

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2010 OpenStack Foundation. # Copyright (c) 2010 OpenStack Foundation.
# #
@ -15,8 +16,8 @@
# limitations under the License. # limitations under the License.
# #
# Glance documentation build configuration file, created by # Portas documentation build configuration file, created by
# sphinx-quickstart on Tue May 18 13:50:15 2010. # sphinx-quickstart on Tue February 28 13:50:15 2013.
# #
# This file is execfile()'d with the current directory set to its containing # This file is execfile()'d with the current directory set to its containing
# dir. # dir.
@ -33,7 +34,7 @@ import sys
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path = [os.path.abspath('../../glance'), sys.path = [os.path.abspath('../../portas'),
os.path.abspath('../..'), os.path.abspath('../..'),
os.path.abspath('../../bin') os.path.abspath('../../bin')
] + sys.path ] + sys.path
@ -65,19 +66,19 @@ source_suffix = '.rst'
master_doc = 'index' master_doc = 'index'
# General information about the project. # General information about the project.
project = u'Glance' project = u'Portas'
copyright = u'2010, OpenStack Foundation.' copyright = u'2013, Mirantis, Inc.'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
from glance.version import version_info as glance_version from portas.version import version_info as portas_version
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = glance_version.version_string_with_vcs() release = portas_version.version_string_with_vcs()
# The short X.Y version. # The short X.Y version.
version = glance_version.canonical_version_string() version = portas_version.canonical_version_string()
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
@ -114,7 +115,7 @@ show_authors = True
pygments_style = 'sphinx' pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting. # A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['glance.'] modindex_common_prefix = ['portas.']
# -- Options for man page output -------------------------------------------- # -- Options for man page output --------------------------------------------
@ -122,25 +123,7 @@ modindex_common_prefix = ['glance.']
# List of tuples 'sourcefile', 'target', u'title', u'Authors name', 'manual' # List of tuples 'sourcefile', 'target', u'title', u'Authors name', 'manual'
man_pages = [ man_pages = [
('man/glance', 'glance', u'Glance CLI', ('man/portasapi', 'portas-api', u'Portas API Server',
[u'OpenStack'], 1),
('man/glanceapi', 'glance-api', u'Glance API Server',
[u'OpenStack'], 1),
('man/glancecachecleaner', 'glance-cache-cleaner', u'Glance Cache Cleaner',
[u'OpenStack'], 1),
('man/glancecachemanage', 'glance-cache-manage', u'Glance Cache Manager',
[u'OpenStack'], 1),
('man/glancecacheprefetcher', 'glance-cache-prefetcher',
u'Glance Cache Pre-fetcher', [u'OpenStack'], 1),
('man/glancecachepruner', 'glance-cache-pruner', u'Glance Cache Pruner',
[u'OpenStack'], 1),
('man/glancecontrol', 'glance-control', u'Glance Daemon Control Helper ',
[u'OpenStack'], 1),
('man/glancemanage', 'glance-manage', u'Glance Management Utility',
[u'OpenStack'], 1),
('man/glanceregistry', 'glance-registry', u'Glance Registry Server',
[u'OpenStack'], 1),
('man/glancescrubber', 'glance-scrubber', u'Glance Scrubber Service',
[u'OpenStack'], 1) [u'OpenStack'], 1)
] ]
@ -219,7 +202,7 @@ html_use_index = False
#html_file_suffix = '' #html_file_suffix = ''
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = 'glancedoc' htmlhelp_basename = 'portasdoc'
# -- Options for LaTeX output ------------------------------------------------ # -- Options for LaTeX output ------------------------------------------------
@ -234,8 +217,8 @@ htmlhelp_basename = 'glancedoc'
# (source start file, target name, title, author, # (source start file, target name, title, author,
# documentclass [howto/manual]). # documentclass [howto/manual]).
latex_documents = [ latex_documents = [
('index', 'Glance.tex', u'Glance Documentation', ('index', 'Portas.tex', u'Portas Documentation',
u'Glance Team', 'manual'), u'Keero Team', 'manual'),
] ]
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of
@ -256,6 +239,4 @@ latex_documents = [
#latex_use_modindex = True #latex_use_modindex = True
# Example configuration for intersphinx: refer to the Python standard library. # Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'python': ('http://docs.python.org/', None), intersphinx_mapping = {'python': ('http://docs.python.org/', None)}
'nova': ('http://nova.openstack.org', None),
'swift': ('http://swift.openstack.org', None)}

View File

@ -14,70 +14,7 @@
License for the specific language governing permissions and limitations License for the specific language governing permissions and limitations
under the License. under the License.
Welcome to Glance's documentation! Welcome to Portas's documentation!
================================== ==================================
The Glance project provides services for discovering, registering, and We rule the world!
retrieving virtual machine images. Glance has a RESTful API that allows
querying of VM image metadata as well as retrieval of the actual image.
VM images made available through Glance can be stored in a variety of
locations from simple filesystems to object-storage systems like the
OpenStack Swift project.
Glance, as with all OpenStack projects, is written with the following design
guidelines in mind:
* **Component based architecture**: Quickly add new behaviors
* **Highly available**: Scale to very serious workloads
* **Fault tolerant**: Isolated processes avoid cascading failures
* **Recoverable**: Failures should be easy to diagnose, debug, and rectify
* **Open standards**: Be a reference implementation for a community-driven api
This documentation is generated by the Sphinx toolkit and lives in the source
tree. Additional documentation on Glance and other components of OpenStack can
be found on the `OpenStack wiki`_.
.. _`OpenStack wiki`: http://wiki.openstack.org
Concepts
========
.. toctree::
:maxdepth: 1
identifiers
statuses
formats
common-image-properties
Installing/Configuring Glance
=============================
.. toctree::
:maxdepth: 1
installing
configuring
authentication
policies
Operating Glance
================
.. toctree::
:maxdepth: 1
controllingservers
db
cache
notifications
Using Glance
============
.. toctree::
:maxdepth: 1
glanceapi
glanceclient

View File

@ -1,57 +1,5 @@
[DEFAULT] [pipeline:portas-api]
# Show more verbose log output (sets INFO log level output)
verbose = True
# Show debugging output in logs (sets DEBUG log level output)
debug = True
# Address to bind the server to
bind_host = 0.0.0.0
# Port the bind the server to
bind_port = 8082
# Log to this file. Make sure the user running skeleton-api has
# permissions to write to this file!
log_file = /tmp/api.log
# Orchestration Adapter Section
#
#provider - Cloud provider to use (openstack, amazon, dummy)
provider = openstack
# Heat specific parameters
#heat_url - url for the heat service
# [auto] - find in the keystone
heat_url = auto
#heat_api_version - version of the API to use
#
heat_api_version = 1
[pipeline:windc-api]
pipeline = apiv1app pipeline = apiv1app
# NOTE: use the following pipeline for keystone
#pipeline = authtoken context apiv1app
[app:apiv1app] [app:apiv1app]
paste.app_factory = windc.common.wsgi:app_factory paste.app_factory = portas.api.v1.router:API.factory
windc.app_factory = windc.api.v1.router:API
[filter:context]
paste.filter_factory = windc.common.wsgi:filter_factory
windc.filter_factory = windc.common.context:ContextMiddleware
[filter:authtoken]
paste.filter_factory = keystone.middleware.auth_token:filter_factory
auth_host = 172.18.67.57
auth_port = 35357
auth_protocol = http
auth_uri = http://172.18.67.57:5000/v2.0/
admin_tenant_name = service
admin_user = windc
admin_password = 000
[filter:auth-context]
paste.filter_factory = windc.common.wsgi:filter_factory
windc.filter_factory = keystone.middleware.balancer_auth_token:KeystoneContextMiddleware
[rabbitmq]
host = 10.0.0.1
vhost = keero

View File

@ -13,22 +13,7 @@ bind_port = 8082
# Log to this file. Make sure the user running skeleton-api has # Log to this file. Make sure the user running skeleton-api has
# permissions to write to this file! # permissions to write to this file!
log_file = /tmp/api.log log_file = /tmp/portas-api.log
[pipeline:windc-api] #A valid SQLAlchemy connection string for the metadata database
pipeline = versionnegotiation context apiv1app sql_connection = sqlite:///portas.sqlite
[pipeline:versions]
pipeline = versionsapp
[app:versionsapp]
paste.app_factory = windc.api.versions:app_factory
[app:apiv1app]
paste.app_factory = windc.api.v1:app_factory
[filter:versionnegotiation]
paste.filter_factory = windc.api.middleware.version_negotiation:filter_factory
[filter:context]
paste.filter_factory = openstack.common.middleware.context:filter_factory

View File

@ -1,7 +1,7 @@
[DEFAULT] [DEFAULT]
# The list of modules to copy from openstack-common # The list of modules to copy from openstack-common
modules=setup,wsgi,config,exception,gettextutilsl,jsonutils,log,xmlutils,sslutils,service,notifier,local,install_venv_common modules=setup,wsgi,config,exception,gettextutils,importutils,jsonutils,log,xmlutils,sslutils,service,notifier,local,install_venv_common,version,timeutils,eventlet_backdoor,threadgroup,loopingcall,uuidutils
# The base module to hold the copy of openstack.common # The base module to hold the copy of openstack.common
base=portas base=portas

View File

@ -0,0 +1,30 @@
from portas.db.api import EnvironmentRepository
from portas.openstack.common import wsgi
from portas.openstack.common import log as logging
log = logging.getLogger(__name__)
class Controller(object):
repository = EnvironmentRepository()
def index(self, request):
log.debug(_("Display list of environments"))
return {"environments": [env.to_dict() for env in self.repository.list()]}
def create(self, request, body):
return self.repository.add(body).to_dict()
# def delete(self, request, datacenter_id):
# log.debug("Got delete request. Request: %s", req)
# self.repository., datacenter_id)
#
# def update(self, req, tenant_id, datacenter_id, body):
# log.debug("Got update request. Request: %s", req)
# core_api.update_dc(self.conf, tenant_id, datacenter_id, body)
# return {'datacenter': {'id': dc_id}}
def create_resource():
return wsgi.Resource(Controller())

View File

@ -14,37 +14,17 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import routes
from portas.openstack.common import wsgi
from glance.api.v1 import images from portas.api.v1 import environments
from glance.api.v1 import members
from glance.common import wsgi
class API(wsgi.Router): class API(wsgi.Router):
@classmethod
"""WSGI router for Glance v1 API requests.""" def factory(cls, global_conf, **local_conf):
return cls(routes.Mapper())
def __init__(self, mapper): def __init__(self, mapper):
images_resource = images.create_resource() environments_resource = environments.create_resource()
mapper.resource("environment", "environments", controller=environments_resource)
mapper.resource("image", "images", controller=images_resource,
collection={'detail': 'GET'})
mapper.connect("/", controller=images_resource, action="index")
mapper.connect("/images/{id}", controller=images_resource,
action="meta", conditions=dict(method=["HEAD"]))
members_resource = members.create_resource()
mapper.resource("member", "members", controller=members_resource,
parent_resource=dict(member_name='image',
collection_name='images'))
mapper.connect("/shared-images/{id}",
controller=members_resource,
action="index_shared_images")
mapper.connect("/images/{image_id}/members",
controller=members_resource,
action="update_all",
conditions=dict(method=["PUT"]))
super(API, self).__init__(mapper) super(API, self).__init__(mapper)

View File

@ -29,71 +29,40 @@ import sys
from oslo.config import cfg from oslo.config import cfg
from paste import deploy from paste import deploy
from glance.version import version_info as version from portas.version import version_info as version
paste_deploy_opts = [ paste_deploy_opts = [
cfg.StrOpt('flavor'), cfg.StrOpt('flavor'),
cfg.StrOpt('config_file'), cfg.StrOpt('config_file'),
] ]
common_opts = [
cfg.BoolOpt('allow_additional_image_properties', default=True, bind_opts = [
help=_('Whether to allow users to specify image properties ' cfg.StrOpt('bind_host', default='localhost'),
'beyond what the image schema provides')), cfg.IntOpt('bind_port'),
cfg.StrOpt('data_api', default='glance.db.sqlalchemy.api',
help=_('Python module path of data access API')),
cfg.IntOpt('limit_param_default', default=25,
help=_('Default value for the number of items returned by a '
'request if not specified explicitly in the request')),
cfg.IntOpt('api_limit_max', default=1000,
help=_('Maximum permissible number of items that could be '
'returned by a request')),
cfg.BoolOpt('show_image_direct_url', default=False,
help=_('Whether to include the backend image storage location '
'in image properties. Revealing storage location can be a '
'security risk, so use this setting with caution!')),
cfg.IntOpt('image_size_cap', default=1099511627776,
help=_("Maximum size of image a user can upload in bytes. "
"Defaults to 1099511627776 bytes (1 TB).")),
cfg.BoolOpt('enable_v1_api', default=True,
help=_("Deploy the v1 OpenStack Images API. ")),
cfg.BoolOpt('enable_v2_api', default=True,
help=_("Deploy the v2 OpenStack Images API. ")),
cfg.StrOpt('pydev_worker_debug_host', default=None,
help=_('The hostname/IP of the pydev process listening for '
'debug connections')),
cfg.IntOpt('pydev_worker_debug_port', default=5678,
help=_('The port on which a pydev process is listening for '
'connections.')),
] ]
CONF = cfg.CONF CONF = cfg.CONF
CONF.register_opts(paste_deploy_opts, group='paste_deploy') CONF.register_opts(paste_deploy_opts, group='paste_deploy')
CONF.register_opts(common_opts) CONF.register_opts(bind_opts)
CONF.import_opt('verbose', 'glance.openstack.common.log') CONF.import_opt('verbose', 'portas.openstack.common.log')
CONF.import_opt('debug', 'glance.openstack.common.log') CONF.import_opt('debug', 'portas.openstack.common.log')
CONF.import_opt('log_dir', 'glance.openstack.common.log') CONF.import_opt('log_dir', 'portas.openstack.common.log')
CONF.import_opt('log_file', 'glance.openstack.common.log') CONF.import_opt('log_file', 'portas.openstack.common.log')
CONF.import_opt('log_config', 'glance.openstack.common.log') CONF.import_opt('log_config', 'portas.openstack.common.log')
CONF.import_opt('log_format', 'glance.openstack.common.log') CONF.import_opt('log_format', 'portas.openstack.common.log')
CONF.import_opt('log_date_format', 'glance.openstack.common.log') CONF.import_opt('log_date_format', 'portas.openstack.common.log')
CONF.import_opt('use_syslog', 'glance.openstack.common.log') CONF.import_opt('use_syslog', 'portas.openstack.common.log')
CONF.import_opt('syslog_log_facility', 'glance.openstack.common.log') CONF.import_opt('syslog_log_facility', 'portas.openstack.common.log')
def parse_args(args=None, usage=None, default_config_files=None): def parse_args(args=None, usage=None, default_config_files=None):
CONF(args=args, CONF(args=args,
project='glance', project='portas',
version=version.cached_version_string(), version=version.cached_version_string(),
usage=usage, usage=usage,
default_config_files=default_config_files) default_config_files=default_config_files)
def parse_cache_args(args=None):
config_files = cfg.find_config_files(project='glance', prog='glance-cache')
parse_args(args=args, default_config_files=config_files)
def setup_logging(): def setup_logging():
""" """
Sets up the logging options for a log with supplied name Sets up the logging options for a log with supplied name

View File

@ -16,21 +16,14 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
"""Glance exception subclasses""" """Portas exception subclasses"""
import urlparse
_FATAL_EXCEPTION_FORMAT_ERRORS = False _FATAL_EXCEPTION_FORMAT_ERRORS = False
class RedirectException(Exception): class PortasException(Exception):
def __init__(self, url):
self.url = urlparse.urlparse(url)
class GlanceException(Exception):
""" """
Base Glance Exception Base Portas Exception
To correctly use this class, inherit from it and define To correctly use this class, inherit from it and define
a 'message' property. That message will get printf'd a 'message' property. That message will get printf'd
@ -50,223 +43,14 @@ class GlanceException(Exception):
# at least get the core message out if something happened # at least get the core message out if something happened
pass pass
super(GlanceException, self).__init__(message) super(PortasException, self).__init__(message)
class SchemaLoadError(PortasException):
class MissingArgumentError(GlanceException):
message = _("Missing required argument.")
class MissingCredentialError(GlanceException):
message = _("Missing required credential: %(required)s")
class BadAuthStrategy(GlanceException):
message = _("Incorrect auth strategy, expected \"%(expected)s\" but "
"received \"%(received)s\"")
class NotFound(GlanceException):
message = _("An object with the specified identifier was not found.")
class UnknownScheme(GlanceException):
message = _("Unknown scheme '%(scheme)s' found in URI")
class BadStoreUri(GlanceException):
message = _("The Store URI was malformed.")
class Duplicate(GlanceException):
message = _("An object with the same identifier already exists.")
class StorageFull(GlanceException):
message = _("There is not enough disk space on the image storage media.")
class StorageWriteDenied(GlanceException):
message = _("Permission to write image storage media denied.")
class AuthBadRequest(GlanceException):
message = _("Connect error/bad request to Auth service at URL %(url)s.")
class AuthUrlNotFound(GlanceException):
message = _("Auth service at URL %(url)s not found.")
class AuthorizationFailure(GlanceException):
message = _("Authorization failed.")
class NotAuthenticated(GlanceException):
message = _("You are not authenticated.")
class Forbidden(GlanceException):
message = _("You are not authorized to complete this action.")
class ForbiddenPublicImage(Forbidden):
message = _("You are not authorized to complete this action.")
class ProtectedImageDelete(Forbidden):
message = _("Image %(image_id)s is protected and cannot be deleted.")
#NOTE(bcwaldon): here for backwards-compatability, need to deprecate.
class NotAuthorized(Forbidden):
message = _("You are not authorized to complete this action.")
class Invalid(GlanceException):
message = _("Data supplied was not valid.")
class InvalidSortKey(Invalid):
message = _("Sort key supplied was not valid.")
class InvalidFilterRangeValue(Invalid):
message = _("Unable to filter using the specified range.")
class ReadonlyProperty(Forbidden):
message = _("Attribute '%(property)s' is read-only.")
class ReservedProperty(Forbidden):
message = _("Attribute '%(property)s' is reserved.")
class AuthorizationRedirect(GlanceException):
message = _("Redirecting to %(uri)s for authorization.")
class DatabaseMigrationError(GlanceException):
message = _("There was an error migrating the database.")
class ClientConnectionError(GlanceException):
message = _("There was an error connecting to a server")
class ClientConfigurationError(GlanceException):
message = _("There was an error configuring the client.")
class MultipleChoices(GlanceException):
message = _("The request returned a 302 Multiple Choices. This generally "
"means that you have not included a version indicator in a "
"request URI.\n\nThe body of response returned:\n%(body)s")
class LimitExceeded(GlanceException):
message = _("The request returned a 413 Request Entity Too Large. This "
"generally means that rate limiting or a quota threshold was "
"breached.\n\nThe response body:\n%(body)s")
def __init__(self, *args, **kwargs):
self.retry_after = (int(kwargs['retry']) if kwargs.get('retry')
else None)
super(LimitExceeded, self).__init__(*args, **kwargs)
class ServiceUnavailable(GlanceException):
message = _("The request returned 503 Service Unavilable. This "
"generally occurs on service overload or other transient "
"outage.")
def __init__(self, *args, **kwargs):
self.retry_after = (int(kwargs['retry']) if kwargs.get('retry')
else None)
super(ServiceUnavailable, self).__init__(*args, **kwargs)
class ServerError(GlanceException):
message = _("The request returned 500 Internal Server Error.")
class UnexpectedStatus(GlanceException):
message = _("The request returned an unexpected status: %(status)s."
"\n\nThe response body:\n%(body)s")
class InvalidContentType(GlanceException):
message = _("Invalid content type %(content_type)s")
class BadRegistryConnectionConfiguration(GlanceException):
message = _("Registry was not configured correctly on API server. "
"Reason: %(reason)s")
class BadStoreConfiguration(GlanceException):
message = _("Store %(store_name)s could not be configured correctly. "
"Reason: %(reason)s")
class BadDriverConfiguration(GlanceException):
message = _("Driver %(driver_name)s could not be configured correctly. "
"Reason: %(reason)s")
class StoreDeleteNotSupported(GlanceException):
message = _("Deleting images from this store is not supported.")
class StoreAddDisabled(GlanceException):
message = _("Configuration for store failed. Adding images to this "
"store is disabled.")
class InvalidNotifierStrategy(GlanceException):
message = _("'%(strategy)s' is not an available notifier strategy.")
class MaxRedirectsExceeded(GlanceException):
message = _("Maximum redirects (%(redirects)s) was exceeded.")
class InvalidRedirect(GlanceException):
message = _("Received invalid HTTP redirect.")
class NoServiceEndpoint(GlanceException):
message = _("Response from Keystone does not contain a Glance endpoint.")
class RegionAmbiguity(GlanceException):
message = _("Multiple 'image' service matches for region %(region)s. This "
"generally means that a region is required and you have not "
"supplied one.")
class WorkerCreationFailure(GlanceException):
message = _("Server worker creation failed: %(reason)s.")
class SchemaLoadError(GlanceException):
message = _("Unable to load schema: %(reason)s") message = _("Unable to load schema: %(reason)s")
class InvalidObject(GlanceException): class InvalidObject(PortasException):
message = _("Provided object does not match schema " message = _("Provided object does not match schema "
"'%(schema)s': %(reason)s") "'%(schema)s': %(reason)s")
class UnsupportedHeaderFeature(GlanceException):
message = _("Provided header feature is unsupported: %(feature)s")
class InUseByStore(GlanceException):
message = _("The image cannot be deleted because it is in use through "
"the backend store outside of Glance.")
class ImageSizeLimitExceeded(GlanceException):
message = _("The provided image is too large.")

View File

@ -1 +1,12 @@
__author__ = 'sad' from oslo.config import cfg
sql_connection_opt = cfg.StrOpt('sql_connection',
default='sqlite:///portas.sqlite',
secret=True,
metavar='CONNECTION',
help='A valid SQLAlchemy connection '
'string for the metadata database. '
'Default: %(default)s')
CONF = cfg.CONF
CONF.register_opt(sql_connection_opt)

View File

@ -1 +1,18 @@
__author__ = 'sad' from portas.db.models import Environment
from portas.db.session import get_session
class EnvironmentRepository(object):
def list(self):
session = get_session()
return session.query(Environment).all()
def add(self, values):
session = get_session()
with session.begin():
env = Environment()
env.update(values)
session.add(env)
return env
# def update(self, env):

View File

@ -1,7 +1,7 @@
[db_settings] [db_settings]
# Used to identify which repository this database is versioned under. # Used to identify which repository this database is versioned under.
# You can use the name of your project. # You can use the name of your project.
repository_id=Glance Migrations repository_id=Portas Migrations
# The name of the database table used to track the schema version. # The name of the database table used to track the schema version.
# This name shouldn't already be used by your project. # This name shouldn't already be used by your project.

View File

@ -1,36 +1,29 @@
from sqlalchemy.schema import MetaData, Table, Column, ForeignKey from sqlalchemy.schema import MetaData, Table, Column, ForeignKey
from sqlalchemy.types import Integer, String, Text, DateTime from sqlalchemy.types import String, Text, DateTime
meta = MetaData() meta = MetaData()
Table('datacenter', meta, Table('environment', meta,
Column('id', String(32), primary_key=True), Column('id', String(32), primary_key=True),
Column('name', String(255)), Column('name', String(255)),
Column('type', String(255)), Column('created', DateTime(), nullable=False),
Column('version', String(255)), Column('updated', DateTime(), nullable=False),
Column('tenant_id',String(100)), Column('tenant_id', String(36)),
Column('KMS', String(80)), Column('description', Text()),
Column('WSUS', String(80)),
Column('extra', Text()),
) )
Table('service', meta, Table('service', meta,
Column('id', String(32), primary_key=True), Column('id', String(32), primary_key=True),
Column('datacenter_id', String(32), ForeignKey('datacenter.id')), Column('name', String(255)),
Column('name', String(255)), Column('type', String(40)),
Column('type', String(40)), Column('environment_id', String(32), ForeignKey('environment.id')),
Column('status', String(255)), Column('created', DateTime, nullable=False),
Column('tenant_id', String(40)), Column('updated', DateTime, nullable=False),
Column('created_at', DateTime, nullable=False), Column('description', Text()),
Column('updated_at', DateTime, nullable=False),
Column('deployed', String(40)),
Column('vm_id',String(40)),
Column('extra', Text()),
) )
def upgrade(migrate_engine): def upgrade(migrate_engine):
meta.bind = migrate_engine meta.bind = migrate_engine
meta.create_all() meta.create_all()

View File

@ -1 +0,0 @@
# template repository default versions module

View File

@ -17,19 +17,20 @@
# under the License. # under the License.
""" """
SQLAlchemy models for glance data SQLAlchemy models for portas data
""" """
import anyjson
from sqlalchemy import Column, Integer, String, BigInteger from sqlalchemy import Column, String, BigInteger, TypeDecorator, ForeignKey
from sqlalchemy.ext.compiler import compiles from sqlalchemy.ext.compiler import compiles
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import ForeignKey, DateTime, Boolean, Text from sqlalchemy import DateTime, Text
from sqlalchemy.orm import relationship, backref, object_mapper from sqlalchemy.orm import relationship, backref, object_mapper
from sqlalchemy import UniqueConstraint
import glance.db.sqlalchemy.api from portas.openstack.common import timeutils
from glance.openstack.common import timeutils from portas.openstack.common import uuidutils
from glance.openstack.common import uuidutils
from portas.db.session import get_session
BASE = declarative_base() BASE = declarative_base()
@ -40,22 +41,16 @@ def compile_big_int_sqlite(type_, compiler, **kw):
class ModelBase(object): class ModelBase(object):
"""Base class for Nova and Glance Models""" __protected_attributes__ = {"created", "updated"}
__table_args__ = {'mysql_engine': 'InnoDB'}
__table_initialized__ = False
__protected_attributes__ = set([
"created_at", "updated_at", "deleted_at", "deleted"])
created_at = Column(DateTime, default=timeutils.utcnow, created = Column(DateTime, default=timeutils.utcnow,
nullable=False) nullable=False)
updated_at = Column(DateTime, default=timeutils.utcnow, updated = Column(DateTime, default=timeutils.utcnow,
nullable=False, onupdate=timeutils.utcnow) nullable=False, onupdate=timeutils.utcnow)
deleted_at = Column(DateTime)
deleted = Column(Boolean, nullable=False, default=False)
def save(self, session=None): def save(self, session=None):
"""Save this object""" """Save this object"""
session = session or glance.db.sqlalchemy.api.get_session() session = session or get_session()
session.add(self) session.add(self)
session.flush() session.flush()
@ -94,80 +89,55 @@ class ModelBase(object):
return self.__dict__.items() return self.__dict__.items()
def to_dict(self): def to_dict(self):
return self.__dict__.copy() dictionary = self.__dict__.copy()
return {k: v for k, v in dictionary.iteritems() if k != '_sa_instance_state'}
class Image(BASE, ModelBase): class JsonBlob(TypeDecorator):
"""Represents an image in the datastore""" impl = Text
__tablename__ = 'images'
def process_bind_param(self, value, dialect):
return anyjson.serialize(value)
def process_result_value(self, value, dialect):
return anyjson.deserialize(value)
class Environment(BASE, ModelBase):
"""Represents a Environment in the metadata-store"""
__tablename__ = 'environment'
id = Column(String(36), primary_key=True, default=uuidutils.generate_uuid) id = Column(String(36), primary_key=True, default=uuidutils.generate_uuid)
name = Column(String(255)) name = Column(String(255))
disk_format = Column(String(20)) tenant_id = Column(String(36))
container_format = Column(String(20)) description = Column(JsonBlob())
size = Column(BigInteger)
status = Column(String(30), nullable=False)
is_public = Column(Boolean, nullable=False, default=False)
checksum = Column(String(32))
min_disk = Column(Integer(), nullable=False, default=0)
min_ram = Column(Integer(), nullable=False, default=0)
owner = Column(String(255))
protected = Column(Boolean, nullable=False, default=False)
class ImageProperty(BASE, ModelBase): class Service(BASE, ModelBase):
"""Represents an image properties in the datastore""" """
__tablename__ = 'image_properties' Represents an instance of service.
__table_args__ = (UniqueConstraint('image_id', 'name'), {})
id = Column(Integer, primary_key=True) :var name: string
image_id = Column(String(36), ForeignKey('images.id'), :var type: string - type of service (e.g. Active Directory)
nullable=False) """
image = relationship(Image, backref=backref('properties'))
__tablename__ = 'service'
id = Column(String(36), primary_key=True, default=uuidutils.generate_uuid)
name = Column(String(255), index=True, nullable=False) name = Column(String(255), index=True, nullable=False)
value = Column(Text) type = Column(String(255), index=True, nullable=False)
environment_id = Column(String(36), ForeignKey('environment.id'))
environment = relationship(Environment,
class ImageTag(BASE, ModelBase): backref=backref('service', order_by=id),
"""Represents an image tag in the datastore""" uselist=False)
__tablename__ = 'image_tags' description = Column(JsonBlob())
id = Column(Integer, primary_key=True, nullable=False)
image_id = Column(String(36), ForeignKey('images.id'), nullable=False)
value = Column(String(255), nullable=False)
class ImageLocation(BASE, ModelBase):
"""Represents an image location in the datastore"""
__tablename__ = 'image_locations'
id = Column(Integer, primary_key=True, nullable=False)
image_id = Column(String(36), ForeignKey('images.id'), nullable=False)
image = relationship(Image, backref=backref('locations'))
value = Column(Text(), nullable=False)
class ImageMember(BASE, ModelBase):
"""Represents an image members in the datastore"""
__tablename__ = 'image_members'
__table_args__ = (UniqueConstraint('image_id', 'member'), {})
id = Column(Integer, primary_key=True)
image_id = Column(String(36), ForeignKey('images.id'),
nullable=False)
image = relationship(Image, backref=backref('members'))
member = Column(String(255), nullable=False)
can_share = Column(Boolean, nullable=False, default=False)
status = Column(String(20), nullable=False, default="pending")
def register_models(engine): def register_models(engine):
""" """
Creates database tables for all models with the given engine Creates database tables for all models with the given engine
""" """
models = (Image, ImageProperty, ImageMember) models = (Environment, Service)
for model in models: for model in models:
model.metadata.create_all(engine) model.metadata.create_all(engine)
@ -176,6 +146,6 @@ def unregister_models(engine):
""" """
Drops database tables for all models with the given engine Drops database tables for all models with the given engine
""" """
models = (Image, ImageProperty) models = (Environment, Service)
for model in models: for model in models:
model.metadata.drop_all(engine) model.metadata.drop_all(engine)

View File

@ -30,15 +30,10 @@ from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import NullPool from sqlalchemy.pool import NullPool
from sqlalchemy.exc import DisconnectionError from sqlalchemy.exc import DisconnectionError
from windc.common import cfg from portas.common.config import CONF as conf
from windc.db import migrate_repo
from portas.db import migrate_repo
DB_GROUP_NAME = 'sql'
DB_OPTIONS = (
cfg.IntOpt('idle_timeout', default=3600),
cfg.StrOpt('connection', default='sqlite:///windc.sqlite'),
)
MAKER = None MAKER = None
ENGINE = None ENGINE = None
@ -73,27 +68,26 @@ class MySQLPingListener(object):
raise raise
def get_session(conf, autocommit=True, expire_on_commit=False): def get_session(autocommit=True, expire_on_commit=False):
"""Return a SQLAlchemy session.""" """Return a SQLAlchemy session."""
global MAKER global MAKER
if MAKER is None: if MAKER is None:
MAKER = sessionmaker(autocommit=autocommit, MAKER = sessionmaker(autocommit=autocommit,
expire_on_commit=expire_on_commit) expire_on_commit=expire_on_commit)
engine = get_engine(conf) engine = get_engine()
MAKER.configure(bind=engine) MAKER.configure(bind=engine)
session = MAKER() session = MAKER()
return session return session
def get_engine(conf): def get_engine():
"""Return a SQLAlchemy engine.""" """Return a SQLAlchemy engine."""
global ENGINE global ENGINE
register_conf_opts(conf) connection_url = make_url(conf.sql_connection)
connection_url = make_url(conf.sql.connection)
if ENGINE is None or not ENGINE.url == connection_url: if ENGINE is None or not ENGINE.url == connection_url:
engine_args = {'pool_recycle': conf.sql.idle_timeout, engine_args = {'pool_recycle': 3600,
'echo': False, 'echo': False,
'convert_unicode': True 'convert_unicode': True
} }
@ -101,22 +95,16 @@ def get_engine(conf):
engine_args['poolclass'] = NullPool engine_args['poolclass'] = NullPool
if 'mysql' in connection_url.drivername: if 'mysql' in connection_url.drivername:
engine_args['listeners'] = [MySQLPingListener()] engine_args['listeners'] = [MySQLPingListener()]
ENGINE = create_engine(conf.sql.connection, **engine_args) ENGINE = create_engine(conf.sql_connection, **engine_args)
sync()
return ENGINE return ENGINE
def register_conf_opts(conf, options=DB_OPTIONS, group=DB_GROUP_NAME): def sync():
"""Register database options."""
conf.register_group(cfg.OptGroup(name=group))
conf.register_opts(options, group=group)
def sync(conf):
register_conf_opts(conf)
repo_path = os.path.abspath(os.path.dirname(migrate_repo.__file__)) repo_path = os.path.abspath(os.path.dirname(migrate_repo.__file__))
try: try:
versioning_api.upgrade(conf.sql.connection, repo_path) versioning_api.upgrade(conf.sql_connection, repo_path)
except versioning_exceptions.DatabaseNotControlledError: except versioning_exceptions.DatabaseNotControlledError:
versioning_api.version_control(conf.sql.connection, repo_path) versioning_api.version_control(conf.sql_connection, repo_path)
versioning_api.upgrade(conf.sql.connection, repo_path) versioning_api.upgrade(conf.sql_connection, repo_path)

File diff suppressed because it is too large Load Diff

View File

@ -44,21 +44,6 @@ class Schema(object):
def _filter_func(properties, key): def _filter_func(properties, key):
return key in properties return key in properties
def merge_properties(self, properties):
# Ensure custom props aren't attempting to override base props
original_keys = set(self.properties.keys())
new_keys = set(properties.keys())
intersecting_keys = original_keys.intersection(new_keys)
conflicting_keys = [k for k in intersecting_keys
if self.properties[k] != properties[k]]
if len(conflicting_keys) > 0:
props = ', '.join(conflicting_keys)
reason = _("custom properties (%(props)s) conflict "
"with base properties")
raise exception.SchemaLoadError(reason=reason % {'props': props})
self.properties.update(properties)
def raw(self): def raw(self):
raw = { raw = {
'name': self.name, 'name': self.name,
@ -70,17 +55,6 @@ class Schema(object):
return raw return raw
class PermissiveSchema(Schema):
@staticmethod
def _filter_func(properties, key):
return True
def raw(self):
raw = super(PermissiveSchema, self).raw()
raw['additionalProperties'] = {'type': 'string'}
return raw
class CollectionSchema(object): class CollectionSchema(object):
def __init__(self, name, item_schema): def __init__(self, name, item_schema):

View File

@ -3,4 +3,4 @@ import unittest
class Test(unittest.TestCase): class Test(unittest.TestCase):
def test(self): def test(self):
assert False assert True

View File

@ -15,6 +15,6 @@
# under the License. # under the License.
from glance.openstack.common import version as common_version from portas.openstack.common import version as common_version
version_info = common_version.VersionInfo('glance') version_info = common_version.VersionInfo('portas')

View File

@ -28,8 +28,8 @@ function process_option {
-P|--no-pep8) let no_pep8=1;; -P|--no-pep8) let no_pep8=1;;
-f|--force) let force=1;; -f|--force) let force=1;;
-u|--update) update=1;; -u|--update) update=1;;
--unittests-only) noseopts="$noseopts --exclude-dir=glance/tests/functional";; --unittests-only) noseopts="$noseopts --exclude-dir=portas/tests/functional";;
-c|--coverage) noseopts="$noseopts --with-coverage --cover-package=glance";; -c|--coverage) noseopts="$noseopts --with-coverage --cover-package=portas";;
-*) noseopts="$noseopts $1";; -*) noseopts="$noseopts $1";;
*) noseargs="$noseargs $1" *) noseargs="$noseargs $1"
esac esac

View File

@ -7,3 +7,27 @@ source-dir = doc/source
tag_build = tag_build =
tag_date = 0 tag_date = 0
tag_svn_revision = 0 tag_svn_revision = 0
[compile_catalog]
directory = portas/locale
domain = portas
[update_catalog]
domain = portas
output_dir = portas/locale
input_file = portas/locale/portas.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = portas/locale/portas.pot
[nosetests]
# NOTE(jkoelker) To run the test suite under nose install the following
# coverage http://pypi.python.org/pypi/coverage
# tissue http://pypi.python.org/pypi/tissue (pep8 checker)
# openstack-nose https://github.com/jkoelker/openstack-nose
verbosity=2
cover-package = portas
cover-html = true
cover-erase = true

View File

@ -25,7 +25,8 @@ project = 'portas'
setuptools.setup( setuptools.setup(
name=project, name=project,
version=setup.get_version(project, '2013.1'), version=setup.get_version(project, '2013.1'),
description='The Portas project provides a simple WSGI server for Windows Environment Management', description='The Portas project provides a simple WSGI server for Windows '
'Environment Management',
license='Apache License (2.0)', license='Apache License (2.0)',
author='OpenStack', author='OpenStack',
author_email='openstack@lists.launchpad.net', author_email='openstack@lists.launchpad.net',
@ -43,6 +44,7 @@ setuptools.setup(
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Environment :: No Input/Output (Daemon)', 'Environment :: No Input/Output (Daemon)',
'Environment :: OpenStack', 'Environment :: OpenStack',
], ],
scripts=['bin/portas-api'], scripts=['bin/portas-api'],
py_modules=[]) py_modules=[]
)

View File

@ -1,23 +1,30 @@
# The greenlet package must be compiled with gcc and needs Babel
# the Python.h headers. Make sure you install the python-dev SQLAlchemy>=0.7,<=0.7.9
# package to get the right headers...
greenlet>=0.3.1
SQLAlchemy<=0.7.9
anyjson anyjson
eventlet>=0.9.12 eventlet>=0.9.12
PasteDeploy PasteDeploy
Routes routes
webob==1.0.8 WebOb>=1.2
wsgiref wsgiref
argparse argparse
sqlalchemy-migrate>=0.7.2 boto
sqlalchemy-migrate>=0.7
httplib2 httplib2
kombu kombu
pycrypto>=2.1.0alpha1
iso8601>=0.1.4 iso8601>=0.1.4
PyChef
# Note you will need gcc buildtools installed and must
# have installed libxml headers for lxml to be successfully
# installed using pip, therefore you will need to install the
# libxml2-dev and libxslt-dev Ubuntu packages.
lxml
# For paste.util.template used in keystone.common.template # For paste.util.template used in keystone.common.template
Paste Paste
passlib passlib
puka jsonschema
python-keystoneclient>=0.2.0
http://tarballs.openstack.org/oslo-config/oslo-config-2013.1b4.tar.gz#egg=oslo-config

View File

@ -0,0 +1,19 @@
# Packages needed for dev testing
distribute>=0.6.24
# Needed for testing
coverage
fixtures>=0.3.12
mox
nose
nose-exclude
openstack.nose_plugin>=0.7
nosehtmloutput>=0.0.3
pep8==1.3.3
sphinx>=1.1.2
requests
testtools>=0.9.22
# Optional packages that should be installed when testing
xattr>=0.6.0
pysendfile==2.0.0