Remove old Flask Refstack application
It will be replaced with API v1 written on Pecan https://storyboard.openstack.org/#!/story/132 Change-Id: I69a27b640544c22790e768be440a8396c9c3b7b8
This commit is contained in:
parent
5cae323d2b
commit
3f56fde9c1
@ -4,7 +4,6 @@ RefStack/TCUP and Driver Test
|
||||
This project support collection and publication of Community Test results for OpenStack. There are multiple components of this effort:
|
||||
|
||||
* `RefStack <doc/refstack.md>`_: Community-facing API for registration of interop-compliance endpoints for on-demand testing.
|
||||
* `TCUP <doc/tcup.md>`_: Portable, Containerized Tempest for Community running and reporting results to RefStack
|
||||
* Driver Test
|
||||
|
||||
Participate
|
||||
@ -31,10 +30,3 @@ Collecting the Results > Running RefStack
|
||||
RefStack is a Web UI and API used to collect and display test results. This information is used by the DefCore committee to help select must-pass capabilities.
|
||||
|
||||
`RefStack docs <doc/refstack.md>`_
|
||||
|
||||
Test your Cloud and Share! > Running TCUP
|
||||
-----------------------------------------
|
||||
|
||||
TCUP (Tempest in a Container to Upload from Probe) is a portable way for community members to quickly and consistently run Tempest against private and public clouds.
|
||||
|
||||
`TCUP docs <doc/tcup.md>`_
|
||||
|
@ -1,24 +0,0 @@
|
||||
"""added_alt_user
|
||||
|
||||
Revision ID: 121ee191d348
|
||||
Revises: 2d1f3e3cd357
|
||||
Create Date: 2014-04-07 11:43:51.800255
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '121ee191d348'
|
||||
down_revision = '2d1f3e3cd357'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column(
|
||||
'cloud',
|
||||
sa.Column('alt_user', sa.String(length=80), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('cloud', 'alt_user')
|
@ -1,28 +0,0 @@
|
||||
"""added architecture to cloud
|
||||
|
||||
Revision ID: 2d1f3e3cd357
|
||||
Revises: 4a425a7aff50
|
||||
Create Date: 2014-03-07 12:11:20.403933
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '2d1f3e3cd357'
|
||||
down_revision = '4a425a7aff50'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column(
|
||||
'cloud',
|
||||
sa.Column('architecture', sa.String(length=40), nullable=True))
|
||||
### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('cloud', 'architecture')
|
||||
### end Alembic commands ###
|
@ -1,100 +0,0 @@
|
||||
"""major restructure
|
||||
|
||||
Revision ID: 4a425a7aff50
|
||||
Revises: None
|
||||
Create Date: 2014-03-07 11:20:58.899889
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '4a425a7aff50'
|
||||
down_revision = None
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
'vendor',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('vendor_name', sa.String(length=80), nullable=True),
|
||||
sa.Column('contact_email', sa.String(length=120), nullable=True),
|
||||
sa.Column('contact_name', sa.String(length=120), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('contact_email'),
|
||||
sa.UniqueConstraint('vendor_name')
|
||||
)
|
||||
op.create_table(
|
||||
'user',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('vendor_id', sa.Integer(), nullable=True),
|
||||
sa.Column('name', sa.String(length=60), nullable=True),
|
||||
sa.Column('email', sa.String(length=200), nullable=True),
|
||||
sa.Column('email_verified', sa.Boolean(), nullable=True),
|
||||
sa.Column('openid', sa.String(length=200), nullable=True),
|
||||
sa.Column('authorized', sa.Boolean(), nullable=True),
|
||||
sa.Column('su', sa.Boolean(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['vendor_id'], ['vendor.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('email'),
|
||||
sa.UniqueConstraint('openid')
|
||||
)
|
||||
op.create_table(
|
||||
'apikey',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=60), nullable=True),
|
||||
sa.Column('key', sa.String(length=200), nullable=True),
|
||||
sa.Column('openid', sa.String(length=200), nullable=True),
|
||||
sa.Column('timestamp', sa.DateTime(), nullable=True),
|
||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table(
|
||||
'cloud',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('label', sa.String(length=60), nullable=True),
|
||||
sa.Column('endpoint', sa.String(length=512), nullable=True),
|
||||
sa.Column('endpoint_v3', sa.String(length=512), nullable=True),
|
||||
sa.Column('admin_endpoint', sa.String(length=512), nullable=True),
|
||||
sa.Column('test_user', sa.String(length=80), nullable=True),
|
||||
sa.Column('admin_user', sa.String(length=80), nullable=True),
|
||||
sa.Column('version', sa.String(length=80), nullable=True),
|
||||
sa.Column('tempest_sha', sa.String(length=128), nullable=True),
|
||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table(
|
||||
'test',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('cloud_id', sa.Integer(), nullable=True),
|
||||
sa.Column('finished', sa.Boolean(), nullable=True),
|
||||
sa.Column('subunit', sa.String(length=4096), nullable=True),
|
||||
sa.Column('parsed', sa.String(length=4096), nullable=True),
|
||||
sa.ForeignKeyConstraint(['cloud_id'], ['cloud.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table(
|
||||
'test_status',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('test_id', sa.Integer(), nullable=True),
|
||||
sa.Column('message', sa.String(length=1024), nullable=True),
|
||||
sa.Column('timestamp', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['test_id'], ['test.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('test_status')
|
||||
op.drop_table('test')
|
||||
op.drop_table('cloud')
|
||||
op.drop_table('apikey')
|
||||
op.drop_table('user')
|
||||
op.drop_table('vendor')
|
||||
### end Alembic commands ###
|
@ -1,32 +0,0 @@
|
||||
Below Instructions guide you to run tempest manually from tcup container :
|
||||
|
||||
Pre-requisites : set up docker container by following instructions in the tcup.md document
|
||||
Once you are in TCUP container, make sure you can able to ping $OS_AUTH_URL
|
||||
|
||||
Instructions :
|
||||
|
||||
1)cd tempest/etc
|
||||
|
||||
2)copy tempest.conf.sample tempest.conf
|
||||
|
||||
3)update the tempest.conf file with the target cloud values(AUTH_URL,User name,Password,Tenent name,Tenent Id,IMAGE_ID,IMAGE_ID_ALT)
|
||||
|
||||
|
||||
4)update below attributes in tempest.conf with the OS_VALUES then save the tempest.conf file
|
||||
|
||||
**Tempest.conf** ** Replace with ****
|
||||
uri = OS_AUTH_URL
|
||||
username = OS_USERNAME
|
||||
password = OS_PASSWORD
|
||||
tenant name = OS_TENANT_NAME
|
||||
image_ref = {$IMAGE_ID}(run glance image-list command on target cloud,
|
||||
copy image id then replace here)
|
||||
image_ref_alt = {$IMAGE_ID_ALT}(run glance image-list command on target
|
||||
cloud,copy image id then replace here)
|
||||
5)cd tempest
|
||||
|
||||
6)nosetests -v tempest ( to run full tempest suits)
|
||||
|
||||
If you want to stop the tempest execution at the first failure,use below command
|
||||
|
||||
7)nosetests -vx tempest
|
70
doc/tcup.md
70
doc/tcup.md
@ -1,70 +0,0 @@
|
||||
TCUP Configuration
|
||||
===========================
|
||||
|
||||
The following instructions are designs run Refstack/Tempest in a container with minimal setup on your system.
|
||||
|
||||
> These steps are do not install Refstack for contributions or development, they are intended for a user who wants to just run and report test results.
|
||||
|
||||
1. Make sure you have python and wget installed for your operating system.
|
||||
|
||||
1. Install Docker using [[https://www.docker.io/gettingstarted/#h_installation]]
|
||||
1. Note: if you are in an environment with a proxy, make sure you configure `/etc/default/docker` to leverage the proxy too!
|
||||
1. You may want to prep-the environment using `sudo docker pull ubuntu:13.10`
|
||||
|
||||
1. Setup Docker to run without sudo
|
||||
1. permanently (recommended):
|
||||
1. `sudo usermod -a -G docker <your-user>`
|
||||
1. you will need to reboot after this change
|
||||
1. short term: `sudo chmod 666 /var/run/docker.sock`
|
||||
|
||||
1. Get the code: `wget https://raw.githubusercontent.com/stackforge/refstack/master/scripts/tcup.py`
|
||||
1. note: you can also get the code by cloning the Refstack and running the code in `/scripts/tcup.py`
|
||||
|
||||
1. Set your environment variables to access the test target cloud
|
||||
1. generally, you will `source openrc.sh` to load the cloud credentials and URLs
|
||||
|
||||
Note : once you have loaded openrc.sh, type this command : ‘env | grep OS_’ then make sure values are assigned to each of the below:
|
||||
|
||||
*OS_TENANT_ID
|
||||
*OS_PASSWORD
|
||||
*OS_AUTH_URL
|
||||
*OS_USERNAME
|
||||
*OS_TENANT_NAME
|
||||
|
||||
If values are missing for any of the above, you could manually export for missing env variable (ex:‘export OS_PASSWORD=<YourPassword>’)
|
||||
|
||||
1. Run TCUP: `python tcup.py`
|
||||
1. if you want to work on the code from Refstack, use `scripts/tcup.py'
|
||||
|
||||
|
||||
## Troubleshooting TCUP
|
||||
|
||||
Before troubleshooting TCUP, make sure that you have network connectivity to our target cloud from the host system (the one you run TCUP on).
|
||||
1. ping the IP or FQDN from `echo $OS_AUTH_URL`
|
||||
1. get a "HTTP/1.1 200 OK" response from `curl -I $OS_AUTH_URL`
|
||||
|
||||
There are several ways to trouble shoot, TCUP.
|
||||
|
||||
1. Run TCUP using the debug flag: `tcup.py --debug`
|
||||
1. Attach to the container as instructed at the end of the TCUP script
|
||||
1. Inside the container:
|
||||
1. check your environment variables include the OS_* values using `export | grep OS_`
|
||||
1. ping the IP or FQDN from `echo $OS_AUTH_URL`
|
||||
1. get "200 OK" from Keystone using `curl -I $OS_AUTH_URL`
|
||||
1. confirm your credentials are working using `keystone catalog`
|
||||
|
||||
## Docker Tips
|
||||
|
||||
1. You can inspect which containers are running!
|
||||
1. `docker ps` shows the running containers
|
||||
1. `docker attach` allows you to connect to a container (may have to press enter)
|
||||
1. exit from inside the container with `Ctrl-p` + `Ctrl-q`
|
||||
1. Orphaned Containers: Over time, you may end up with [orphaned contaniers](http://jimhoskins.com/2013/07/27/remove-untagged-docker-images.html), use the following to clean them up
|
||||
1. `docker rm $(docker ps -a -q)`
|
||||
1. `docker rmi $(docker images | grep "^<none>" | awk "{print $3}")`
|
||||
|
||||
## For Developers
|
||||
|
||||
If you run TCUP in debug mode (`export DEBUG=true` or using `--debug` parameter) then TCUP will automatically mount your PWD as /dev.
|
||||
If you run TCUP from your Refstack clone, then you can work directly in Refstack code from inside
|
||||
a TCUP container from the /dev directory.
|
@ -1,28 +0,0 @@
|
||||
|
||||
import flask
|
||||
from flask.ext.admin.contrib import sqla
|
||||
|
||||
import models
|
||||
|
||||
# Global admin object
|
||||
from refstack.extensions import admin
|
||||
from refstack.extensions import db
|
||||
|
||||
|
||||
class SecureView(sqla.ModelView):
|
||||
def is_accessible(self):
|
||||
# let us look at the admin if we're in debug mode
|
||||
if flask.current_app.debug:
|
||||
return True
|
||||
return flask.g.user.su is not False
|
||||
|
||||
|
||||
def init_app(app):
|
||||
admin.init_app(app)
|
||||
|
||||
|
||||
def configure_admin(app):
|
||||
admin.add_view(SecureView(models.ApiKey, db.session))
|
||||
admin.add_view(SecureView(models.Cloud, db.session))
|
||||
admin.add_view(SecureView(models.User, db.session))
|
||||
admin.add_view(SecureView(models.Vendor, db.session))
|
@ -1,81 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2013 Piston Cloud Computing, Inc. 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.
|
||||
#
|
||||
"""Basic API code.
|
||||
|
||||
This is using Flask-Restless at the moment because it is super simple,
|
||||
but probably should be moved to a more versatile framework like
|
||||
Flask-Restful later on.
|
||||
"""
|
||||
|
||||
import flask
|
||||
from flask.ext import restless
|
||||
|
||||
import models
|
||||
from refstack.extensions import api
|
||||
|
||||
|
||||
def init_app(app, *args, **kw):
|
||||
api.init_app(app, *args, **kw)
|
||||
|
||||
|
||||
def access_control(**kw):
|
||||
if not flask.g.user:
|
||||
raise _not_authorized()
|
||||
|
||||
if not flask.g.user.su:
|
||||
return _not_authorized()
|
||||
|
||||
# That's it, we're defaulting to superuser only access
|
||||
# until we flesh this out further
|
||||
|
||||
|
||||
ALL_METHODS = {'GET_SINGLE': [access_control],
|
||||
'GET_MANY': [access_control],
|
||||
'PUT_SINGLE': [access_control],
|
||||
'PUT_MANY': [access_control],
|
||||
'POST': [access_control],
|
||||
'DELETE': [access_control]}
|
||||
|
||||
|
||||
def configure_api(app):
|
||||
cloud_api = api.create_api_blueprint(models.Cloud,
|
||||
preprocessors=ALL_METHODS)
|
||||
cloud_api.before_request(authenticate)
|
||||
app.register_blueprint(cloud_api)
|
||||
|
||||
|
||||
def _not_authorized():
|
||||
return restless.ProcessingException(message='Not Authorized',
|
||||
status_code=401)
|
||||
|
||||
|
||||
def authenticate():
|
||||
# If we're already authenticated, we can ignore this
|
||||
if flask.g.user:
|
||||
return
|
||||
|
||||
# Otherwise check headers
|
||||
openid = flask.request.headers.get('X-AUTH-OPENID')
|
||||
if openid:
|
||||
# In debug mode accept anything
|
||||
if flask.current_app.debug and False:
|
||||
flask.g.user = models.User.query.filter_by(openid=openid).first()
|
||||
return
|
||||
|
||||
apikey = flask.request.headers.get('X-AUTH-APIKEY')
|
||||
apikey_ref = models.ApiKey.query.filter_by(key=apikey)
|
||||
if apikey_ref['openid'] == openid:
|
||||
flask.g.user = apikey_ref.user
|
207
refstack/app.py
207
refstack/app.py
@ -1,207 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2013 Piston Cloud Computing, Inc. 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
|
||||
from flask import Flask, render_template
|
||||
from refstack.config import DefaultConfig
|
||||
import admin
|
||||
import api
|
||||
from refstack.extensions import db
|
||||
from refstack.extensions import oid
|
||||
|
||||
|
||||
from refstack import utils
|
||||
|
||||
|
||||
INSTANCE_FOLDER_PATH = utils.INSTANCE_FOLDER_PATH
|
||||
|
||||
|
||||
# For import *
|
||||
__all__ = ['create_app']
|
||||
|
||||
DEFAULT_BLUEPRINTS = tuple()
|
||||
# frontend,
|
||||
# user,
|
||||
# settings,
|
||||
# api,
|
||||
# admin,
|
||||
#)
|
||||
|
||||
|
||||
def create_app(config=None, app_name=None, blueprints=None):
|
||||
"""Create a Flask app."""
|
||||
|
||||
if app_name is None:
|
||||
app_name = DefaultConfig.PROJECT
|
||||
if blueprints is None:
|
||||
blueprints = DEFAULT_BLUEPRINTS
|
||||
|
||||
# NOTE(termie): Flask has this new instance_path stuff that allows
|
||||
# you to keep config and such in different places, but I don't really
|
||||
# see how that is going to be very helpful, so we're going to stick
|
||||
# to using config relative to the root unless we explicitly set such
|
||||
# a path in the INSTANCE_FOLDER_PATH environment variable.
|
||||
app = Flask(app_name,
|
||||
instance_path=INSTANCE_FOLDER_PATH,
|
||||
instance_relative_config=True)
|
||||
|
||||
configure_app(app, config)
|
||||
configure_hook(app)
|
||||
configure_blueprints(app, blueprints)
|
||||
configure_extensions(app)
|
||||
configure_logging(app)
|
||||
configure_template_filters(app)
|
||||
configure_error_handlers(app)
|
||||
|
||||
if app.debug:
|
||||
print(utils.dump_config(app))
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def configure_app(app, config=None):
|
||||
"""Different ways of configurations."""
|
||||
|
||||
# http://flask.pocoo.org/docs/api/#configuration
|
||||
app.config.from_object(DefaultConfig)
|
||||
|
||||
# If we've set the INSTANCE_FOLDER_PATH environment var, this may be
|
||||
# loaded from an instance folder, otherwise relative to flask.root_path.
|
||||
# http://flask.pocoo.org/docs/config/#instance-folders
|
||||
app.config.from_pyfile('refstack.cfg', silent=True)
|
||||
|
||||
if config:
|
||||
app.config.from_object(config)
|
||||
|
||||
|
||||
def configure_extensions(app):
|
||||
# flask-sqlalchemy
|
||||
db.init_app(app)
|
||||
|
||||
# flask-admin
|
||||
admin.init_app(app)
|
||||
admin.configure_admin(app)
|
||||
|
||||
# flask-restless
|
||||
api.init_app(app, flask_sqlalchemy_db=db)
|
||||
api.configure_api(app)
|
||||
|
||||
## flask-mail
|
||||
#mail.init_app(app)
|
||||
|
||||
## flask-cache
|
||||
#cache.init_app(app)
|
||||
|
||||
## flask-babel
|
||||
#babel = Babel(app)
|
||||
|
||||
#@babel.localeselector
|
||||
#def get_locale():
|
||||
# accept_languages = app.config.get('ACCEPT_LANGUAGES')
|
||||
# return request.accept_languages.best_match(accept_languages)
|
||||
|
||||
## flask-login
|
||||
#login_manager.login_view = 'frontend.login'
|
||||
#login_manager.refresh_view = 'frontend.reauth'
|
||||
|
||||
#@login_manager.user_loader
|
||||
#def load_user(id):
|
||||
# return User.query.get(id)
|
||||
#login_manager.setup_app(app)
|
||||
|
||||
# flask-openid
|
||||
oid.init_app(app)
|
||||
|
||||
|
||||
def configure_blueprints(app, blueprints):
|
||||
"""Configure blueprints in views."""
|
||||
|
||||
for blueprint in blueprints:
|
||||
app.register_blueprint(blueprint)
|
||||
|
||||
|
||||
def configure_template_filters(app):
|
||||
|
||||
@app.template_filter()
|
||||
def pretty_date(value):
|
||||
return pretty_date(value)
|
||||
|
||||
@app.template_filter()
|
||||
def format_date(value, format='%Y-%m-%d'):
|
||||
return value.strftime(format)
|
||||
|
||||
|
||||
def configure_logging(app):
|
||||
"""Configure file(info) and email(error) logging."""
|
||||
|
||||
if app.debug or app.testing:
|
||||
# Skip debug and test mode. Just check standard output.
|
||||
return
|
||||
|
||||
import logging
|
||||
from logging.handlers import SMTPHandler
|
||||
|
||||
# Set info level on logger, which might be overwritten by handlers.
|
||||
# Suppress DEBUG messages.
|
||||
app.logger.setLevel(logging.INFO)
|
||||
|
||||
info_log = os.path.join(app.config['LOG_FOLDER'], 'info.log')
|
||||
info_file_handler = logging.handlers.RotatingFileHandler(
|
||||
info_log, maxBytes=100000, backupCount=10)
|
||||
info_file_handler.setLevel(logging.INFO)
|
||||
info_file_handler.setFormatter(logging.Formatter(
|
||||
'%(asctime)s %(levelname)s: %(message)s '
|
||||
'[in %(pathname)s:%(lineno)d]')
|
||||
)
|
||||
app.logger.addHandler(info_file_handler)
|
||||
|
||||
# Testing
|
||||
#app.logger.info("testing info.")
|
||||
#app.logger.warn("testing warn.")
|
||||
#app.logger.error("testing error.")
|
||||
|
||||
mail_handler = SMTPHandler(app.config['MAIL_SERVER'],
|
||||
app.config['MAIL_USERNAME'],
|
||||
app.config['ADMINS'],
|
||||
'O_ops... %s failed!' % app.config['PROJECT'],
|
||||
(app.config['MAIL_USERNAME'],
|
||||
app.config['MAIL_PASSWORD']))
|
||||
mail_handler.setLevel(logging.ERROR)
|
||||
mail_handler.setFormatter(logging.Formatter(
|
||||
'%(asctime)s %(levelname)s: %(message)s '
|
||||
'[in %(pathname)s:%(lineno)d]')
|
||||
)
|
||||
app.logger.addHandler(mail_handler)
|
||||
|
||||
|
||||
def configure_hook(app):
|
||||
@app.before_request
|
||||
def before_request():
|
||||
pass
|
||||
|
||||
|
||||
def configure_error_handlers(app):
|
||||
|
||||
@app.errorhandler(403)
|
||||
def forbidden_page(error):
|
||||
return render_template("errors/forbidden_page.html"), 403
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(error):
|
||||
return render_template("errors/page_not_found.html"), 404
|
||||
|
||||
@app.errorhandler(500)
|
||||
def server_error_page(error):
|
||||
return render_template("errors/server_error.html"), 500
|
@ -1,10 +0,0 @@
|
||||
lockfile
|
||||
subunit
|
||||
python-daemon
|
||||
extras
|
||||
statsd>=1.0.0,<3.0
|
||||
sqlalchemy>=0.8.2,<0.9.0
|
||||
pyzmq>=13.1.0,<14.0.0
|
||||
git+https://github.com/openstack/tempest.git
|
||||
SQLAlchemy==0.8.1
|
||||
requests==1.2.3
|
@ -1,2 +0,0 @@
|
||||
python-subunit
|
||||
docutils==0.9.1
|
@ -1,19 +0,0 @@
|
||||
# content of: tox.ini , put in same dir as setup.py
|
||||
[tox]
|
||||
envlist = py27
|
||||
|
||||
|
||||
[testenv]
|
||||
# Set STATSD env variables so that statsd code paths are tested.
|
||||
setenv = STATSD_HOST=localhost
|
||||
STATSD_PORT=8125
|
||||
|
||||
VIRTUAL_ENV=/tmp/1/env
|
||||
changedir=/tmp/1
|
||||
commands=
|
||||
client.py {posargs:--config}
|
||||
|
||||
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
|
@ -1,90 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2013 Piston Cloud Computing, Inc. 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
|
||||
|
||||
from utils import make_dir, INSTANCE_FOLDER_PATH, PROJECT_ROOT
|
||||
|
||||
|
||||
class BaseConfig(object):
|
||||
"""base config object"""
|
||||
PROJECT = "refstack"
|
||||
|
||||
# The app root path, also can use flask.root_path.
|
||||
PROJECT_ROOT = PROJECT_ROOT
|
||||
|
||||
DEBUG = False
|
||||
TESTING = False
|
||||
|
||||
ADMINS = ['youremail@yourdomain.com']
|
||||
|
||||
# http://flask.pocoo.org/docs/quickstart/#sessions
|
||||
SECRET_KEY = 'secret key'
|
||||
|
||||
LOG_FOLDER = os.path.join(INSTANCE_FOLDER_PATH, 'logs')
|
||||
make_dir(LOG_FOLDER)
|
||||
|
||||
# Fild upload, should override in production.
|
||||
# Limited the maximum allowed payload to 16 megabytes.
|
||||
# http://flask.pocoo.org/docs/patterns/fileuploads/#improving-uploads
|
||||
MAX_CONTENT_LENGTH = 16 * 1024 * 1024
|
||||
UPLOAD_FOLDER = os.path.join(INSTANCE_FOLDER_PATH, 'uploads')
|
||||
make_dir(UPLOAD_FOLDER)
|
||||
|
||||
|
||||
class DefaultConfig(BaseConfig):
|
||||
"""default config thing"""
|
||||
DEBUG = True
|
||||
|
||||
# Flask-Sqlalchemy: http://packages.python.org/Flask-SQLAlchemy/config.html
|
||||
SQLALCHEMY_ECHO = True
|
||||
# SQLITE for prototyping.
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + \
|
||||
INSTANCE_FOLDER_PATH + '/db.sqlite'
|
||||
# MYSQL for production.
|
||||
#SQLALCHEMY_DATABASE_URI = \
|
||||
# 'mysql://username:password@server/db?charset=utf8'
|
||||
|
||||
# Flask-babel: http://pythonhosted.org/Flask-Babel/
|
||||
ACCEPT_LANGUAGES = ['zh']
|
||||
BABEL_DEFAULT_LOCALE = 'en'
|
||||
|
||||
# Flask-cache: http://pythonhosted.org/Flask-Cache/
|
||||
CACHE_TYPE = 'simple'
|
||||
CACHE_DEFAULT_TIMEOUT = 60
|
||||
|
||||
# Flask-mail: http://pythonhosted.org/flask-mail/
|
||||
MAIL_DEBUG = DEBUG
|
||||
MAIL_SERVER = 'smtp.gmail.com'
|
||||
MAIL_PORT = 587
|
||||
MAIL_USE_TLS = True
|
||||
MAIL_USE_SSL = False
|
||||
# Should put MAIL_USERNAME and MAIL_PASSWORD
|
||||
# in production under instance folder.
|
||||
MAIL_USERNAME = 'yourmail@gmail.com'
|
||||
MAIL_PASSWORD = 'yourpass'
|
||||
MAIL_DEFAULT_SENDER = MAIL_USERNAME
|
||||
|
||||
# Flask-openid: http://pythonhosted.org/Flask-OpenID/
|
||||
OPENID_FS_STORE_PATH = os.path.join(INSTANCE_FOLDER_PATH, 'openid')
|
||||
make_dir(OPENID_FS_STORE_PATH)
|
||||
|
||||
|
||||
class TestConfig(BaseConfig):
|
||||
TESTING = True
|
||||
WTF_CSRF_ENABLED = False
|
||||
|
||||
SQLALCHEMY_ECHO = False
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite://'
|
@ -1,17 +0,0 @@
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from flask import abort
|
||||
from flask.ext.login import current_user
|
||||
|
||||
|
||||
def admin_required(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if not current_user.is_admin():
|
||||
abort(403)
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
@ -1,37 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2013 Piston Cloud Computing, Inc.
|
||||
# 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 flask.ext.admin import Admin
|
||||
admin = Admin()
|
||||
|
||||
from flask.ext.restless import APIManager
|
||||
api = APIManager()
|
||||
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
db = SQLAlchemy()
|
||||
|
||||
from flask.ext.mail import Mail
|
||||
mail = Mail()
|
||||
|
||||
# TODO(termie): not used yet
|
||||
#from flask.ext.cache import Cache
|
||||
#cache = Cache()
|
||||
|
||||
from flask.ext.login import LoginManager
|
||||
login_manager = LoginManager()
|
||||
|
||||
from flask.ext.openid import OpenID
|
||||
oid = OpenID()
|
@ -1,11 +0,0 @@
|
||||
{% extends "layout.html" %}
|
||||
{% import 'admin/layout.html' as layout with context -%}
|
||||
{% block head_css %}
|
||||
<link href="{{ url_for('admin.static', filename='bootstrap/css/bootstrap.css') }}" rel="stylesheet">
|
||||
|
||||
<link href="{{ url_for('admin.static', filename='admin/css/admin.css') }}" rel="stylesheet">
|
||||
{% endblock %}
|
||||
{% block body %}{% endblock %}
|
||||
{% block tail_js %}
|
||||
<script src="{{ url_for('admin.static', filename='select2/select2.min.js') }}" type="text/javascript"></script>
|
||||
{% endblock %}
|
@ -1,22 +0,0 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Create Profile{% endblock %}
|
||||
{% block body %}
|
||||
<h2>Create Profile</h2>
|
||||
<p>
|
||||
Hey! This is the first time you signed in on this website. In
|
||||
order to proceed we need some extra information from you:
|
||||
<form action="" method=post>
|
||||
<dl>
|
||||
<dt>Name:
|
||||
<dd><input type=text name=name size=30 value="{{ request.values.name }}">
|
||||
<dt>E-Mail:
|
||||
<dd><input type=text name=email size=30 value="{{ request.values.email }}">
|
||||
</dl>
|
||||
<p>
|
||||
<input type=submit value="Create profile">
|
||||
<input type=hidden name=next value="{{ next }}">
|
||||
</form>
|
||||
<p>
|
||||
If you don't want to proceed, you can <a href="{{ url_for('logout')
|
||||
}}">sign out</a> again.
|
||||
{% endblock %}
|
@ -1,16 +0,0 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Edit Profile{% endblock %}
|
||||
{% block body %}
|
||||
<h2>Edit Profile</h2>
|
||||
<form action="" method=post>
|
||||
<dl>
|
||||
<dt>Name:
|
||||
<dd><input type=text name=name size=30 value="{{ form.name }}">
|
||||
<dt>E-Mail
|
||||
<dd><input type=text name=email size=30 value="{{ form.email }}">
|
||||
</dl>
|
||||
<p>
|
||||
<input type=submit value="Update profile">
|
||||
<input type=submit name=delete value="Delete">
|
||||
</form>
|
||||
{% endblock %}
|
@ -1 +0,0 @@
|
||||
page not found
|
@ -1,131 +0,0 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Welcome{% endblock %}
|
||||
{% block body %}
|
||||
<div class="panel panel-default unit one-of-two">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-cloud"></span> Clouds
|
||||
<button type="button"
|
||||
class="btn btn-default btn-xs"
|
||||
onclick="location.href='/create-cloud'"
|
||||
style="float: right;">
|
||||
<span class="glyphicon glyphicon-plus-sign"></span> add cloud
|
||||
</button>
|
||||
</div>
|
||||
<table class="table">
|
||||
{% for cloud in clouds %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ cloud.label }}
|
||||
</td>
|
||||
<td align="right">
|
||||
<div class="btn-group">
|
||||
<button type="button"
|
||||
class="btn btn-default btn-xs"
|
||||
rel="tooltip"
|
||||
title="Test Cloud"
|
||||
data-placement="top"
|
||||
onclick="window.location='/test-cloud/{{ cloud.id }}'">
|
||||
<span class="glyphicon glyphicon-play"></span>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-default btn-xs"
|
||||
rel="tooltip"
|
||||
title="Edit Cloud"
|
||||
data-placement="top"
|
||||
onclick="window.location='/edit-cloud/{{ cloud.id }}'">
|
||||
<span class="glyphicon glyphicon-pencil"></span>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-default btn-xs"
|
||||
rel="tooltip"
|
||||
title="Delete Cloud"
|
||||
data-placement="top"
|
||||
onclick="confirm_delete_cloud('{{ cloud.id }}','{{ cloud.label}}')">
|
||||
<span class="glyphicon glyphicon-trash"></span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default unit one-of-two">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-list"></span> Tests
|
||||
</div>
|
||||
<table class="table">
|
||||
{% for cloud in clouds %}
|
||||
{% for test in cloud.tests %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ cloud.label }} (Test ID: {{ test.id }} )
|
||||
</td>
|
||||
<td align="right">
|
||||
<div class="btn-group">
|
||||
{% if test.finished %}
|
||||
<button type="button"
|
||||
class="btn btn-default btn-xs"
|
||||
rel="tooltip"
|
||||
title="Show Report"
|
||||
data-placement="top"
|
||||
onclick="window.location='/show-report/{{ test.id }}'">
|
||||
<span class="glyphicon glyphicon-file"></span>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-default btn-xs"
|
||||
rel="tooltip"
|
||||
title="Download Result"
|
||||
data-placement="top"
|
||||
onclick="window.location='/download-result/{{ test.id }}'">
|
||||
<span class="glyphicon glyphicon-cloud-download"></span>
|
||||
</button>
|
||||
{% else %}
|
||||
<button type="button"
|
||||
class="btn btn-default btn-xs"
|
||||
rel="tooltip"
|
||||
title="Test In Progress..."
|
||||
data-placement="top"
|
||||
onclick="window.location='/show-status/{{ test.id }}'">
|
||||
<span class="glyphicon glyphicon-refresh"></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type="button"
|
||||
class="btn btn-default btn-xs"
|
||||
rel="tooltip"
|
||||
title="Delete Test"
|
||||
data-placement="top"
|
||||
onclick="confirm_delete_test('{{ test.id }}','{{ cloud.label}}')">
|
||||
<span class="glyphicon glyphicon-trash"></span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
$("[rel='tooltip']").tooltip({container:'body'});
|
||||
}
|
||||
);
|
||||
function confirm_delete_cloud(cloud_id,label)
|
||||
{
|
||||
var r=confirm("Are you sure you want to delete " + label + "?");
|
||||
if (r==true)
|
||||
{
|
||||
window.location='/delete-cloud/' + cloud_id;
|
||||
}
|
||||
}
|
||||
function confirm_delete_test(test_id,label)
|
||||
{
|
||||
var r=confirm("Are you sure you want to delete test " + test_id + " of " + label + "?");
|
||||
if (r==true)
|
||||
{
|
||||
window.location='/delete-test/' + test_id;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
@ -1,41 +0,0 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Welcome{% endblock %}
|
||||
{% block body %}
|
||||
<div class="unit span-grid">
|
||||
<h2>What is Refstack?</h2>
|
||||
</div>
|
||||
<div class="unit one-of-three index_left option">
|
||||
<span class="glyphicon glyphicon-check"></span>
|
||||
<p>An existence proof of the certified openstack APIs.</p>
|
||||
</div>
|
||||
<div class="unit one-of-three index_center option">
|
||||
<span class="glyphicon glyphicon-check"></span>
|
||||
<p>A reference OpenStack environment for tools developers.</p>
|
||||
</div>
|
||||
<div class="unit one-of-three index_right option">
|
||||
<span class="glyphicon glyphicon-check"></span>
|
||||
<p>A certification process for OpenStack service and product vendors.</p>
|
||||
</div>
|
||||
<div class="unit span-grid">
|
||||
<br/>
|
||||
Vendors that are registered with RefStack:<br/><br/>
|
||||
<ul class="vendors">
|
||||
{% for vendor in vendors %}
|
||||
<li>{{ vendor.vendor_name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="unit one-of-two">
|
||||
<h3>Get involved!</h3>
|
||||
We welcome collaboration. <Br>
|
||||
To join the conversation jump into <a href="irc://irc.freenode.net/channel?refstack">#refstack</a> on freenode.
|
||||
<br/><br/>
|
||||
For a living blueprint see our <a href="https://etherpad.openstack.org/RefStackBlueprint">etherpad</a>!
|
||||
</div>
|
||||
<div class="unit one-of-two">
|
||||
<h3>Refstack CLI</h3>
|
||||
The refstack command line interface allows you to pre-test your cloud. <br>
|
||||
<br/>
|
||||
Documentation coming soon!
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,99 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}Welcome{% endblock %} | RefStack</title>
|
||||
<script src="{{ url_for('static', filename='jquery-1.10.1.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"></script>
|
||||
<!-- refstack specific js-->
|
||||
|
||||
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css') }}">
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/toast.css">
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='refstack.css') }}">
|
||||
<!-- compiled and minified bootstrap JavaScript -->
|
||||
|
||||
{% block head_css %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<a href="/" class="logo">
|
||||
<span class="glyphicon glyphicon-check"></span>
|
||||
Refstack
|
||||
</a>
|
||||
<ul class="ref_nav">
|
||||
{% if g.user %}
|
||||
{% if g.user.su %}
|
||||
<li>
|
||||
<span class="glyphicon glyphicon-th-large"></span>
|
||||
<a href="{{ url_for('index') }}">Dashboard</a>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
<a data-toggle="dropdown" href="#">
|
||||
|
||||
Admin
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li>
|
||||
<a href="/admin/vendorview/">Vendors</a>
|
||||
</li>
|
||||
<li >
|
||||
<a href="/admin/cloudview/">Clouds</a>
|
||||
</li>
|
||||
<li >
|
||||
<a href="/admin/userview/">Users</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<span class="glyphicon glyphicon-user"></span>
|
||||
<a href="{{ url_for('view_profile') }}">{{ g.user.name }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<span class="glyphicon glyphicon-log-out"></span>
|
||||
<a href="{{ url_for('logout') }}">sign out</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<span class="glyphicon glyphicon-th-large"></span>
|
||||
<a href="{{ url_for('index') }}">Dashboard</a>
|
||||
</li>
|
||||
<li>
|
||||
<span class="glyphicon glyphicon-user"></span>
|
||||
<a href="{{ url_for('view_profile') }}">{{ g.user.name }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<span class="glyphicon glyphicon-log-out"></span>
|
||||
<a href="{{ url_for('logout') }}">sign out</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<li>
|
||||
<span class="glyphicon glyphicon-home"></span>
|
||||
<a href="{{ url_for('index') }}">Overview</a>
|
||||
</li>
|
||||
<li>
|
||||
<span class="glyphicon glyphicon-log-in"></span>
|
||||
<a href="{{ url_for('login') }}">sign in</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="inner_container">
|
||||
{% for message in get_flashed_messages() %}
|
||||
<p class=message>{{ message }}</p>
|
||||
{% endfor %}
|
||||
<div class="">
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% block tail_js %}{% endblock %}
|
||||
<script src="{{ url_for('static', filename='refstack.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,13 +0,0 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Sign in{% endblock %}
|
||||
{% block body %}
|
||||
<h2>Sign in</h2>
|
||||
<form action="" method=post>
|
||||
{% if error %}<p class=error><strong>Error:</strong> {{ error }}</p>{% endif %}
|
||||
<p>
|
||||
OpenID:
|
||||
<input type=text name=openid size=30>
|
||||
<input type=submit value="Sign in">
|
||||
<input type=hidden name=next value="{{ next }}">
|
||||
</form>
|
||||
{% endblock %}
|
@ -1,142 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}Welcome{% endblock %} | RefStack</title>
|
||||
<script src="{{ url_for('static', filename='jquery-1.10.1.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"></script>
|
||||
<!-- refstack specific js-->
|
||||
|
||||
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css') }}">
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/toast.css">
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='refstack.css') }}">
|
||||
<!-- compiled and minified bootstrap JavaScript -->
|
||||
|
||||
{% block head_css %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<a href="/" class="logo">
|
||||
<span class="glyphicon glyphicon-check"></span>
|
||||
Refstack
|
||||
</a>
|
||||
</div>
|
||||
<div class="inner_container">
|
||||
|
||||
<div class="panel panel-default unit one-of-two">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-cloud"></span> Forced Results
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
<li class="heading list-group-item">compute</li>
|
||||
<li class="list-group-item">aggregates<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">availability zone<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">fixed ips<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">flavors access<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">flavors extra specs<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">flavors<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">hosts<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">hypervisor<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">quotas<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">servers<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">services<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">simple tenant usage<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="heading list-group-item">flavors</li>
|
||||
<li class="list-group-item">floating ips<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">floating ips actions<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">list floating ips<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="heading list-group-item">images</li>
|
||||
<li class="list-group-item">image metadata<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">images oneserver<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">images<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">list image filters<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">list images<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="heading list-group-item">keypairs</li>
|
||||
<li class="list-group-item">keypairs<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="heading list-group-item">limits</li>
|
||||
<li class="list-group-item">absolute limits<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="heading list-group-item">security groups</li>
|
||||
<li class="list-group-item">security group rules<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">security groups<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="heading list-group-item">servers</li>
|
||||
<li class="list-group-item">attach interfaces<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">create server<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">disk config<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">instance actions<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">list server filters<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">list servers negative<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">multiple create<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">server actions<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">server addresses<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">server metadata<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">server personality<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">server rescue<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">servers negative<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">servers<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">virtual interfaces<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">authorization<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">auth token<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">extensions<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">live block migration<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">quotas<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="heading list-group-item">volumes</li>
|
||||
<li class="list-group-item">attach volume<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">volumes get<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">volumes list<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">volumes negative<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="heading list-group-item">identity</li>
|
||||
<li class="list-group-item">admin<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">roles<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">services<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">tenants<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">users<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="heading list-group-item">image</li>
|
||||
<li class="sub-heading list-group-item">v1</li>
|
||||
<li class="list-group-item">image members<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">images<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="sub-heading list-group-item">v2</li>
|
||||
<li class="list-group-item">images<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="heading list-group-item">network</li>
|
||||
<li class="list-group-item">floating ips<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">load balancer<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">networks<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">quotas<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">routers<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">security groups<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="heading list-group-item">object storage</li>
|
||||
<li class="list-group-item">account quotas<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">account services<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">container acl<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">container services<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">container staticweb<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">container sync<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">object expiry<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">object services<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">object version<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="heading list-group-item">orchestration</li>
|
||||
<li class="list-group-item">neutron resources<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">non empty stack<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">server cfn init<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">stacks<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="list-group-item">templates<span class="glyphicon glyphicon-ok-circle"></span></li>
|
||||
<li class="heading list-group-item">volume</li>
|
||||
<li class="list-group-item">multi backend<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">volume types extra specs negative<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">volume types extra specs<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">volume types negative<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">volume types<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">volumes actions<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">volumes get<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">volumes list<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">volumes negative<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
<li class="list-group-item">volumes snapshots<span class="glyphicon glyphicon-remove-circle"></span></li>
|
||||
</ul>
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,45 +0,0 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}View Profile{% endblock %}
|
||||
{% block body %}
|
||||
|
||||
<div class="panel panel-default unit one-of-two">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-user"></span> {{ user.name }}'s profile
|
||||
|
||||
<a href="/profile/edit" style="float: right;">
|
||||
<span class="glyphicon glyphicon-pencil"></span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td>
|
||||
E-Mail:
|
||||
</td>
|
||||
<td >
|
||||
{{ user.email }}
|
||||
{% if user.email_verified %}
|
||||
<span class="glyphicon glyphicon-ok-circle green" ></span>
|
||||
{% else %}
|
||||
<span class="glyphicon glyphicon-remove-circle red"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Vendor:
|
||||
</td>
|
||||
<td >
|
||||
{{ user.vendor.vendor_name }}
|
||||
{% if user.authorized %}
|
||||
<span class="glyphicon glyphicon-ok-circle green" ></span>
|
||||
{% else %}
|
||||
<span class="glyphicon glyphicon-remove-circle red"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -13,15 +13,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
from refstack import app as base_app
|
||||
import unittest
|
||||
|
||||
|
||||
class TestSequenceFunctions(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.app = base_app.create_app().test_client()
|
||||
|
||||
def test_nothing(self):
|
||||
# make sure the shuffled sequence does not lose any elements
|
||||
pass
|
||||
|
@ -19,7 +19,6 @@
|
||||
|
||||
from datetime import datetime
|
||||
import os
|
||||
import pprint
|
||||
import random
|
||||
import string
|
||||
|
||||
@ -120,8 +119,3 @@ def make_dir(dir_path):
|
||||
|
||||
|
||||
### Begin Non-Fbone stuff
|
||||
|
||||
|
||||
def dump_config(app):
|
||||
"""Useful to dump app config for debug purposes."""
|
||||
return pprint.pformat(dict(app.config.items()))
|
||||
|
184
refstack/web.py
184
refstack/web.py
@ -1,184 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2013 Piston Cloud Computing, Inc. 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
|
||||
from flask import abort, flash, request, redirect, url_for, \
|
||||
render_template, g, session
|
||||
from flask_mail import Mail
|
||||
from refstack import app as base_app
|
||||
from refstack.extensions import db
|
||||
from refstack.extensions import oid
|
||||
from refstack.models import Cloud
|
||||
from refstack.models import Test
|
||||
from refstack.models import User
|
||||
from refstack.models import Vendor
|
||||
|
||||
app = base_app.create_app()
|
||||
mail = Mail(app)
|
||||
public_routes = ['/post-result', '/get-miniconf']
|
||||
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
"""Runs before the request itself."""
|
||||
if request.path not in public_routes:
|
||||
g.user = None
|
||||
if 'openid' in session:
|
||||
flask.g.user = User.query.\
|
||||
filter_by(openid=session['openid']).first()
|
||||
|
||||
|
||||
@app.route('/', methods=['POST', 'GET'])
|
||||
def index():
|
||||
"""Index view."""
|
||||
if g.user is not None:
|
||||
# something else
|
||||
clouds = Cloud.query.filter_by(user_id=g.user.id).all()
|
||||
return render_template('home.html', clouds=clouds)
|
||||
else:
|
||||
vendors = Vendor.query.all()
|
||||
return render_template('index.html', vendors=vendors)
|
||||
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
@oid.loginhandler
|
||||
def login():
|
||||
"""Does the login via OpenID.
|
||||
|
||||
Has to call into `oid.try_login` to start the OpenID machinery.
|
||||
"""
|
||||
# if we are already logged in, go back to were we came from
|
||||
if g.user is not None:
|
||||
return redirect(oid.get_next_url())
|
||||
return oid.try_login(
|
||||
"https://login.launchpad.net/",
|
||||
ask_for=['email', 'nickname'])
|
||||
|
||||
|
||||
@oid.after_login
|
||||
def create_or_login(resp):
|
||||
"""This is called when login with OpenID succeeded and it's not
|
||||
necessary to figure out if this is the users's first login or not.
|
||||
This function has to redirect otherwise the user will be presented
|
||||
with a terrible URL which we certainly don't want.
|
||||
"""
|
||||
session['openid'] = resp.identity_url
|
||||
user = User.query.filter_by(openid=resp.identity_url).first()
|
||||
if user is not None:
|
||||
flash(u'Successfully signed in')
|
||||
g.user = user
|
||||
return redirect(oid.get_next_url())
|
||||
return redirect(url_for('create_profile', next=oid.get_next_url(),
|
||||
name=resp.fullname or resp.nickname,
|
||||
email=resp.email))
|
||||
|
||||
|
||||
@app.route('/create-profile', methods=['GET', 'POST'])
|
||||
def create_profile():
|
||||
"""If this is the user's first login, the create_or_login function
|
||||
will redirect here so that the user can set up his profile.
|
||||
"""
|
||||
if g.user is not None or 'openid' not in session:
|
||||
return redirect(url_for('index'))
|
||||
if request.method == 'POST':
|
||||
name = request.form['name']
|
||||
email = request.form['email']
|
||||
if not name:
|
||||
flash(u'Error: you have to provide a name')
|
||||
elif '@' not in email:
|
||||
flash(u'Error: you have to enter a valid email address')
|
||||
else:
|
||||
flash(u'Profile successfully created')
|
||||
db.session.add(User(name, email, session['openid']))
|
||||
db.session.commit()
|
||||
return redirect(oid.get_next_url())
|
||||
return render_template(
|
||||
'create_profile.html', next_url=oid.get_next_url())
|
||||
|
||||
|
||||
@app.route('/profile/edit', methods=['GET', 'POST'])
|
||||
def edit_profile():
|
||||
"""Updates a profile."""
|
||||
if g.user is None:
|
||||
abort(401)
|
||||
form = dict(name=g.user.name, email=g.user.email)
|
||||
if request.method == 'POST':
|
||||
if 'delete' in request.form:
|
||||
db.session.delete(g.user)
|
||||
db.session.commit()
|
||||
session['openid'] = None
|
||||
flash(u'Profile deleted')
|
||||
return redirect(url_for('index'))
|
||||
form['name'] = request.form['name']
|
||||
form['email'] = request.form['email']
|
||||
if not form['name']:
|
||||
flash(u'Error: you have to provide a name')
|
||||
elif '@' not in form['email']:
|
||||
flash(u'Error: you have to enter a valid email address')
|
||||
else:
|
||||
flash(u'Profile successfully created')
|
||||
g.user.name = form['name']
|
||||
g.user.email = form['email']
|
||||
db.session.commit()
|
||||
return redirect(url_for('edit_profile'))
|
||||
return render_template('edit_profile.html', form=form)
|
||||
|
||||
|
||||
@app.route('/profile', methods=['GET', 'POST'])
|
||||
def view_profile():
|
||||
"""Updates a profile."""
|
||||
if g.user is None:
|
||||
abort(401)
|
||||
|
||||
return render_template('view_profile.html', user=g.user)
|
||||
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
"""Log out."""
|
||||
session.pop('openid', None)
|
||||
flash(u'You have been signed out')
|
||||
return redirect(oid.get_next_url())
|
||||
|
||||
|
||||
@app.route('/post-result', methods=['POST'])
|
||||
def post_result():
|
||||
"""Receive tempest test result from a remote test runner."""
|
||||
# todo: come up with some form of authentication
|
||||
# Im sure we can come with something more elegant than this
|
||||
# but it should work for now.
|
||||
#print request.files
|
||||
f = request.files['file']
|
||||
if not f:
|
||||
return 'only valid with file post', 400
|
||||
else:
|
||||
if request.args.get('test_id', ''):
|
||||
# this data is for a specific test triggered by the gui and we
|
||||
# want to relate it
|
||||
new_test = Test.query.\
|
||||
filter_by(id=request.args.get('test_id', '')).first()
|
||||
new_test.subunit = f.read()
|
||||
new_test.finished = True
|
||||
|
||||
else:
|
||||
# anonymous data .. we still want to capture it
|
||||
new_test = Test()
|
||||
new_test.subunit = f.read()
|
||||
new_test.finished = True
|
||||
db.session.add(new_test)
|
||||
|
||||
db.session.commit()
|
||||
return 'thank you', 201
|
@ -1,21 +1,6 @@
|
||||
Flask==0.10.1
|
||||
Flask-Admin==1.0.7
|
||||
Flask-Login==0.2.11
|
||||
Flask-Mail==0.9.0
|
||||
Flask-OpenID==1.2.1
|
||||
Flask-Principal==0.3.5
|
||||
Flask-SQLAlchemy==1.0
|
||||
Flask-Security==1.6.3
|
||||
Flask-WTF==0.8.3
|
||||
Flask-Restless==0.13.1
|
||||
SQLAlchemy==0.8.3
|
||||
WTForms==1.0.4
|
||||
Werkzeug==0.9.6
|
||||
alembic==0.5.0
|
||||
gunicorn==0.17.4
|
||||
pyOpenSSL==0.13
|
||||
pycrypto==2.6
|
||||
python-openid==2.2.5
|
||||
requests==1.2.3
|
||||
python-keystoneclient
|
||||
Jinja2==2.7.1
|
||||
|
Loading…
Reference in New Issue
Block a user