Installation struct API call

* jsonschema and model for InstallationStruct added
* post API call handled
* DB migration fixed for adding new tables
* .gitreview added
* licence text added into files
* test app and uwsgi conf for test app added
* import cycles fixed (reproduced on CI only)
* autocommit option removed from DB session

Blueprint: send-anon-usage
Change-Id: If83cb6f86fa96d609da4453ed7417cff996c2019
This commit is contained in:
Alexander Kislitsky 2014-10-08 20:30:39 +04:00
parent e5e4186bc3
commit 21c6679048
33 changed files with 734 additions and 33 deletions

4
.gitreview Normal file
View File

@ -0,0 +1,4 @@
[gerrit]
host=review.openstack.org
port=29418
project=stackforge/fuel-stats.git

View File

@ -0,0 +1,13 @@
# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

View File

@ -1 +1,13 @@
# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

View File

@ -1,3 +1,17 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 import Flask
from flask import jsonify
from flask import make_response
@ -14,13 +28,16 @@ app = Flask(__name__)
app.config['JSONSCHEMA_DIR'] = os.path.join(app.root_path, 'schemas')
flask_jsonschema.JsonSchema(app)
db = flask_sqlalchemy.SQLAlchemy(app, session_options={'autocommit': True})
db = flask_sqlalchemy.SQLAlchemy(app)
# Registering blueprints
from collector.api.resources.action_logs import bp as action_logs_bp
from collector.api.resources.installation_struct import \
bp as installation_struct_bp
from collector.api.resources.ping import bp as ping_bp
app.register_blueprint(installation_struct_bp)
app.register_blueprint(action_logs_bp)
app.register_blueprint(ping_bp)

View File

@ -0,0 +1,6 @@
from collector.api.app import app
from collector.api import log
app.config.from_object('collector.api.config.Testing')
log.init_logger()

View File

@ -1 +1,13 @@
# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

View File

@ -1,3 +1,17 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 collections import namedtuple

View File

