Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

outcalls: auto generate name to improve multi-tenancy #402

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 23.10

* An outcall now has a read-only auto-generated `name` and a required `label` instead
of a user-provided `name` and optional `label`. For compatibility purposes, if an outcall is
created using only a `name`, it will be used as the `label` and auto-generated.

## 23.04

* PUT on `/users?recursive=true` updated, to provide a way to update lines, switchboards, agent, voicemail and incalls for a specific user.
Expand Down
30 changes: 11 additions & 19 deletions integration_tests/suite/base/test_outcalls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2016-2022 The Wazo Authors (see the AUTHORS file)
# Copyright 2016-2023 The Wazo Authors (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

from hamcrest import (
Expand Down Expand Up @@ -47,12 +47,11 @@ def error_checks(url):
)
yield s.check_bogus_field_returns_error, url, 'preprocess_subroutine', []
yield s.check_bogus_field_returns_error, url, 'preprocess_subroutine', {}
yield s.check_bogus_field_returns_error, url, 'name', True
yield s.check_bogus_field_returns_error, url, 'name', None
yield s.check_bogus_field_returns_error, url, 'name', s.random_string(129)
yield s.check_bogus_field_returns_error, url, 'name', 1234
yield s.check_bogus_field_returns_error, url, 'name', []
yield s.check_bogus_field_returns_error, url, 'name', {}
Comment on lines -50 to -55
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as #401 (comment)
about testing deprecated name

yield s.check_bogus_field_returns_error, url, 'label', 123
yield s.check_bogus_field_returns_error, url, 'label', None
yield s.check_bogus_field_returns_error, url, 'label', True
yield s.check_bogus_field_returns_error, url, 'label', {}
yield s.check_bogus_field_returns_error, url, 'label', []
yield s.check_bogus_field_returns_error, url, 'internal_caller_id', 1234
yield s.check_bogus_field_returns_error, url, 'internal_caller_id', 'invalid'
yield s.check_bogus_field_returns_error, url, 'internal_caller_id', None
Expand All @@ -69,20 +68,12 @@ def error_checks(url):
yield s.check_bogus_field_returns_error, url, 'enabled', []
yield s.check_bogus_field_returns_error, url, 'enabled', {}

for check in unique_error_checks(url):
yield check


@fixtures.outcall(name='unique')
def unique_error_checks(url, outcall):
yield s.check_bogus_field_returns_error, url, 'name', outcall['name']


@fixtures.outcall(description='search')
@fixtures.outcall(label='search', description='search')
@fixtures.outcall(description='hidden')
def test_search(outcall, hidden):
url = confd.outcalls
searches = {'description': 'search'}
searches = {'label': 'search', 'description': 'search'}

for field, term in searches.items():
yield check_search, url, outcall, hidden, field, term
Expand Down Expand Up @@ -133,6 +124,7 @@ def test_get(outcall):
description=outcall['description'],
internal_caller_id=outcall['internal_caller_id'],
name=outcall['name'],
label=outcall['label'],
ring_time=outcall['ring_time'],
trunks=empty(),
),
Expand Down Expand Up @@ -160,7 +152,7 @@ def test_create_minimal_parameters():

