diff --git a/openstack_dashboard/api/trove.py b/openstack_dashboard/api/trove.py
index 481442bbe4..208a1ea01a 100644
--- a/openstack_dashboard/api/trove.py
+++ b/openstack_dashboard/api/trove.py
@@ -80,6 +80,11 @@ def instance_resize_volume(request, instance_id, size):
return troveclient(request).instances.resize_volume(instance_id, size)
+def instance_resize(request, instance_id, flavor_id):
+ return troveclient(request).instances.resize_instance(instance_id,
+ flavor_id)
+
+
def instance_backups(request, instance_id):
return troveclient(request).instances.backups(instance_id)
diff --git a/openstack_dashboard/dashboards/project/databases/forms.py b/openstack_dashboard/dashboards/project/databases/forms.py
index 32b21d41bd..da06ae2c75 100644
--- a/openstack_dashboard/dashboards/project/databases/forms.py
+++ b/openstack_dashboard/dashboards/project/databases/forms.py
@@ -53,3 +53,46 @@ class ResizeVolumeForm(forms.SelfHandlingForm):
exceptions.handle(request, _('Unable to resize volume. %s') %
e.message, redirect=redirect)
return True
+
+
+class ResizeInstanceForm(forms.SelfHandlingForm):
+ instance_id = forms.CharField(widget=forms.HiddenInput())
+ old_flavor_name = forms.CharField(label=_("Old Flavor"),
+ required=False,
+ widget=forms.TextInput(
+ attrs={'readonly': 'readonly'}))
+ new_flavor = forms.ChoiceField(label=_("New Flavor"),
+ help_text=_("Choose a new instance "
+ "flavor."))
+
+ def __init__(self, request, *args, **kwargs):
+ super(ResizeInstanceForm, self).__init__(request, *args, **kwargs)
+
+ choices = kwargs.get('initial', {}).get('flavors')
+ if choices:
+ choices.insert(0, ("", _("Select a new flavor")))
+ else:
+ choices.insert(0, ("", _("No flavors available")))
+ self.fields['new_flavor'].choices = choices
+
+ def clean(self):
+ cleaned_data = super(ResizeInstanceForm, self).clean()
+ flavor = cleaned_data.get('new_flavor', None)
+
+ if flavor is None or flavor == self.initial['old_flavor_id']:
+ raise forms.ValidationError(_('Please choose a new flavor that '
+ 'is not the same as the old one.'))
+ return cleaned_data
+
+ def handle(self, request, data):
+ instance = data.get('instance_id')
+ flavor = data.get('new_flavor')
+ try:
+ api.trove.instance_resize(request, instance, flavor)
+
+ messages.success(request, _('Resizing instance "%s"') % instance)
+ except Exception as e:
+ redirect = reverse("horizon:project:databases:index")
+ exceptions.handle(request, _('Unable to resize instance. %s') %
+ e.message, redirect=redirect)
+ return True
diff --git a/openstack_dashboard/dashboards/project/databases/tables.py b/openstack_dashboard/dashboards/project/databases/tables.py
index 23fb017450..ef9f99613a 100644
--- a/openstack_dashboard/dashboards/project/databases/tables.py
+++ b/openstack_dashboard/dashboards/project/databases/tables.py
@@ -173,6 +173,21 @@ class ResizeVolume(tables.LinkAction):
return urlresolvers.reverse(self.url, args=[instance_id])
+class ResizeInstance(tables.LinkAction):
+ name = "resize_instance"
+ verbose_name = _("Resize Instance")
+ url = "horizon:project:databases:resize_instance"
+ classes = ("ajax-modal", "btn-resize")
+
+ def allowed(self, request, instance=None):
+ return ((instance.status in ACTIVE_STATES
+ or instance.status == 'SHUTOFF'))
+
+ def get_link_url(self, datum):
+ instance_id = self.table.get_object_id(datum)
+ return urlresolvers.reverse(self.url, args=[instance_id])
+
+
class UpdateRow(tables.Row):
ajax = True
@@ -272,6 +287,7 @@ class InstancesTable(tables.DataTable):
table_actions = (LaunchLink, TerminateInstance)
row_actions = (CreateBackup,
ResizeVolume,
+ ResizeInstance,
RestartInstance,
TerminateInstance)
diff --git a/openstack_dashboard/dashboards/project/databases/templates/databases/_resize_instance.html b/openstack_dashboard/dashboards/project/databases/templates/databases/_resize_instance.html
new file mode 100644
index 0000000000..8de3c3390f
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/databases/templates/databases/_resize_instance.html
@@ -0,0 +1,26 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+{% load url from future %}
+
+{% block form_id %}resize_instance_form{% endblock %}
+{% block form_action %}{% url "horizon:project:databases:resize_instance" instance_id %}{% endblock %}
+
+{% block modal_id %}resize_instance_modal{% endblock %}
+{% block modal-header %}{% trans "Resize Database Instance" %}{% endblock %}
+
+{% block modal-body %}
+
+
+
+
+
{% blocktrans %}Specify a new flavor for the database instance.{% endblocktrans %}
+
+{% endblock %}
+
+{% block modal-footer %}
+
+ {% trans "Cancel" %}
+{% endblock %}
+
diff --git a/openstack_dashboard/dashboards/project/databases/templates/databases/resize_instance.html b/openstack_dashboard/dashboards/project/databases/templates/databases/resize_instance.html
new file mode 100644
index 0000000000..ebbcf83aba
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/databases/templates/databases/resize_instance.html
@@ -0,0 +1,11 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% block title %}{% trans "Resize Database Instance" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Resize Database Instance") %}
+{% endblock %}
+
+{% block main %}
+ {% include "project/databases/_resize_instance.html" %}
+{% endblock %}
\ No newline at end of file
diff --git a/openstack_dashboard/dashboards/project/databases/tests.py b/openstack_dashboard/dashboards/project/databases/tests.py
index 6a23686800..501c724924 100644
--- a/openstack_dashboard/dashboards/project/databases/tests.py
+++ b/openstack_dashboard/dashboards/project/databases/tests.py
@@ -339,8 +339,7 @@ class DatabaseTests(test.TestCase):
self.assertRedirectsNoFollow(res, url)
@test.create_stubs({
- api.trove: ('instance_get', 'instance_resize_volume'),
- })
+ api.trove: ('instance_get', 'instance_resize_volume')})
def test_resize_volume(self):
database = self.databases.first()
database_id = database.id
@@ -367,9 +366,7 @@ class DatabaseTests(test.TestCase):
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
- @test.create_stubs({
- api.trove: ('instance_get', 'instance_resize_volume'),
- })
+ @test.create_stubs({api.trove: ('instance_get', )})
def test_resize_volume_bad_value(self):
database = self.databases.first()
database_id = database.id
@@ -390,3 +387,62 @@ class DatabaseTests(test.TestCase):
res = self.client.post(url, post)
self.assertContains(
res, "New size for volume must be greater than current size.")
+
+ @test.create_stubs(
+ {api.trove: ('instance_get',
+ 'flavor_list',
+ 'instance_resize')})
+ def test_resize_instance(self):
+ database = self.databases.first()
+
+ # views.py: DetailView.get_data
+ api.trove.instance_get(IsA(http.HttpRequest), database.id)\
+ .AndReturn(database)
+ api.trove.flavor_list(IsA(http.HttpRequest)).\
+ AndReturn(self.database_flavors.list())
+
+ old_flavor = self.database_flavors.list()[0]
+ new_flavor = self.database_flavors.list()[1]
+
+ api.trove.instance_resize(IsA(http.HttpRequest),
+ database.id,
+ new_flavor.id).AndReturn(None)
+
+ self.mox.ReplayAll()
+ url = reverse('horizon:project:databases:resize_instance',
+ args=[database.id])
+ post = {
+ 'instance_id': database.id,
+ 'old_flavor_name': old_flavor.name,
+ 'old_flavor_id': old_flavor.id,
+ 'new_flavor': new_flavor.id
+ }
+ res = self.client.post(url, post)
+ self.assertNoFormErrors(res)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+
+ @test.create_stubs(
+ {api.trove: ('instance_get', 'flavor_list')})
+ def test_resize_instance_bad_value(self):
+ database = self.databases.first()
+
+ api.trove.instance_get(IsA(http.HttpRequest),
+ database.id).AndReturn(database)
+ api.trove.flavor_list(IsA(http.HttpRequest)).\
+ AndReturn(self.database_flavors.list())
+
+ old_flavor = self.database_flavors.list()[0]
+
+ self.mox.ReplayAll()
+ url = reverse('horizon:project:databases:resize_instance',
+ args=[database.id])
+ post = {
+ 'instance_id': database.id,
+ 'old_flavor_name': old_flavor.name,
+ 'old_flavor_id': old_flavor.id,
+ 'new_flavor': old_flavor.id
+ }
+ res = self.client.post(url, post)
+ self.assertContains(res,
+ "Please choose a new flavor that is "
+ "not the same as the old one.")
diff --git a/openstack_dashboard/dashboards/project/databases/urls.py b/openstack_dashboard/dashboards/project/databases/urls.py
index 701dbf6e79..0b62bebf05 100644
--- a/openstack_dashboard/dashboards/project/databases/urls.py
+++ b/openstack_dashboard/dashboards/project/databases/urls.py
@@ -27,5 +27,7 @@ urlpatterns = patterns(
url(r'^launch$', views.LaunchInstanceView.as_view(), name='launch'),
url(INSTANCES % '', views.DetailView.as_view(), name='detail'),
url(INSTANCES % 'resize_volume', views.ResizeVolumeView.as_view(),
- name='resize_volume')
+ name='resize_volume'),
+ url(INSTANCES % 'resize_instance', views.ResizeInstanceView.as_view(),
+ name='resize_instance')
)
diff --git a/openstack_dashboard/dashboards/project/databases/views.py b/openstack_dashboard/dashboards/project/databases/views.py
index 4c2db269e9..191edbb457 100644
--- a/openstack_dashboard/dashboards/project/databases/views.py
+++ b/openstack_dashboard/dashboards/project/databases/views.py
@@ -35,6 +35,9 @@ from openstack_dashboard.dashboards.project.databases import tables
from openstack_dashboard.dashboards.project.databases import tabs
from openstack_dashboard.dashboards.project.databases import workflows
+from openstack_dashboard.dashboards.project.instances \
+ import utils as instance_utils
+
LOG = logging.getLogger(__name__)
@@ -156,3 +159,58 @@ class ResizeVolumeView(horizon_forms.ModalFormView):
instance = self.get_object()
return {'instance_id': self.kwargs['instance_id'],
'orig_size': instance.volume.get('size', 0)}
+
+
+class ResizeInstanceView(horizon_forms.ModalFormView):
+ form_class = forms.ResizeInstanceForm
+ template_name = 'project/databases/resize_instance.html'
+ success_url = reverse_lazy('horizon:project:databases:index')
+
+ @memoized.memoized_method
+ def get_object(self, *args, **kwargs):
+ instance_id = self.kwargs['instance_id']
+
+ try:
+ instance = api.trove.instance_get(self.request, instance_id)
+ flavor_id = instance.flavor['id']
+ flavors = {}
+ for i, j in self.get_flavors():
+ flavors[str(i)] = j
+
+ if flavor_id in flavors:
+ instance.flavor_name = flavors[flavor_id]
+ else:
+ flavor = api.trove.flavor_get(self.request, flavor_id)
+ instance.flavor_name = flavor.name
+ return instance
+ except Exception:
+ redirect = reverse('horizon:project:databases:index')
+ msg = _('Unable to retrieve instance details.')
+ exceptions.handle(self.request, msg, redirect=redirect)
+
+ def get_context_data(self, **kwargs):
+ context = super(ResizeInstanceView, self).get_context_data(**kwargs)
+ context['instance_id'] = self.kwargs['instance_id']
+ return context
+
+ @memoized.memoized_method
+ def get_flavors(self, *args, **kwargs):
+ try:
+ flavors = api.trove.flavor_list(self.request)
+ return instance_utils.sort_flavor_list(self.request, flavors)
+ except Exception:
+ redirect = reverse("horizon:project:databases:index")
+ exceptions.handle(self.request,
+ _('Unable to retrieve flavors.'),
+ redirect=redirect)
+
+ def get_initial(self):
+ initial = super(ResizeInstanceView, self).get_initial()
+ obj = self.get_object()
+ if obj:
+ initial.update({'instance_id': self.kwargs['instance_id'],
+ 'old_flavor_id': obj.flavor['id'],
+ 'old_flavor_name': getattr(obj,
+ 'flavor_name', ''),
+ 'flavors': self.get_flavors()})
+ return initial
diff --git a/openstack_dashboard/test/test_data/trove_data.py b/openstack_dashboard/test/test_data/trove_data.py
index 40ce597fa2..a791f2acac 100644
--- a/openstack_dashboard/test/test_data/trove_data.py
+++ b/openstack_dashboard/test/test_data/trove_data.py
@@ -15,6 +15,7 @@
from troveclient.v1 import backups
from troveclient.v1 import databases
from troveclient.v1 import datastores
+from troveclient.v1 import flavors
from troveclient.v1 import instances
from troveclient.v1 import users
@@ -149,6 +150,27 @@ VERSION_TWO = {
"id": "500a6d52-8347-4e00-8e4c-f4fa9cf96ae9"
}
+FLAVOR_ONE = {
+ "ram": 512,
+ "id": "1",
+ "links": [],
+ "name": "m1.tiny"
+}
+
+FLAVOR_TWO = {
+ "ram": 768,
+ "id": "10",
+ "links": [],
+ "name": "eph.rd-smaller"
+}
+
+FLAVOR_THREE = {
+ "ram": 800,
+ "id": "100",
+ "links": [],
+ "name": "test.1"
+}
+
def data(TEST):
database1 = instances.Instance(instances.Instances(None),
@@ -172,10 +194,16 @@ def data(TEST):
DatastoreVersion(datastores.DatastoreVersions(None),
VERSION_TWO)
+ flavor1 = flavors.Flavor(flavors.Flavors(None), FLAVOR_ONE)
+ flavor2 = flavors.Flavor(flavors.Flavors(None), FLAVOR_TWO)
+ flavor3 = flavors.Flavor(flavors.Flavors(None), FLAVOR_THREE)
+
TEST.databases = utils.TestDataContainer()
TEST.database_backups = utils.TestDataContainer()
TEST.database_users = utils.TestDataContainer()
TEST.database_user_dbs = utils.TestDataContainer()
+ TEST.database_flavors = utils.TestDataContainer()
+
TEST.databases.add(database1)
TEST.databases.add(database2)
TEST.database_backups.add(bkup1)
@@ -188,3 +216,4 @@ def data(TEST):
TEST.datastore_versions = utils.TestDataContainer()
TEST.datastore_versions.add(version1)
TEST.datastore_versions.add(version2)
+ TEST.database_flavors.add(flavor1, flavor2, flavor3)