Skip to content

Commit

Permalink
Merge pull request #23 from cloudblue/feature/LITE-27236
Browse files Browse the repository at this point in the history
LITE-27236 LITE-27243 DB Owner and DB Number Limit
  • Loading branch information
AntonHinz authored Apr 18, 2023
2 parents 744fe72 + 7169ad6 commit a8a6667
Show file tree
Hide file tree
Showing 21 changed files with 130 additions and 33 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,8 @@
* Added workload description and external link to docs
* 0.3.2: Backend dependencies are bumped
* 0.3.3: Removed limit of returned DB objects in DB List API
* 0.4.0: Enhancements
* DB Owner is shown in Databases API and Administrative UI
* Max number of allowed DB per account can now be set via `DB_MAX_ALLOWED_NUMBER_PER_ACCOUNT` env variable
* Frontend changes:
* 400 and 422 server errors are better handled
6 changes: 3 additions & 3 deletions dbaas/extension.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "DBaaS",
"description": "On-demand provisioning of cloud-based database storages as a service.",
"version": "0.3.3",
"version": "0.4.0",
"audience": ["reseller", "distributor", "vendor"],
"readme_url": "https://github.com/cloudblue/connect-extension-dbaas/blob/0.3.3/README.md",
"changelog_url": "https://github.com/cloudblue/connect-extension-dbaas/blob/0.3.3/CHANGELOG.md",
"readme_url": "https://github.com/cloudblue/connect-extension-dbaas/blob/0.4.0/README.md",
"changelog_url": "https://github.com/cloudblue/connect-extension-dbaas/blob/0.4.0/CHANGELOG.md",
"icon": "googleExtensionBaseline"
}
1 change: 1 addition & 0 deletions dbaas/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class DatabaseOutList(DatabaseInCreate):
region: RefOut
tech_contact: RefOut
status: str
owner: RefIn
case: Optional[RefIn]
events: Optional[dict]

Expand Down
24 changes: 23 additions & 1 deletion dbaas/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ async def create(
if tech_contact['id'] != context.user_id:
actor = await cls._get_actor(context, client)

await cls._validate_allowed_db_number_per_account(db, context, config)

prepared_db_doc = cls._prepare_db_document(data, context, region_doc, tech_contact, actor)
inserted_db_doc = await cls._create_db_document(
prepared_db_doc, db, context, client, config,
Expand Down Expand Up @@ -304,6 +306,7 @@ def _default_query(cls, context: Context) -> dict:
@classmethod
def _db_document_repr(cls, db_document: dict, config: dict = None) -> dict:
document = copy(db_document)
document['owner'] = {'id': document.get('account_id')}

case = cls._get_last_db_document_case(document)
if case:
Expand Down Expand Up @@ -354,6 +357,25 @@ async def _get_validated_tech_contact(

return tech_contact

@classmethod
async def _validate_allowed_db_number_per_account(
cls,
db: AsyncIOMotorDatabase,
context: Context,
config: dict,
):
if is_admin_context(context):
return

max_allowed_number_of_db = int(config.get('DB_MAX_ALLOWED_NUMBER_PER_ACCOUNT', 50))

db_coll = db[cls.COLLECTION]
current_number_of_db = await db_coll.count_documents(cls._default_query(context))
if current_number_of_db + 1 > max_allowed_number_of_db:
raise ValueError(
f'Max allowed number of databases is reached: {max_allowed_number_of_db}.',
)

@classmethod
async def _get_actor(cls, context: Context, client: AsyncConnectClient) -> dict:
actor = await ConnectAccountUser.retrieve(context.account_id, context.user_id, client)
Expand Down Expand Up @@ -460,7 +482,7 @@ def _db_collection_from_db_session(cls, db_session, config):

@staticmethod
def _generate_id(config: dict) -> str:
id_random_length = config.get('DB_ID_RANDOM_LENGTH', 5)
id_random_length = int(config.get('DB_ID_RANDOM_LENGTH', 5))
id_prefix = config.get('DB_ID_PREFIX', 'DBPG')

random_part = ''.join(random.choice(string.digits) for _ in range(id_random_length))
Expand Down
2 changes: 2 additions & 0 deletions dbaas/static/7cace99224d3d55c5b00.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions dbaas/static/e362c49b3047272f792b.js

This file was deleted.

2 changes: 1 addition & 1 deletion dbaas/static/index.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<html><head><title>Lorem ipsum</title><link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Roboto+Mono:400,500|Material+Icons" rel="stylesheet"><link id="mock-favicon" rel="shortcut icon" href="#"><script defer="defer" src="e362c49b3047272f792b.js"></script><link href="main.css" rel="stylesheet"></head><body><div id="app"></div></body></html>
<html><head><title>Lorem ipsum</title><link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Roboto+Mono:400,500|Material+Icons" rel="stylesheet"><link id="mock-favicon" rel="shortcut icon" href="#"><script defer="defer" src="7cace99224d3d55c5b00.js"></script><link href="main.css" rel="stylesheet"></head><body><div id="app"></div></body></html>
10 changes: 5 additions & 5 deletions dbaas/static/main.css

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cloudblueconnect/eaas-database-extension",
"version": "0.3.2",
"version": "0.4.0",
"description": "On-demand provisioning of cloud-based database storages as a service.",
"author": "Ingram Micro",
"license": "Apache Software License 2.0",
Expand Down
4 changes: 4 additions & 0 deletions tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ class DBFactory(factory.DictFactory):

account_id = factory.Sequence(lambda n: f'VA-{n:05}')

@factory.post_generation
def owner(obj, *a, **kw):
obj['owner'] = {'id': obj['account_id']}


class InstallationFactory(factory.DictFactory):
id = factory.Sequence(lambda n: f'EIN-{n:05}')
Expand Down
52 changes: 48 additions & 4 deletions tests/services/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,28 @@ async def test_list_no_account_dbs(db, admin_context):
),
))
def test__db_document_repr(in_doc, out_doc):
assert DB._db_document_repr(in_doc) == out_doc
result = DB._db_document_repr(in_doc)
del result['owner']