def test_create_all_parameters():
parameters = {
'name': 'MyOutcall',
'label': 'My Outcall',
'internal_caller_id': True,
'preprocess_subroutine': 'subroutine',
'ring_time': 10,
Expand All @@ -185,7 +177,7 @@ def test_edit_minimal_parameters(outcall):
@fixtures.outcall()
def test_edit_all_parameters(outcall):
parameters = {
'name': 'MyOutcall',
'label': 'MyOutcall',
'internal_caller_id': True,
'preprocess_subroutine': 'subroutine',
'ring_time': 10,
Expand Down
20 changes: 10 additions & 10 deletions integration_tests/suite/helpers/helpers/outcall.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2016-2021 The Wazo Authors (see the AUTHORS file)
# Copyright 2016-2023 The Wazo Authors (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

import random
Expand All @@ -9,7 +9,7 @@


def generate_outcall(**parameters):
parameters.setdefault('name', generate_name())
parameters.setdefault('label', generate_label())
parameters.setdefault('context', CONTEXT)
return add_outcall(**parameters)

Expand All @@ -25,14 +25,14 @@ def delete_outcall(outcall_id, check=False, **params):
response.assert_ok()


def generate_name():
def generate_label():
response = confd.outcalls.get()
names = set(d['name'] for d in response.items)
return _random_name(names)
labels = set(d['label'] for d in response.items)
return _random_label(labels)


def _random_name(names):
name = ''.join(random.choice(string.ascii_letters) for _ in range(10))
if name in names:
return _random_name(names)
return name
def _random_label(labels):
label = ''.join(random.choice(string.ascii_lowercase) for i in range(10))
while label in labels:
label = ''.join(random.choice(string.ascii_lowercase) for i in range(10))
return label
6 changes: 5 additions & 1 deletion wazo_confd/plugins/outcall/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ definitions:
- $ref: '#/definitions/OutcallRelationSchedules'
- $ref: '#/definitions/OutcallRelationCallPermissions'
- required:
- name
- label
OutcallRelationBase:
properties:
id:
Expand All @@ -143,6 +143,10 @@ definitions:
name:
type: string
description: The name of the outcall
readOnly: true
label:
type: string
description: The label of the outcall
OutcallRelationTrunks:
properties:
trunks:
Expand Down
8 changes: 6 additions & 2 deletions wazo_confd/plugins/outcall/plugin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Copyright 2016-2019 The Wazo Authors (see the AUTHORS file)
# Copyright 2016-2023 The Wazo Authors (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

from xivo_dao.resources.tenant import dao as tenant_dao

from .resource import OutcallItem, OutcallList
from .service import build_service

Expand All @@ -10,7 +12,9 @@ def load(self, dependencies):
api = dependencies['api']
service = build_service()

api.add_resource(OutcallList, '/outcalls', resource_class_args=(service,))
api.add_resource(
OutcallList, '/outcalls', resource_class_args=(tenant_dao, service)
)

api.add_resource(
OutcallItem,
Expand Down
22 changes: 20 additions & 2 deletions wazo_confd/plugins/outcall/resource.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Copyright 2016-2023 The Wazo Authors (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

from flask import url_for
from flask import request, url_for
from uuid import uuid4

from xivo_dao.alchemy.outcall import Outcall

Expand All @@ -14,13 +15,30 @@
class OutcallList(ListResource):
model = Outcall
schema = OutcallSchema
outcall_name_fmt = 'outcall-{tenant_slug}-{outcall_uuid}'

def __init__(self, tenant_dao, *args, **kwargs):
super().__init__(*args, **kwargs)
self._tenant_dao = tenant_dao

def build_headers(self, outcall):
return {'Location': url_for('outcalls', id=outcall.id, _external=True)}

@required_acl('confd.outcalls.create')
def post(self):
return super().post()
form = self.schema().load(request.get_json())
form = self.add_tenant_to_form(form)

tenant = self._tenant_dao.get(form['tenant_uuid'])
# NOTE(afournier): we use a UUID as if it was the outcall UUID but it's not
# Outcalls do not use UUIDs yet
form['name'] = self.outcall_name_fmt.format(
tenant_slug=tenant.slug,
outcall_uuid=uuid4(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as: #401 (comment)
About adding uuid in database

)
model = self.model(**form)
model = self.service.create(model)
return self.schema().dump(model), 201, self.build_headers(model)

@required_acl('confd.outcalls.read')
def get(self):
Expand Down
22 changes: 19 additions & 3 deletions wazo_confd/plugins/outcall/schema.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
# Copyright 2016-2022 The Wazo Authors (see the AUTHORS file)
# Copyright 2016-2023 The Wazo Authors (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

import logging

from marshmallow import fields, post_dump
from marshmallow import fields, post_dump, pre_load
from marshmallow.validate import Length, Range

from wazo_confd.helpers.mallow import BaseSchema, StrictBoolean, Link, ListLink, Nested

logger = logging.getLogger(__name__)


class OutcallSchema(BaseSchema):
id = fields.Integer(dump_only=True)
tenant_uuid = fields.String(dump_only=True)
name = fields.String(validate=Length(max=128), required=True)
name = fields.String(dump_only=True)
label = fields.String(validate=Length(max=128), required=True)
internal_caller_id = StrictBoolean()
preprocess_subroutine = fields.String(validate=Length(max=39), allow_none=True)
ring_time = fields.Integer(validate=Range(min=0), allow_none=True)
Expand Down Expand Up @@ -40,6 +44,18 @@ class OutcallSchema(BaseSchema):
dump_only=True,
)

# DEPRECATED 23.10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# DEPRECATED 23.10
# DEPRECATED 23.11

@pre_load
def copy_name_to_label(self, data, **kwargs):
if 'label' in data:
return data
if 'name' in data:
logger.warning(
'The "name" field of outcall is deprecated. Use "label" instead'
)
data['label'] = data['name']
return data


class DialPatternSchema(BaseSchema):
external_prefix = fields.String()
Expand Down
4 changes: 1 addition & 3 deletions wazo_confd/plugins/outcall/validator.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Copyright 2016-2020 The Wazo Authors (see the AUTHORS file)
# Copyright 2016-2023 The Wazo Authors (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

from xivo_dao.resources.outcall import dao as outcall_dao

from wazo_confd.helpers.validator import (
UniqueField,
UniqueFieldChanged,
ValidationGroup,
)

Expand All @@ -15,5 +14,4 @@ def build_validator():
create=[
UniqueField('name', lambda name: outcall_dao.find_by(name=name), 'Outcall')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove this validator too, since the input does not come from the user anymore

],
edit=[UniqueFieldChanged('name', outcall_dao.find_by, 'Outcall')],
)