Specify database instance access during creation
Change-Id: I75950ae949e7f19e48f5246e4db9f5d5c514dd2b
This commit is contained in:
parent
2db165a81a
commit
c9d6d03a0f
@ -7,3 +7,4 @@ oslo.log>=3.30.0 # Apache-2.0
|
|||||||
python-swiftclient>=2.2.0
|
python-swiftclient>=2.2.0
|
||||||
python-troveclient>=1.2.0
|
python-troveclient>=1.2.0
|
||||||
horizon>=17.1.0 # Apache-2.0
|
horizon>=17.1.0 # Apache-2.0
|
||||||
|
netaddr>=0.7.18 # BSD
|
||||||
|
@ -149,7 +149,7 @@ def instance_create(request, name, volume, flavor=None, databases=None,
|
|||||||
datastore=None, datastore_version=None,
|
datastore=None, datastore_version=None,
|
||||||
replica_of=None, replica_count=None,
|
replica_of=None, replica_count=None,
|
||||||
volume_type=None, configuration=None, locality=None,
|
volume_type=None, configuration=None, locality=None,
|
||||||
availability_zone=None):
|
availability_zone=None, access=None):
|
||||||
# TODO(dklyle): adding conditional to support trove without volume
|
# TODO(dklyle): adding conditional to support trove without volume
|
||||||
# support for now until API supports checking for volume support
|
# support for now until API supports checking for volume support
|
||||||
if volume > 0 and not replica_of:
|
if volume > 0 and not replica_of:
|
||||||
@ -179,7 +179,8 @@ def instance_create(request, name, volume, flavor=None, databases=None,
|
|||||||
replica_count=replica_count,
|
replica_count=replica_count,
|
||||||
configuration=configuration,
|
configuration=configuration,
|
||||||
locality=locality,
|
locality=locality,
|
||||||
availability_zone=availability_zone)
|
availability_zone=availability_zone,
|
||||||
|
access=access)
|
||||||
|
|
||||||
|
|
||||||
def instance_resize_volume(request, instance_id, size):
|
def instance_resize_volume(request, instance_id, size):
|
||||||
|
@ -214,7 +214,7 @@ class DatabasesBackupsTests(test.TestCase):
|
|||||||
url = RESTORE_URL + '?backup=%s' % self.database_backups.first().id
|
url = RESTORE_URL + '?backup=%s' % self.database_backups.first().id
|
||||||
res = self.client.get(url)
|
res = self.client.get(url)
|
||||||
self.assert_mock_multiple_calls_with_same_arguments(
|
self.assert_mock_multiple_calls_with_same_arguments(
|
||||||
self.mock_check, 4, mock.call((), test.IsHttpRequest()))
|
self.mock_check, 5, mock.call((), test.IsHttpRequest()))
|
||||||
self.mock_backup_get.assert_called_once_with(
|
self.mock_backup_get.assert_called_once_with(
|
||||||
test.IsHttpRequest(), test.IsA(str))
|
test.IsHttpRequest(), test.IsA(str))
|
||||||
self.mock_backup_list.assert_called_once_with(test.IsHttpRequest())
|
self.mock_backup_list.assert_called_once_with(test.IsHttpRequest())
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<p>{% blocktrans trimmed %}Access defines how the database service is exposed.{% endblocktrans %}</p>
|
||||||
|
<p>{% blocktrans trimmed %}CIDRs is a comma-separated list of IPv4, IPv6 or mix of both CIDRs that restrict access to the database service. 0.0.0.0/0 is used by default if this parameter is not provided. E.g.: 0.0.0.0/0,202.78.240.0/24{% endblocktrans %}</p>
|
@ -163,7 +163,7 @@ class DatabaseTests(test.TestCase):
|
|||||||
|
|
||||||
res = self.client.get(LAUNCH_URL)
|
res = self.client.get(LAUNCH_URL)
|
||||||
self.assert_mock_multiple_calls_with_same_arguments(
|
self.assert_mock_multiple_calls_with_same_arguments(
|
||||||
self.mock_check, 4, mock.call((), test.IsHttpRequest()))
|
self.mock_check, 5, mock.call((), test.IsHttpRequest()))
|
||||||
self.assert_mock_multiple_calls_with_same_arguments(
|
self.assert_mock_multiple_calls_with_same_arguments(
|
||||||
self.mock_datastore_flavors, 20,
|
self.mock_datastore_flavors, 20,
|
||||||
mock.call(test.IsHttpRequest(),
|
mock.call(test.IsHttpRequest(),
|
||||||
@ -269,7 +269,7 @@ class DatabaseTests(test.TestCase):
|
|||||||
|
|
||||||
res = self.client.post(LAUNCH_URL, post)
|
res = self.client.post(LAUNCH_URL, post)
|
||||||
self.assert_mock_multiple_calls_with_same_arguments(
|
self.assert_mock_multiple_calls_with_same_arguments(
|
||||||
self.mock_check, 4, mock.call((), test.IsHttpRequest()))
|
self.mock_check, 5, mock.call((), test.IsHttpRequest()))
|
||||||
self.assert_mock_multiple_calls_with_same_arguments(
|
self.assert_mock_multiple_calls_with_same_arguments(
|
||||||
self.mock_datastore_flavors, 20,
|
self.mock_datastore_flavors, 20,
|
||||||
mock.call(test.IsHttpRequest(),
|
mock.call(test.IsHttpRequest(),
|
||||||
@ -306,7 +306,8 @@ class DatabaseTests(test.TestCase):
|
|||||||
replica_count=None,
|
replica_count=None,
|
||||||
volume_type=None,
|
volume_type=None,
|
||||||
locality=None,
|
locality=None,
|
||||||
availability_zone=test.IsA(str))
|
availability_zone=test.IsA(str),
|
||||||
|
access=None)
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||||
|
|
||||||
@test.create_mocks({
|
@test.create_mocks({
|
||||||
@ -359,7 +360,7 @@ class DatabaseTests(test.TestCase):
|
|||||||
|
|
||||||
res = self.client.post(LAUNCH_URL, post)
|
res = self.client.post(LAUNCH_URL, post)
|
||||||
self.assert_mock_multiple_calls_with_same_arguments(
|
self.assert_mock_multiple_calls_with_same_arguments(
|
||||||
self.mock_check, 4, mock.call((), test.IsHttpRequest()))
|
self.mock_check, 5, mock.call((), test.IsHttpRequest()))
|
||||||
self.assert_mock_multiple_calls_with_same_arguments(
|
self.assert_mock_multiple_calls_with_same_arguments(
|
||||||
self.mock_datastore_flavors, 20,
|
self.mock_datastore_flavors, 20,
|
||||||
mock.call(test.IsHttpRequest(),
|
mock.call(test.IsHttpRequest(),
|
||||||
@ -396,7 +397,8 @@ class DatabaseTests(test.TestCase):
|
|||||||
replica_count=None,
|
replica_count=None,
|
||||||
volume_type=None,
|
volume_type=None,
|
||||||
locality=None,
|
locality=None,
|
||||||
availability_zone=test.IsA(str))
|
availability_zone=test.IsA(str),
|
||||||
|
access=None)
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||||
|
|
||||||
@test.create_mocks({
|
@test.create_mocks({
|
||||||
@ -1093,7 +1095,7 @@ class DatabaseTests(test.TestCase):
|
|||||||
|
|
||||||
res = self.client.post(LAUNCH_URL, post)
|
res = self.client.post(LAUNCH_URL, post)
|
||||||
self.assert_mock_multiple_calls_with_same_arguments(
|
self.assert_mock_multiple_calls_with_same_arguments(
|
||||||
self.mock_check, 4, mock.call((), test.IsHttpRequest()))
|
self.mock_check, 5, mock.call((), test.IsHttpRequest()))
|
||||||
self.assert_mock_multiple_calls_with_same_arguments(
|
self.assert_mock_multiple_calls_with_same_arguments(
|
||||||
self.mock_datastore_flavors, 20,
|
self.mock_datastore_flavors, 20,
|
||||||
mock.call(test.IsHttpRequest(),
|
mock.call(test.IsHttpRequest(),
|
||||||
@ -1133,7 +1135,8 @@ class DatabaseTests(test.TestCase):
|
|||||||
replica_count=2,
|
replica_count=2,
|
||||||
volume_type=None,
|
volume_type=None,
|
||||||
locality=None,
|
locality=None,
|
||||||
availability_zone=test.IsA(str))
|
availability_zone=test.IsA(str),
|
||||||
|
access=None)
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||||
|
|
||||||
@test.create_mocks({
|
@test.create_mocks({
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
import netaddr
|
||||||
|
|
||||||
from horizon import exceptions
|
from horizon import exceptions
|
||||||
from horizon import forms
|
from horizon import forms
|
||||||
@ -283,6 +284,44 @@ class SetInstanceDetails(workflows.Step):
|
|||||||
"locality", "availability_zone")
|
"locality", "availability_zone")
|
||||||
|
|
||||||
|
|
||||||
|
class AddAccessAction(workflows.Action):
|
||||||
|
"""Initialize the database access. This tab will honor
|
||||||
|
the settings which should be a list of permissions required:
|
||||||
|
|
||||||
|
* TROVE_ADD_USER_PERMS = []
|
||||||
|
* TROVE_ADD_DATABASE_PERMS = []
|
||||||
|
"""
|
||||||
|
is_public = forms.BooleanField(label=_("Is Public"),
|
||||||
|
required=False)
|
||||||
|
allowed_cidrs = forms.CharField(label=_("Allowed CIDRs"),
|
||||||
|
required=False,
|
||||||
|
help_text=_("Comma-separated CIDRs "
|
||||||
|
"to connect through."))
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = _("Database Access")
|
||||||
|
permissions = TROVE_ADD_PERMS
|
||||||
|
help_text_template = "project/databases/_launch_access_help.html"
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super(AddAccessAction, self).clean()
|
||||||
|
if cleaned_data.get('allowed_cidrs'):
|
||||||
|
cidrs = cleaned_data.get('allowed_cidrs').split(',')
|
||||||
|
for cidr in cidrs:
|
||||||
|
try:
|
||||||
|
netaddr.IPNetwork(cidr)
|
||||||
|
except netaddr.AddrFormatError:
|
||||||
|
msg = _('Invalid Allowed CIDR provided.')
|
||||||
|
self._errors["allowed_cidrs"] = self.error_class([msg])
|
||||||
|
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseAccess(workflows.Step):
|
||||||
|
action_class = AddAccessAction
|
||||||
|
contributes = ["is_public", "allowed_cidrs"]
|
||||||
|
|
||||||
|
|
||||||
class AddDatabasesAction(workflows.Action):
|
class AddDatabasesAction(workflows.Action):
|
||||||
"""Initialize the database with users/databases. This tab will honor
|
"""Initialize the database with users/databases. This tab will honor
|
||||||
the settings which should be a list of permissions required:
|
the settings which should be a list of permissions required:
|
||||||
@ -513,6 +552,7 @@ class LaunchInstance(workflows.Workflow):
|
|||||||
success_url = "horizon:project:databases:index"
|
success_url = "horizon:project:databases:index"
|
||||||
default_steps = (SetInstanceDetails,
|
default_steps = (SetInstanceDetails,
|
||||||
dash_create_instance.SetNetwork,
|
dash_create_instance.SetNetwork,
|
||||||
|
DatabaseAccess,
|
||||||
InitializeDatabase,
|
InitializeDatabase,
|
||||||
Advanced)
|
Advanced)
|
||||||
|
|
||||||
@ -577,6 +617,16 @@ class LaunchInstance(workflows.Workflow):
|
|||||||
locality = context['locality']
|
locality = context['locality']
|
||||||
return locality
|
return locality
|
||||||
|
|
||||||
|
def _get_access(self, context):
|
||||||
|
if not context['allowed_cidrs'] and not context['is_public']:
|
||||||
|
return None
|
||||||
|
access = {}
|
||||||
|
if context['allowed_cidrs'] != '':
|
||||||
|
access['allowed_cidrs'].split(',')
|
||||||
|
if context['is_public']:
|
||||||
|
access['is_public'] = True
|
||||||
|
return access
|
||||||
|
|
||||||
def handle(self, request, context):
|
def handle(self, request, context):
|
||||||
try:
|
try:
|
||||||
datastore, datastore_version = parse_datastore_and_version_text(
|
datastore, datastore_version = parse_datastore_and_version_text(
|
||||||
@ -597,6 +647,7 @@ class LaunchInstance(workflows.Workflow):
|
|||||||
context.get('master'), context['replica_count'],
|
context.get('master'), context['replica_count'],
|
||||||
context.get('config'), self._get_locality(context),
|
context.get('config'), self._get_locality(context),
|
||||||
avail_zone)
|
avail_zone)
|
||||||
|
|
||||||
api.trove.instance_create(request,
|
api.trove.instance_create(request,
|
||||||
context['name'],
|
context['name'],
|
||||||
context['volume'],
|
context['volume'],
|
||||||
@ -613,7 +664,8 @@ class LaunchInstance(workflows.Workflow):
|
|||||||
context),
|
context),
|
||||||
configuration=context.get('config'),
|
configuration=context.get('config'),
|
||||||
locality=self._get_locality(context),
|
locality=self._get_locality(context),
|
||||||
availability_zone=avail_zone)
|
availability_zone=avail_zone,
|
||||||
|
access=self._get_access(context))
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
exceptions.handle(request)
|
exceptions.handle(request)
|
||||||
|
Loading…
Reference in New Issue
Block a user