Implement organization and product tables

added: migrations, models, simple db api, tests for db api

Addresses-Spec: https://review.openstack.org/#/c/268922/
Change-Id: Ie02c2f160db195fb0e793b6bd9284f2bb006ecbd
This commit is contained in:
Andrey Pavlov 2016-01-25 16:37:52 +03:00
parent efe42b6589
commit 759f687fe2
7 changed files with 451 additions and 0 deletions

View File

@ -47,3 +47,23 @@ SHARED_TEST_RUN = 'shared'
# Roles
ROLE_USER = 'user'
ROLE_OWNER = 'owner'
# Organization types.
# OpenStack Foundation
FOUNDATION = 0
# User's private unofficial Vendor (allows creation and testing
# of user's products)
PRIVATE_VENDOR = 1
# Vendor applied and waiting for official status.
PENDING_VENDOR = 2
# Official Vendor approved by the Foundation.
OFFICIAL_VENDOR = 3
# Product object types.
CLOUD = 0
SOFTWARE = 1
# Product specific types.
DISTRO = 0
PUBLIC_CLOUD = 1
HOSTED_PRIVATE_CLOUD = 2

View File

@ -170,3 +170,43 @@ def add_user_to_group(user_openid, group_id, created_by_user):
def remove_user_from_group(user_openid, group_id):
"""Remove specified user from specified group."""
return IMPL.remove_user_from_group(user_openid, group_id)
def add_organization(organization_info, creator):
"""Add organization."""
return IMPL.add_organization(organization_info, creator)
def update_organization(organization_info):
"""Update organization."""
return IMPL.update_organization(organization_info)
def get_organization(organization_id):
"""Get organization by id."""
return IMPL.get_organization(organization_id)
def delete_organization(organization_id):
"""delete organization by id."""
return IMPL.delete_organization(organization_id)
def add_product(product_info, creator):
"""Add product from product_info dicionary with creator."""
return IMPL.add_product(product_info, creator)
def update_product(product_info):
"""Update product from prodict_info dicionary."""
return IMPL.update_product(product_info)
def get_product(id):
"""Get product by id."""
return IMPL.get_product(id)
def delete_product(id):
"""delete product by id."""
return IMPL.delete_product(id)

View File

@ -0,0 +1,42 @@
"""Create organization table.
Revision ID: 19fded785b8c
Revises: 319ee8fe47c7
Create Date: 2016-01-18 14:40:00
"""
# revision identifiers, used by Alembic.
revision = '19fded785b8c'
down_revision = '319ee8fe47c7'
MYSQL_CHARSET = 'utf8'
from alembic import op
import sqlalchemy as sa
def upgrade():
"""Upgrade DB."""
op.create_table(
'organization',
sa.Column('updated_at', sa.DateTime()),
sa.Column('deleted_at', sa.DateTime()),
sa.Column('deleted', sa.Integer, default=0),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('id', sa.String(36), nullable=False),
sa.Column('type', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=80), nullable=False),
sa.Column('description', sa.Text()),
sa.Column('group_id', sa.String(36), nullable=False),
sa.Column('created_by_user', sa.String(128), nullable=False),
sa.Column('properties', sa.Text()),
sa.PrimaryKeyConstraint('id'),
sa.ForeignKeyConstraint(['group_id'], ['group.id'], ),
sa.ForeignKeyConstraint(['created_by_user'], ['user.openid'], ),
mysql_charset=MYSQL_CHARSET
)
def downgrade():
"""Downgrade DB."""
op.drop_table('organization')

View File