@ -1,3 +1,17 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 datetime
from flask import current_app
from flask import jsonify
@ -65,7 +79,6 @@ def db_transaction(fn):
"""
@wraps(fn)
def decorated(*args, **kwargs):
db.session.begin()
try:
result = fn(*args, **kwargs)
db.session.commit()

View File

@ -1,3 +1,17 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 logging
import os

View File

@ -0,0 +1,13 @@
# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

View File

@ -1,3 +1,17 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool

View File

@ -1,3 +1,17 @@
# Copyright 2014 Mirantis, Inc.
#
# 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.
"""Collector DB schema
Revision ID: 558f628a238
@ -24,10 +38,21 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('node_aid', 'external_id')
)
op.create_table(
'installation_structs',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('aid', sa.String(), nullable=False),
sa.Column('struct', sa.Text(), nullable=False),
sa.Column('creation_date', sa.DateTime(), nullable=True),
sa.Column('modification_date', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('aid')
)
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_table('action_logs')
op.execute('DROP TABLE IF EXISTS installation_structs')
op.execute('DROP TABLE IF EXISTS action_logs')
### end Alembic commands ###

View File

@ -1,3 +1,17 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 collector.api.app import db
@ -10,3 +24,13 @@ class ActionLog(db.Model):
id = db.Column(db.Integer, primary_key=True)
node_aid = db.Column(db.String, nullable=False)
external_id = db.Column(db.Integer, nullable=False)
class InstallationStruct(db.Model):
__tablename__ = 'installation_structs'
id = db.Column(db.Integer, primary_key=True)
aid = db.Column(db.String, nullable=False, unique=True)
struct = db.Column(db.Text, nullable=False)
creation_date = db.Column(db.DateTime)
modification_date = db.Column(db.DateTime)

View File

@ -1,3 +1,17 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 logging import FileHandler
from logging import Formatter
from logging.handlers import RotatingFileHandler

View File

@ -0,0 +1,13 @@
# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

View File

@ -1,9 +1,26 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 import Blueprint
from flask import request
from flask_jsonschema import validate as validate_request
import six
from sqlalchemy import and_
from sqlalchemy import or_
bp = Blueprint('action_logs', __name__, url_prefix='/api/v1/action_logs')
from collector.api.app import app
from collector.api.app import db
from collector.api.common import consts
@ -14,9 +31,6 @@ from collector.api.common.util import handle_response
from collector.api.db.model import ActionLog
bp = Blueprint('action_logs', __name__, url_prefix='/api/v1/action_logs')
@bp.route('/', methods=['POST'])
@validate_request('action_logs', 'request')
@handle_response(201, 'action_logs', 'response')
@ -30,51 +44,57 @@ def post():
objects_info = []
for chunk in util.split_collection(action_logs, chunk_size=1000):
existed_objs, action_logs_to_add = _separate_action_logs(chunk)
_handle_existed_objects(objects_info, existed_objs)
_save_action_logs(objects_info, action_logs_to_add)
objects_info.extend(_extract_objects_info(existed_objs))
objects_info.extend(_save_action_logs(action_logs_to_add))
return {'status': 'ok', 'action_logs': list(objects_info)}
@db_transaction
def _save_action_logs(objects_info, action_logs):
def _save_action_logs(action_logs):
result = []
if not action_logs:
return
return result
try:
db.session.execute(ActionLog.__table__.insert(), action_logs)
for action_log in action_logs:
objects_info.append({
result.append({
'node_aid': action_log['node_aid'],
'external_id': action_log['external_id'],
'status': consts.ACTION_LOG_STATUSES.added
})
except Exception:
app.logger.exception("Processing of action logs chunk failed")
_handle_chunk_processing_error(objects_info, action_logs)
result = _handle_chunk_processing_error(action_logs)
return result
def _handle_existed_objects(objects_info, existed_objects):
def _extract_objects_info(existed_objects):
result = []
for obj in existed_objects:
objects_info.append({
result.append({
'node_aid': obj.node_aid,
'external_id': obj.external_id,
'status': consts.ACTION_LOG_STATUSES.existed
})
return result
def _handle_chunk_processing_error(objects_info, chunk):
def _handle_chunk_processing_error(chunk):
result = []
for action_log in chunk:
objects_info.append({
result.append({
'node_aid': action_log['node_aid'],
'external_id': action_log['external_id'],
'status': consts.ACTION_LOG_STATUSES.failed
})
return result
def _separate_action_logs(action_logs):
existed_objs = []
action_logs_idx = util.build_index(action_logs, 'node_aid', 'external_id')
clauses = []
for aid, ext_id in action_logs_idx.keys():
for aid, ext_id in six.iterkeys(action_logs_idx):
clauses.append(and_(
ActionLog.node_aid == aid,
ActionLog.external_id == ext_id
@ -85,4 +105,4 @@ def _separate_action_logs(action_logs):
existed_objs.append(existed)
idx = (existed.node_aid, existed.external_id)
action_logs_idx.pop(idx)
return existed_objs, action_logs_idx.values()
return existed_objs, list(six.itervalues(action_logs_idx))

View File

@ -0,0 +1,54 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 datetime import datetime
from flask import Blueprint
from flask import json
from flask import request
from flask_jsonschema import validate as validate_request
bp = Blueprint('installation_struct', __name__,
url_prefix='/api/v1/installation_struct')
from collector.api.app import app
from collector.api.app import db
from collector.api.common.util import db_transaction
from collector.api.common.util import exec_time
from collector.api.common.util import handle_response
from collector.api.db.model import InstallationStruct
@bp.route('/', methods=['POST'])
@validate_request('installation_struct', 'request')
@handle_response(201, 'installation_struct', 'response')
@db_transaction
@exec_time
def post():
app.logger.debug(
"Handling installation_struct post request: {}".format(request.json)
)
struct = request.json['installation_struct']
aid = struct['aid']
obj = db.session.query(InstallationStruct).filter(
InstallationStruct.aid == aid).first()
if obj is None:
app.logger.debug("Saving new struct")
obj = InstallationStruct(aid=aid)
obj.creation_date = datetime.utcnow()
else:
app.logger.debug("Updating struct {}".format(obj.id))
obj.modification_date = datetime.utcnow()
obj.struct = json.dumps(struct)
db.session.add(obj)
return {'status': 'ok'}

View File

@ -1,15 +1,28 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 import Blueprint
from flask import request
from flask_jsonschema import validate as validate_request
bp = Blueprint('ping', __name__, url_prefix='/api/v1/ping')
from collector.api.app import app
from collector.api.common.util import exec_time
from collector.api.common.util import handle_response
bp = Blueprint('ping', __name__, url_prefix='/api/v1/ping')
@bp.route('/', methods=['GET'])
@validate_request('ping', 'request')
@handle_response(200, 'ping', 'response')

View File

@ -0,0 +1,102 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"request": {
"type": "object",
"properties": {
"installation_struct": {
"type": "object",
"properties": {
"aid": {"type": "string"},
"clusters_num": {"type": "integer"},
"allocated_nodes_num": {"type": "integer"},
"unallocated_nodes_num": {"type": "integer"},
"fuel_release": {
"type": "object",
"properties": {
"release": {"type": "string"},
"ostf_sha": {"type": "string"},
"astute_sha": {"type": "string"},
"nailgun_sha": {"type": "string"},
"fuellib_sha": {"type": "string"},
"feature_groups": {
"type": "array",
"items": {"type": "string"}
},
"api": {"type": "string"}
},
"required": ["release", "ostf_sha", "astute_sha",
"nailgun_sha", "fuellib_sha", "api",
"feature_groups"]
},
"clusters": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "integer"},
"mode": {"type": "string"},
"release": {
"type": "object",
"properties": {
"version": {"type": "string"},
"os": {"type": "string"},
"name": {"type": "string"}
},
"required": ["version", "os", "name"]
},
"nodes_num": {"type": "integer"},
"nodes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "integer"},
"roles": {
"type": "array",
"items": {"type": "string"}
},
"status": {"type": "string"}
},
"required": ["id", "roles"]
}
}
},
"required": ["id", "nodes_num", "nodes", "mode", "release"]
}
}
},
"required": ["aid", "clusters_num", "allocated_nodes_num",
"unallocated_nodes_num", "clusters"]
}
},
"additionalProperties": false,
"required": ["installation_struct"]
},
"response": {
"oneOf": [
{
"type": "object",
"properties": {
"status": {"enum": ["ok"]},
"exec_time": {"type": "number"}
},
"required": ["status"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"status": {"enum": ["error"]},
"exec_time": {"type": "number"},
"message": {"type": "string"}
},
"required": ["status", "message"],
"additionalProperties": false
}
]
}
}

View File

@ -0,0 +1,13 @@
# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

View File

@ -1,3 +1,18 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 alembic.util import CommandError
from flask import json
import flask_migrate
import os
@ -55,9 +70,16 @@ class DbTest(BaseTest):
def setUp(self):
super(DbTest, self).setUp()
# Connection must be closed before DB migration
db.session.close()
# Cleaning DB. It useful in case of tests failure
directory = os.path.join(os.path.dirname(__file__),
'..', 'api', 'db', 'migrations')
with app.app_context():
flask_migrate.downgrade(directory=directory)
try:
flask_migrate.downgrade(directory=directory)
except CommandError:
# Workaround for the first migration
pass
flask_migrate.upgrade(directory=directory)

View File

@ -1 +1,13 @@
# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

View File

@ -1,3 +1,17 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 collector.test.base import BaseTest
from collector.api.common.util import build_index

View File

@ -1 +1,13 @@
# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

View File

@ -1,3 +1,17 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 collector.test.base import DbTest
from sqlalchemy.exc import IntegrityError
@ -16,6 +30,8 @@ class TestModelActionLog(DbTest):
def test_non_nullable_fields(self):
db.session.add(ActionLog())
self.assertRaises(IntegrityError, db.session.flush)
db.session.rollback()
db.session.add(ActionLog(node_aid='aid'))
self.assertRaises(IntegrityError, db.session.flush)
db.session.rollback()

View File

@ -1 +1,13 @@
# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

View File

@ -1,3 +1,17 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 import json
from collector.test.base import DbTest

View File

@ -1,3 +1,17 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 collector.test.base import BaseTest

View File

@ -0,0 +1,136 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 import json
from collector.test.base import DbTest
from collector.api.app import db
from collector.api.db.model import InstallationStruct
class TestInstallationStruct(DbTest):
def test_not_allowed_methods(self):
resp = self.get('/api/v1/installation_struct/', None)
self.check_response_error(resp, 405)
resp = self.delete('/api/v1/installation_struct/')
self.check_response_error(resp, 405)
resp = self.patch('/api/v1/installation_struct/', None)
self.check_response_error(resp, 405)
resp = self.put('/api/v1/installation_struct/', None)
self.check_response_error(resp, 405)
def test_validation_error(self):
wrong_data_sets = [
{'installation_struct': {'aid': 'x'}},
None,
{}
]
for data in wrong_data_sets:
resp = self.post(
'/api/v1/installation_struct/',
data
)
self.check_response_error(resp, code=400)
def test_post(self):
aid = 'x'
struct = {
'aid': aid,
'fuel_release': {
'release': 'r',
'ostf_sha': 'o_sha',
'astute_sha': 'a_sha',
'nailgun_sha': 'n_sha',
'fuellib_sha': 'fl_sha',
'feature_groups': ['experimental'],
'api': 'v1'
},
'allocated_nodes_num': 4,
'unallocated_nodes_num': 4,
'clusters_num': 2,
'clusters': [
{
'id': 29,
'mode': 'ha_full',
'release': {
'version': '2014.2-6.0',
'name': 'Juno on CentOS 6.5',
'os': 'CentOS'
},
'nodes_num': 3,
'nodes': [
{'id': 33, 'roles': ['a', 'b', 'c'], 'status': 's'},
{'id': 34, 'roles': ['b', 'c'], 'status': 's'},
{'id': 35, 'roles': ['c'], 'status': 's'}
]
},
{
'id': 32,
'mode': 'ha_compact',
'release': {
'version': '2014.2-6.0',
'name': 'Juno on CentOS 6.5',
'os': 'CentOS'
},
'nodes_num': 1,
'nodes': [
{'id': 42, 'roles': ['s'], 'status': 's'}
]
},
]
}
resp = self.post(
'/api/v1/installation_struct/',
{'installation_struct': struct}
)
self.check_response_ok(resp, code=201)
obj = db.session.query(InstallationStruct).filter(
InstallationStruct.aid == aid).one()
self.assertEquals(json.dumps(struct), obj.struct)
self.assertIsNotNone(obj.creation_date)
self.assertIsNone(obj.modification_date)
def test_post_update(self):
aid = 'xx'
struct = {
'aid': aid,
'allocated_nodes_num': 0,
'unallocated_nodes_num': 0,
'clusters_num': 0,
'clusters': []
}
resp = self.post(
'/api/v1/installation_struct/',
{'installation_struct': struct}
)
self.check_response_ok(resp, code=201)
obj_new = db.session.query(InstallationStruct).filter(
InstallationStruct.aid == aid).one()
self.assertEquals(json.dumps(struct), obj_new.struct)
self.assertIsNotNone(obj_new.creation_date)
self.assertIsNone(obj_new.modification_date)
struct['unallocated_nodes_num'] = 5
resp = self.post(
'/api/v1/installation_struct/',
{'installation_struct': struct}
)
self.check_response_ok(resp, code=201)
obj_upd = db.session.query(InstallationStruct).filter(
InstallationStruct.aid == aid).one()
self.assertEquals(json.dumps(struct), obj_upd.struct)
self.assertIsNotNone(obj_upd.creation_date)
self.assertIsNotNone(obj_upd.modification_date)

View File

@ -1,3 +1,17 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 collector.test.base import BaseTest

View File

@ -1,5 +1,19 @@
#!/usr/bin/env python
# Copyright 2014 Mirantis, Inc.
#
# 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_migrate import Migrate
from flask_migrate import MigrateCommand
from flask_script import Manager

View File

@ -1,3 +1,17 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 setuptools import find_packages
@ -37,9 +51,5 @@ setup(
zip_safe=False,
install_requires=parse_requirements_txt(),
include_package_data=True,
scripts=['manage_collector.py'],
# entry_points={
# 'console_scripts': [
# 'upgrade_db = collector.cli:main']
# }
scripts=['manage_collector.py']
)

View File

@ -1,5 +1,6 @@
uwsgi:
socket: :8081
module: collector.api.app
# for production app use collector.api.app as module
module: collector.api.app_test
callable: app
protocol: http