assert result == out_doc


@pytest.mark.parametrize('in_doc, out_doc', (
({}, {}),
({1: True, 'a': 'key'}, {1: True, 'a': 'key'}),
({}, {'owner': {'id': None}}),
({1: True, 'a': 'key'}, {1: True, 'a': 'key', 'owner': {'id': None}}),
(
{
'status': DBStatus.ACTIVE,
'credentials': b'gAAAAABkEdPYFZffrdrEU5_jwzsBO-GstLDA2IYs8uAN7jGyQ4KRKw_'
b'CoxytmSLMdTi_NQ49Oe15RWgVtkbEFM2PAZ4wQI9sLQ==',
'account_id': 'VA-123',
},
{
'status': DBStatus.ACTIVE,
'credentials': {'1': 1},
'account_id': 'VA-123',
'owner': {'id': 'VA-123'},
},
{'status': DBStatus.ACTIVE, 'credentials': {'1': 1}},
),
))
def test__db_document_repr_with_config(in_doc, out_doc, config):
Expand Down Expand Up @@ -259,6 +268,36 @@ async def test__get_actor_ok(async_client_mocker_factory, mocker):
p.assert_called_once_with('PA-123', user['id'], 'client')


@pytest.mark.asyncio
async def test__validate_allowed_db_number_per_account_is_admin(admin_context):
assert await DB._validate_allowed_db_number_per_account('db', admin_context, 'config') is None


@pytest.mark.asyncio
@pytest.mark.parametrize('max_num, error', (
(0, 'Max allowed number of databases is reached: 0.'),
(1, 'Max allowed number of databases is reached: 1.'),
))
async def test__validate_allowed_db_number_per_account_exceeds_max(
db, common_context, max_num, error,
):
account_id = 'VA-234'
common_context.account_id = account_id
await db[Collections.DB].insert_one({'id': 'DB-200', 'account_id': account_id})

config = {'DB_MAX_ALLOWED_NUMBER_PER_ACCOUNT': max_num}

with pytest.raises(ValueError) as e:
await DB._validate_allowed_db_number_per_account(db, common_context, config)

assert str(e.value) == error


@pytest.mark.asyncio
async def test__validate_allowed_db_number_per_account_ok(db, common_context, config):
assert await DB._validate_allowed_db_number_per_account(db, common_context, config) is None


def test__prepare_db_document(mocker):
data = {'name': 'DB-1'}
context = Context(account_id='VA-123')
Expand Down Expand Up @@ -520,6 +559,10 @@ async def test_create_ok(mocker, context_uid, am_call_count, actor):
'dbaas.services.DB._get_actor',
AsyncMock(return_value=actor),
)
vn_p = mocker.patch(
'dbaas.services.DB._validate_allowed_db_number_per_account',
AsyncMock(),
)
pdd_p = mocker.patch('dbaas.services.DB._prepare_db_document', return_value='prepared_db_doc')
cdd_p = mocker.patch(
'dbaas.services.DB._create_db_document',
Expand All @@ -539,6 +582,7 @@ async def test_create_ok(mocker, context_uid, am_call_count, actor):

gvrd_p.assert_called_once_with(data, 'db')
gvtc_p.assert_called_once_with(data, context, 'client')
vn_p.assert_called_once_with('db', context, 'config')
assert ga_p.call_count == am_call_count
pdd_p.assert_called_once_with(
data, context, 'region_doc', {'id': 'UR-123-456'}, actor,
Expand Down
4 changes: 4 additions & 0 deletions tests/test_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ def test_database_out_list():
tech_contact__name='user',
cases=CaseFactory.create_batch(2),
events={'happened': {'at': 1}},
account_id='VA-123',
)

assert jsonable_encoder(DatabaseOutList(**db)) == {
Expand All @@ -229,6 +230,7 @@ def test_database_out_list():
'status': 'reviewing',
'case': None,
'events': {'happened': {'at': 1}},
'owner': {'id': 'VA-123'},
}


Expand All @@ -250,6 +252,7 @@ def test_database_out_detail():
'host': 'some',
'password': 'qwerty',
},
account_id='PA-123',
)