@ -0,0 +1,45 @@
"""Create product table.
Revision ID: 7092392cbb8e
Revises: 19fded785b8c
Create Date: 2016-01-18 16:10:00
"""
# revision identifiers, used by Alembic.
revision = '7092392cbb8e'
down_revision = '19fded785b8c'
MYSQL_CHARSET = 'utf8'
from alembic import op
import sqlalchemy as sa
def upgrade():
"""Upgrade DB."""
op.create_table(
'product',
sa.Column('updated_at', sa.DateTime()),
sa.Column('deleted_at', sa.DateTime()),
sa.Column('deleted', sa.Integer, default=0),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('created_by_user', sa.String(128), nullable=False),
sa.Column('id', sa.String(36), nullable=False),
sa.Column('name', sa.String(length=80), nullable=False),
sa.Column('description', sa.Text()),
sa.Column('product_id', sa.String(36), nullable=False),
sa.Column('type', sa.Integer(), nullable=False),
sa.Column('product_type', sa.Integer(), nullable=False),
sa.Column('public', sa.Boolean(), nullable=False),
sa.Column('organization_id', sa.String(36), nullable=False),
sa.Column('properties', sa.Text()),
sa.PrimaryKeyConstraint('id'),
sa.ForeignKeyConstraint(['organization_id'], ['organization.id'], ),
sa.ForeignKeyConstraint(['created_by_user'], ['user.openid'], ),
mysql_charset=MYSQL_CHARSET
)
def downgrade():
"""Downgrade DB."""
op.drop_table('product')

View File

@ -356,3 +356,130 @@ def remove_user_from_group(user_openid, group_id):
filter_by(user_openid=user_openid).
filter_by(group_id=group_id).
delete(synchronize_session=False))
def add_organization(organization_info, creator):
"""Add organization."""
session = get_session()
with session.begin():
group = models.Group()
group.name = 'Group for %s' % organization_info['name']
group.save(session=session)
group_id = group.id
item = models.UserToGroup()
item.user_openid = creator
item.group_id = group_id
item.created_by_user = creator
item.save(session=session)
organization = models.Organization()
organization.type = organization_info.get(
'type', api_const.PRIVATE_VENDOR)
organization.name = organization_info['name']
organization.description = organization_info.get('description')
organization.group_id = group_id
organization.created_by_user = creator
organization.properties = organization_info.get('properties')
organization.save(session=session)
return _to_dict(organization)
def update_organization(organization_info):
"""Update organization."""
session = get_session()
_id = organization_info['id']
organization = (session.query(models.Organization).
filter_by(id=_id).first())
if organization is None:
raise NotFound('Organization with id %s not found' % _id)
with session.begin():
organization.type = organization_info.get(
'type', organization.type)
organization.name = organization_info.get(
'name', organization.name)
organization.description = organization_info.get(
'description', organization.description)
organization.properties = organization_info.get(
'properties', organization.properties)
organization.save(session=session)
def get_organization(organization_id):
"""Get organization by id."""
session = get_session()
organization = (session.query(models.Organization).
filter_by(id=organization_id).first())
if organization is None:
raise NotFound('Organization with id %s not found' % organization_id)
return _to_dict(organization)
def delete_organization(organization_id):
"""delete organization by id."""
session = get_session()
with session.begin():
(session.query(models.Product).
filter_by(organization_id=organization_id).
delete(synchronize_session=False))
(session.query(models.Organization).
filter_by(id=organization_id).
delete(synchronize_session=False))
def add_product(product_info, creator):
"""Add product."""
product = models.Product()
_id = six.text_type(uuid.uuid4())
product.id = _id
product.type = product_info['type']
product.product_type = product_info['product_type']
product.product_id = product_info['product_id']
product.name = product_info['name']
product.description = product_info.get('description')
product.organization_id = product_info['organization_id']
product.created_by_user = creator
product.public = product_info.get('public', False)
product.properties = product_info.get('properties')
session = get_session()
with session.begin():
product.save(session=session)
return _to_dict(product)
def update_product(product_info):
"""Update product by product_id."""
session = get_session()
_id = product_info.get('product_id')
product = session.query(models.Product).filter_by(product_id=_id).first()
if product is None:
raise NotFound('Product with product_id %s not found' % _id)
product.name = product_info.get('name', product.name)
product.description = product_info.get('description', product.description)
product.public = product_info.get('public', product.public)
product.properties = product_info.get('properties', product.properties)
with session.begin():
product.save(session=session)
return _to_dict(product)
def get_product(id):
"""Get product by id."""
session = get_session()
product = session.query(models.Product).filter_by(id=id).first()
if product is None:
raise NotFound('Product with id "%s" not found' % id)
return _to_dict(product)
def delete_product(id):
"""delete product by id."""
session = get_session()
with session.begin():
(session.query(models.Product).filter_by(id=id).
delete(synchronize_session=False))

