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/
#virtualenv

View File

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

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2010 OpenStack Foundation.
#
@ -15,8 +16,8 @@
# limitations under the License.
#
# Glance documentation build configuration file, created by
# sphinx-quickstart on Tue May 18 13:50:15 2010.
# Portas documentation build configuration file, created by
# sphinx-quickstart on Tue February 28 13:50:15 2013.
#
# This file is execfile()'d with the current directory set to its containing
# dir.
@ -33,7 +34,7 @@ import sys
# 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
# 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('../../bin')
] + sys.path
@ -65,19 +66,19 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = u'Glance'
copyright = u'2010, OpenStack Foundation.'
project = u'Portas'
copyright = u'2013, Mirantis, Inc.'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# 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.
release = glance_version.version_string_with_vcs()
release = portas_version.version_string_with_vcs()
# 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
# for a list of supported languages.
@ -114,7 +115,7 @@ show_authors = True
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['glance.']
modindex_common_prefix = ['portas.']
# -- Options for man page output --------------------------------------------
@ -122,25 +123,7 @@ modindex_common_prefix = ['glance.']
# List of tuples 'sourcefile', 'target', u'title', u'Authors name', 'manual'
man_pages = [
('man/glance', 'glance', u'Glance CLI',
[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',
('man/portasapi', 'portas-api', u'Portas API Server',
[u'OpenStack'], 1)
]
@ -219,7 +202,7 @@ html_use_index = False
#html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'glancedoc'
htmlhelp_basename = 'portasdoc'
# -- Options for LaTeX output ------------------------------------------------
@ -234,8 +217,8 @@ htmlhelp_basename = 'glancedoc'
# (source start file, target name, title, author,
# documentclass [howto/manual]).
latex_documents = [
('index', 'Glance.tex', u'Glance Documentation',
u'Glance Team', 'manual'),
('index', 'Portas.tex', u'Portas Documentation',
u'Keero Team', 'manual'),
]
# 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
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'python': ('http://docs.python.org/', None),
'nova': ('http://nova.openstack.org', None),
'swift': ('http://swift.openstack.org', None)}
intersphinx_mapping = {'python': ('http://docs.python.org/', None)}

View File

@ -14,70 +14,7 @@
License for the specific language governing permissions and limitations
under the License.
Welcome to Glance's documentation!
Welcome to Portas's documentation!
==================================
The Glance project provides services for discovering, registering, and
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
We rule the world!

View File

@ -1,57 +1,5 @@
[DEFAULT]
# 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:portas-api]
pipeline = apiv1app
# NOTE: use the following pipeline for keystone
#pipeline = authtoken context apiv1app
[app:apiv1app]
paste.app_factory = windc.common.wsgi:app_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
paste.app_factory = portas.api.v1.router:API.factory

View File

@ -13,22 +13,7 @@ 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
log_file = /tmp/portas-api.log
[pipeline:windc-api]
pipeline = versionnegotiation context apiv1app
[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
#A valid SQLAlchemy connection string for the metadata database
sql_connection = sqlite:///portas.sqlite

View File

@ -1,7 +1,7 @@
[DEFAULT]
# 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
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
# License for the specific language governing permissions and limitations
# under the License.
from glance.api.v1 import images
from glance.api.v1 import members
from glance.common import wsgi
import routes
from portas.openstack.common import wsgi
from portas.api.v1 import environments
class API(wsgi.Router):
"""WSGI router for Glance v1 API requests."""
@classmethod
def factory(cls, global_conf, **local_conf):
return cls(routes.Mapper())
def __init__(self, mapper):
images_resource = images.create_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"]))
environments_resource = environments.create_resource()
mapper.resource("environment", "environments", controller=environments_resource)
super(API, self).__init__(mapper)

View File

@ -29,71 +29,40 @@ import sys
from oslo.config import cfg
from paste import deploy
from glance.version import version_info as version
from portas.version import version_info as version
paste_deploy_opts = [
cfg.StrOpt('flavor'),
cfg.StrOpt('config_file'),
]
common_opts = [
cfg.BoolOpt('allow_additional_image_properties', default=True,
help=_('Whether to allow users to specify image properties '
'beyond what the image schema provides')),
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.')),
bind_opts = [
cfg.StrOpt('bind_host', default='localhost'),
cfg.IntOpt('bind_port'),
]
CONF = cfg.CONF
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('debug', 'glance.openstack.common.log')
CONF.import_opt('log_dir', 'glance.openstack.common.log')
CONF.import_opt('log_file', 'glance.openstack.common.log')
CONF.import_opt('log_config', 'glance.openstack.common.log')
CONF.import_opt('log_format', 'glance.openstack.common.log')
CONF.import_opt('log_date_format', 'glance.openstack.common.log')
CONF.import_opt('use_syslog', 'glance.openstack.common.log')
CONF.import_opt('syslog_log_facility', 'glance.openstack.common.log')
CONF.import_opt('verbose', 'portas.openstack.common.log')
CONF.import_opt('debug', 'portas.openstack.common.log')
CONF.import_opt('log_dir', 'portas.openstack.common.log')
CONF.import_opt('log_file', 'portas.openstack.common.log')
CONF.import_opt('log_config', 'portas.openstack.common.log')
CONF.import_opt('log_format', 'portas.openstack.common.log')
CONF.import_opt('log_date_format', 'portas.openstack.common.log')
CONF.import_opt('use_syslog', 'portas.openstack.common.log')
CONF.import_opt('syslog_log_facility', 'portas.openstack.common.log')
def parse_args(args=None, usage=None, default_config_files=None):
CONF(args=args,
project='glance',
project='portas',
version=version.cached_version_string(),
usage=usage,
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():
"""
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
# under the License.
"""Glance exception subclasses"""
import urlparse
"""Portas exception subclasses"""
_FATAL_EXCEPTION_FORMAT_ERRORS = False
class RedirectException(Exception):
def __init__(self, url):
self.url = urlparse.urlparse(url)
class GlanceException(Exception):
class PortasException(Exception):
"""
Base Glance Exception
Base Portas Exception
To correctly use this class, inherit from it and define
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
pass
super(GlanceException, self).__init__(message)
super(PortasException, self).__init__(message)
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):
class SchemaLoadError(PortasException):
message = _("Unable to load schema: %(reason)s")
class InvalidObject(GlanceException):
class InvalidObject(PortasException):
message = _("Provided object does not match schema "
"'%(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]
# Used to identify which repository this database is versioned under.
# 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.
# 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.types import Integer, String, Text, DateTime
from sqlalchemy.types import String, Text, DateTime
meta = MetaData()
Table('datacenter', meta,
Column('id', String(32), primary_key=True),
Column('name', String(255)),
Column('type', String(255)),
Column('version', String(255)),
Column('tenant_id',String(100)),
Column('KMS', String(80)),
Column('WSUS', String(80)),
Column('extra', Text()),
Table('environment', meta,
Column('id', String(32), primary_key=True),
Column('name', String(255)),
Column('created', DateTime(), nullable=False),
Column('updated', DateTime(), nullable=False),
Column('tenant_id', String(36)),
Column('description', Text()),
)
Table('service', meta,
Column('id', String(32), primary_key=True),
Column('datacenter_id', String(32), ForeignKey('datacenter.id')),
Column('name', String(255)),
Column('type', String(40)),
Column('status', String(255)),
Column('tenant_id', String(40)),
Column('created_at', DateTime, nullable=False),
Column('updated_at', DateTime, nullable=False),
Column('deployed', String(40)),
Column('vm_id',String(40)),
Column('extra', Text()),
Column('id', String(32), primary_key=True),
Column('name', String(255)),
Column('type', String(40)),
Column('environment_id', String(32), ForeignKey('environment.id')),
Column('created', DateTime, nullable=False),
Column('updated', DateTime, nullable=False),
Column('description', Text()),
)
def upgrade(migrate_engine):
meta.bind = migrate_engine
meta.create_all()

View File

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

View File

@ -17,19 +17,20 @@
# 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.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 import UniqueConstraint
import glance.db.sqlalchemy.api
from glance.openstack.common import timeutils
from glance.openstack.common import uuidutils
from portas.openstack.common import timeutils
from portas.openstack.common import uuidutils
from portas.db.session import get_session
BASE = declarative_base()
@ -40,22 +41,16 @@ def compile_big_int_sqlite(type_, compiler, **kw):
class ModelBase(object):
"""Base class for Nova and Glance Models"""
__table_args__ = {'mysql_engine': 'InnoDB'}
__table_initialized__ = False
__protected_attributes__ = set([
"created_at", "updated_at", "deleted_at", "deleted"])
__protected_attributes__ = {"created", "updated"}
created_at = Column(DateTime, default=timeutils.utcnow,
nullable=False)
updated_at = Column(DateTime, default=timeutils.utcnow,
nullable=False, onupdate=timeutils.utcnow)
deleted_at = Column(DateTime)
deleted = Column(Boolean, nullable=False, default=False)
created = Column(DateTime, default=timeutils.utcnow,
nullable=False)
updated = Column(DateTime, default=timeutils.utcnow,
nullable=False, onupdate=timeutils.utcnow)
def save(self, session=None):
"""Save this object"""
session = session or glance.db.sqlalchemy.api.get_session()
session = session or get_session()
session.add(self)
session.flush()
@ -94,80 +89,55 @@ class ModelBase(object):
return self.__dict__.items()
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):
"""Represents an image in the datastore"""
__tablename__ = 'images'
class JsonBlob(TypeDecorator):
impl = Text
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)
name = Column(String(255))
disk_format = Column(String(20))
container_format = Column(String(20))
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)
tenant_id = Column(String(36))
description = Column(JsonBlob())
class ImageProperty(BASE, ModelBase):
"""Represents an image properties in the datastore"""
__tablename__ = 'image_properties'
__table_args__ = (UniqueConstraint('image_id', 'name'), {})
class Service(BASE, ModelBase):
"""
Represents an instance of service.
id = Column(Integer, primary_key=True)
image_id = Column(String(36), ForeignKey('images.id'),
nullable=False)
image = relationship(Image, backref=backref('properties'))
:var name: string
:var type: string - type of service (e.g. Active Directory)
"""
__tablename__ = 'service'
id = Column(String(36), primary_key=True, default=uuidutils.generate_uuid)
name = Column(String(255), index=True, nullable=False)
value = Column(Text)
class ImageTag(BASE, ModelBase):
"""Represents an image tag in the datastore"""
__tablename__ = 'image_tags'
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")
type = Column(String(255), index=True, nullable=False)
environment_id = Column(String(36), ForeignKey('environment.id'))
environment = relationship(Environment,
backref=backref('service', order_by=id),
uselist=False)
description = Column(JsonBlob())
def register_models(engine):
"""
Creates database tables for all models with the given engine
"""
models = (Image, ImageProperty, ImageMember)
models = (Environment, Service)
for model in models:
model.metadata.create_all(engine)
@ -176,6 +146,6 @@ def unregister_models(engine):
"""
Drops database tables for all models with the given engine
"""
models = (Image, ImageProperty)
models = (Environment, Service)
for model in models:
model.metadata.drop_all(engine)

View File

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

View File

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

View File

@ -15,6 +15,6 @@
# 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;;
-f|--force) let force=1;;
-u|--update) update=1;;
--unittests-only) noseopts="$noseopts --exclude-dir=glance/tests/functional";;
-c|--coverage) noseopts="$noseopts --with-coverage --cover-package=glance";;
--unittests-only) noseopts="$noseopts --exclude-dir=portas/tests/functional";;
-c|--coverage) noseopts="$noseopts --with-coverage --cover-package=portas";;
-*) noseopts="$noseopts $1";;
*) noseargs="$noseargs $1"
esac

View File

@ -7,3 +7,27 @@ source-dir = doc/source
tag_build =
tag_date = 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(
name=project,
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)',
author='OpenStack',
author_email='openstack@lists.launchpad.net',
@ -43,6 +44,7 @@ setuptools.setup(
'Programming Language :: Python :: 2.7',
'Environment :: No Input/Output (Daemon)',
'Environment :: OpenStack',
],
],
scripts=['bin/portas-api'],
py_modules=[])
py_modules=[]
)

View File

@ -1,23 +1,30 @@
# The greenlet package must be compiled with gcc and needs
# the Python.h headers. Make sure you install the python-dev
# package to get the right headers...
greenlet>=0.3.1
SQLAlchemy<=0.7.9
Babel
SQLAlchemy>=0.7,<=0.7.9
anyjson
eventlet>=0.9.12
PasteDeploy
Routes
webob==1.0.8
routes
WebOb>=1.2
wsgiref
argparse
sqlalchemy-migrate>=0.7.2
boto
sqlalchemy-migrate>=0.7
httplib2
kombu
pycrypto>=2.1.0alpha1
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
Paste
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