assert jsonable_encoder(DatabaseOutDetail(case=CaseFactory(id='CS-100'), **db)) == {
Expand All @@ -268,6 +271,7 @@ def test_database_out_detail():
'password': 'qwerty',
'name': None,
},
'owner': {'id': 'PA-123'},
}


Expand Down
9 changes: 8 additions & 1 deletion ui/app/tools/rest.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,17 @@ async function request(
data = responseData;
}

let errorMsg = `Server responded with non-ok code: ${response.status}`;
if (response.status === 422) {
errorMsg = 'An input error occurred. Please fill all required fields';
} else if (response.status === 400 && 'message' in data) {
errorMsg = data.message;
}

throw new ApiError(
data,
response,
`Server responded with non-ok code: ${response.status}`,
errorMsg,
);
}

Expand Down
9 changes: 3 additions & 6 deletions ui/app/views/ActivateDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,7 @@ export default {
this.$emit('saved');
this.close();
} catch (e) {
if (e.status === 422) {
this.errorText = 'An input error occurred. Please fill all required fields';
} else {
this.errorText = `#${e.status} ${e.message}`;
}
this.errorText = e.message;
this.emit({ name: 'snackbar:error', value: e });
}
Expand All @@ -158,6 +153,8 @@ export default {
value: {
immediate: true,
handler(v) {
this.errorText = null;
const data = clone(this.item);
if (!data?.credentials) data.credentials = initialForm().credentials;
this.form = data;
Expand Down
8 changes: 2 additions & 6 deletions ui/app/views/CreateEditDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,7 @@ export default {
this.$emit('saved');
this.close();
} catch (e) {
if (e.status === 422) {
this.errorText = 'An input error occurred. Please fill all required fields';
} else {
this.errorText = `#${e.status} ${e.message}`;
}
this.errorText = e.message;
this.emit({ name: 'snackbar:error', value: e });
}
Expand All @@ -235,6 +230,7 @@ export default {
immediate: true,
async handler(v) {
if (v) {
this.errorText = null;
if (this.item) this.form = clone(this.item);
try {
Expand Down
4 changes: 4 additions & 0 deletions ui/app/views/ItemDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ div
.item-label Workload Type
.item-value.capitalize {{ localItem.workload }}

.item-row(v-if="installationContext.isAdmin")
.item-label Owner ID
.item-value.capitalize {{ localItem.owner.id }}

.divider._mt_24._mb_24

.item-row
Expand Down
10 changes: 9 additions & 1 deletion ui/app/views/ItemsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ div
template(#name="{ item }")
.detail-item
a.detail-item__text(@click="$emit('item-clicked', item)") {{ item.name }}
.detail-item__assistive-text {{ item.id }}
.detail-item__assistive-text
span {{ item.id }}
span(v-if="installationContext.isAdmin") • {{ item.owner.id }}

template(#description="{ value }")
.assistive-text {{ value }}
Expand All @@ -56,6 +58,10 @@ div
</template>

<script>
import {
mapState,
} from 'vuex';
import {
googleStorageBaseline,
} from '@cloudblueconnect/material-svg/baseline';
Expand Down Expand Up @@ -94,6 +100,8 @@ export default {
}),
computed: {
...mapState(['installationContext']),
placeholderIcon: () => googleStorageBaseline,
showPlaceholder: ({ list, loading }) => !loading && isNilOrEmpty(list),
Expand Down
1 change: 1 addition & 0 deletions ui/app/views/ReconfDialog.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('ReconfDialog', () => {
describe('#data', () => {
it('should provide initial data', () => {
expect(cmp.data()).toEqual({
errorText: null,
dialogOpened: false,
acceptTermsAndConds: false,
saving: false,
Expand Down
4 changes: 4 additions & 0 deletions ui/app/views/ReconfDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ ez-dialog(
v-model="dialogOpened",
width="800",
title="Request Reconfiguration",
:error-text="errorText",
)
ui-card(title="Type")
.two-columns
Expand Down Expand Up @@ -106,6 +107,7 @@ export default {
},
data: () => ({
errorText: null,
dialogOpened: false,
acceptTermsAndConds: false,
form: initialForm(),
Expand Down Expand Up @@ -138,6 +140,7 @@ export default {
this.$emit('saved', item);
this.close();
} catch (e) {
this.errorText = e.message;
this.emit({ name: 'snackbar:error', value: e });
/* eslint-disable no-console */
console.error(e);
Expand All @@ -151,6 +154,7 @@ export default {
value: {
immediate: true,
handler(v) {
this.errorText = null;
this.dialogOpened = v;
},
},
Expand Down

0 comments on commit a8a6667

Please sign in to comment.