View File

@ -193,3 +193,52 @@ class UserToGroup(BASE, RefStackBase): # pragma: no cover
def default_allowed_keys(self):
"""Default keys."""
return 'user_openid', 'group_id'
class Organization(BASE, RefStackBase): # pragma: no cover
"""Organization definition."""
__tablename__ = 'organization'
id = sa.Column(sa.String(36), primary_key=True,
default=lambda: six.text_type(uuid.uuid4()))
type = sa.Column(sa.Integer, nullable=False)
name = sa.Column(sa.String(80), nullable=False)
description = sa.Column(sa.Text())
group_id = sa.Column(sa.String(36), sa.ForeignKey('group.id'),
nullable=False)
created_by_user = sa.Column(sa.String(128), sa.ForeignKey('user.openid'),
nullable=False)
properties = sa.Column(sa.Text())
@property
def default_allowed_keys(self):
"""Default keys."""
return ('id', 'type', 'name', 'description', 'group_id',
'created_by_user', 'properties')
class Product(BASE, RefStackBase): # pragma: no cover
"""Product definition."""
__tablename__ = 'product'
id = sa.Column(sa.Integer(), primary_key=True)
product_id = sa.Column(sa.String(36), nullable=False)
name = sa.Column(sa.String(80), nullable=False)
description = sa.Column(sa.Text())
organization_id = sa.Column(sa.String(36),
sa.ForeignKey('organization.id'),
nullable=False)
created_by_user = sa.Column(sa.String(128), sa.ForeignKey('user.openid'),
nullable=False)
public = sa.Column(sa.Boolean(), nullable=False)
properties = sa.Column(sa.Text())
type = sa.Column(sa.Integer(), nullable=False)
product_type = sa.Column(sa.Integer(), nullable=False)
@property
def default_allowed_keys(self):
"""Default keys."""
return ('id', 'product_id', 'name', 'description', 'organization_id',
'created_by_user', 'properties', 'type', 'product_type')

View File

@ -638,3 +638,131 @@ class DBBackendTestCase(base.BaseTestCase):
mock.call().filter_by(group_id='GUID'),
mock.call().filter_by().delete(synchronize_session=False)))
session.begin.assert_called_once_with()
@mock.patch.object(api, 'get_session')
@mock.patch('refstack.db.sqlalchemy.models.Organization')
@mock.patch('refstack.db.sqlalchemy.models.Group')
@mock.patch('refstack.db.sqlalchemy.models.UserToGroup')
@mock.patch.object(api, '_to_dict', side_effect=lambda x: x)
def test_organization_add(self, mock_to_dict, mock_model_user_to_group,
mock_model_group, mock_model_organization,
mock_get_session):
organization_info = {'name': 'a', 'description': 'b', 'type': 1}
session = mock_get_session.return_value
organization = mock_model_organization.return_value
result = api.add_organization(organization_info, 'user-123')
self.assertEqual(result, organization)
group = mock_model_group.return_value
self.assertIsNotNone(group.id)
self.assertIsNotNone(organization.id)
self.assertIsNotNone(organization.group_id)
mock_model_organization.assert_called_once_with()
mock_model_group.assert_called_once_with()
mock_model_user_to_group.assert_called_once_with()
mock_get_session.assert_called_once_with()
organization.save.assert_called_once_with(session=session)
group.save.assert_called_once_with(session=session)
user_to_group = mock_model_user_to_group.return_value
user_to_group.save.assert_called_once_with(session=session)
session.begin.assert_called_once_with()
@mock.patch.object(api, 'get_session')
@mock.patch('refstack.db.sqlalchemy.models.Product')
@mock.patch.object(api, '_to_dict', side_effect=lambda x: x)
def test_product_add(self, mock_to_dict, mock_product, mock_get_session):
session = mock_get_session.return_value
product = mock_product.return_value
product_info = {'product_id': 'hash_or_guid', 'name': 'a',
'organization_id': 'GUID0', 'type': 0,
'product_type': 0}
result = api.add_product(product_info, 'user-123')
self.assertEqual(result, product)
self.assertIsNotNone(product.id)
mock_get_session.assert_called_once_with()
product.save.assert_called_once_with(session=session)
session.begin.assert_called_once_with()
@mock.patch.object(api, 'get_session')
@mock.patch('refstack.db.sqlalchemy.models.Product')
def test_incomplete_product_add(self, mock_product, mock_get_session):
product_info = {}
self.assertRaises(KeyError, api.add_product, product_info, 'u')
@mock.patch.object(api, 'get_session')
@mock.patch('refstack.db.sqlalchemy.models.Product.save')
def test_product_update(self, mock_product_save, mock_get_session):
session = mock_get_session.return_value
query = session.query.return_value
filtered = query.filter_by.return_value
product = models.Product()
product.product_id = '123'
filtered.first.return_value = product
product_info = {'product_id': '098', 'name': 'a', 'description': 'b',
'creator_openid': 'abc', 'organization_id': '1',
'type': 0, 'product_type': 0}
api.update_product(product_info)
self.assertEqual('123', product.product_id)
self.assertIsNone(product.created_by_user)
self.assertIsNone(product.organization_id)
self.assertIsNone(product.type)
self.assertIsNone(product.product_type)
mock_get_session.assert_called_once_with()
mock_product_save.assert_called_once_with(session=session)
session.begin.assert_called_once_with()
@mock.patch.object(api, 'get_session',
return_value=mock.Mock(name='session'),)
@mock.patch('refstack.db.sqlalchemy.models.Organization')
@mock.patch.object(api, '_to_dict', side_effect=lambda x: x)
def test_organization_get(self, mock_to_dict, mock_model,
mock_get_session):
organization_id = 12345
session = mock_get_session.return_value
query = session.query.return_value
filtered = query.filter_by.return_value
organization = filtered.first.return_value
result = api.get_organization(organization_id)
self.assertEqual(result, organization)
session.query.assert_called_once_with(mock_model)
query.filter_by.assert_called_once_with(id=organization_id)
filtered.first.assert_called_once_with()
@mock.patch.object(api, 'get_session',
return_value=mock.Mock(name='session'),)
@mock.patch('refstack.db.sqlalchemy.models.Product')
@mock.patch.object(api, '_to_dict', side_effect=lambda x: x)
def test_product_get(self, mock_to_dict, mock_model, mock_get_session):
_id = 12345
session = mock_get_session.return_value
query = session.query.return_value
filtered = query.filter_by.return_value
product = filtered.first.return_value
result = api.get_product(_id)
self.assertEqual(result, product)
session.query.assert_called_once_with(mock_model)
query.filter_by.assert_called_once_with(id=_id)
filtered.first.assert_called_once_with()
@mock.patch.object(api, 'get_session')
@mock.patch('refstack.db.sqlalchemy.api.models')
def test_product_delete(self, mock_models, mock_get_session):
session = mock_get_session.return_value
db.delete_product('product_id')
session.query.assert_called_once_with(mock_models.Product)
session.query.return_value.filter_by.assert_has_calls((
mock.call(id='product_id'),
mock.call().delete(synchronize_session=False)))
session.begin.assert_called_once_with()