Merge "Excise Trove from Horizon"
This commit is contained in:
commit
948fa99ae5
@ -1,5 +0,0 @@
|
||||
from openstack_dashboard.contrib.trove.api import trove
|
||||
|
||||
__all__ = [
|
||||
"trove"
|
||||
]
|
@ -1,218 +0,0 @@
|
||||
# Copyright 2013 Rackspace Hosting.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from troveclient.v1 import client
|
||||
|
||||
from openstack_dashboard.api import base
|
||||
|
||||
from horizon.utils import functions as utils
|
||||
from horizon.utils.memoized import memoized # noqa
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@memoized
|
||||
def troveclient(request):
|
||||
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
|
||||
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
|
||||
trove_url = base.url_for(request, 'database')
|
||||
c = client.Client(request.user.username,
|
||||
request.user.token.id,
|
||||
project_id=request.user.project_id,
|
||||
auth_url=trove_url,
|
||||
insecure=insecure,
|
||||
cacert=cacert,
|
||||
http_log_debug=settings.DEBUG)
|
||||
c.client.auth_token = request.user.token.id
|
||||
c.client.management_url = trove_url
|
||||
return c
|
||||
|
||||
|
||||
def cluster_list(request, marker=None):
|
||||
page_size = utils.get_page_size(request)
|
||||
return troveclient(request).clusters.list(limit=page_size, marker=marker)
|
||||
|
||||
|
||||
def cluster_get(request, cluster_id):
|
||||
return troveclient(request).clusters.get(cluster_id)
|
||||
|
||||
|
||||
def cluster_delete(request, cluster_id):
|
||||
return troveclient(request).clusters.delete(cluster_id)
|
||||
|
||||
|
||||
def cluster_create(request, name, volume, flavor, num_instances,
|
||||
datastore, datastore_version,
|
||||
nics=None, root_password=None):
|
||||
# TODO(dklyle): adding to support trove without volume
|
||||
# support for now until API supports checking for volume support
|
||||
if volume > 0:
|
||||
volume_params = {'size': volume}
|
||||
else:
|
||||
volume_params = None
|
||||
instances = []
|
||||
for i in range(num_instances):
|
||||
instance = {}
|
||||
instance["flavorRef"] = flavor
|
||||
instance["volume"] = volume_params
|
||||
if nics:
|
||||
instance["nics"] = [{"net-id": nics}]
|
||||
instances.append(instance)
|
||||
|
||||
# TODO(saurabhs): vertica needs root password on cluster create
|
||||
return troveclient(request).clusters.create(
|
||||
name,
|
||||
datastore,
|
||||
datastore_version,
|
||||
instances=instances)
|
||||
|
||||
|
||||
def cluster_add_shard(request, cluster_id):
|
||||
return troveclient(request).clusters.add_shard(cluster_id)
|
||||
|
||||
|
||||
def create_cluster_root(request, cluster_id, password):
|
||||
# It appears the code below depends on this trove change
|
||||
# https://review.openstack.org/#/c/166954/. Comment out when that
|
||||
# change merges.
|
||||
# return troveclient(request).cluster.reset_root_password(cluster_id)
|
||||
troveclient(request).root.create_cluster_root(cluster_id, password)
|
||||
|
||||
|
||||
def instance_list(request, marker=None):
|
||||
page_size = utils.get_page_size(request)
|
||||
return troveclient(request).instances.list(limit=page_size, marker=marker)
|
||||
|
||||
|
||||
def instance_get(request, instance_id):
|
||||
return troveclient(request).instances.get(instance_id)
|
||||
|
||||
|
||||
def instance_delete(request, instance_id):
|
||||
return troveclient(request).instances.delete(instance_id)
|
||||
|
||||
|
||||
def instance_create(request, name, volume, flavor, databases=None,
|
||||
users=None, restore_point=None, nics=None,
|
||||
datastore=None, datastore_version=None,
|
||||
replica_of=None):
|
||||
# TODO(dklyle): adding conditional to support trove without volume
|
||||
# support for now until API supports checking for volume support
|
||||
if volume > 0:
|
||||
volume_params = {'size': volume}
|
||||
else:
|
||||
volume_params = None
|
||||
return troveclient(request).instances.create(
|
||||
name,
|
||||
flavor,
|
||||
volume=volume_params,
|
||||
databases=databases,
|
||||
users=users,
|
||||
restorePoint=restore_point,
|
||||
nics=nics,
|
||||
datastore=datastore,
|
||||
datastore_version=datastore_version,
|
||||
replica_of=replica_of)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def instance_restart(request, instance_id):
|
||||
return troveclient(request).instances.restart(instance_id)
|
||||
|
||||
|
||||
def instance_detach_replica(request, instance_id):
|
||||
return troveclient(request).instances.edit(instance_id,
|
||||
detach_replica_source=True)
|
||||
|
||||
|
||||
def database_list(request, instance_id):
|
||||
return troveclient(request).databases.list(instance_id)
|
||||
|
||||
|
||||
def database_delete(request, instance_id, db_name):
|
||||
return troveclient(request).databases.delete(instance_id, db_name)
|
||||
|
||||
|
||||
def backup_list(request):
|
||||
return troveclient(request).backups.list()
|
||||
|
||||
|
||||
def backup_get(request, backup_id):
|
||||
return troveclient(request).backups.get(backup_id)
|
||||
|
||||
|
||||
def backup_delete(request, backup_id):
|
||||
return troveclient(request).backups.delete(backup_id)
|
||||
|
||||
|
||||
def backup_create(request, name, instance_id, description=None,
|
||||
parent_id=None):
|
||||
return troveclient(request).backups.create(name, instance_id,
|
||||
description, parent_id)
|
||||
|
||||
|
||||
def flavor_list(request):
|
||||
return troveclient(request).flavors.list()
|
||||
|
||||
|
||||
def datastore_flavors(request, datastore_name=None,
|
||||
datastore_version=None):
|
||||
# if datastore info is available then get datastore specific flavors
|
||||
if datastore_name and datastore_version:
|
||||
try:
|
||||
return troveclient(request).flavors.\
|
||||
list_datastore_version_associated_flavors(datastore_name,
|
||||
datastore_version)
|
||||
except Exception:
|
||||
LOG.warn("Failed to retrieve datastore specific flavors")
|
||||
return flavor_list(request)
|
||||
|
||||
|
||||
def flavor_get(request, flavor_id):
|
||||
return troveclient(request).flavors.get(flavor_id)
|
||||
|
||||
|
||||
def users_list(request, instance_id):
|
||||
return troveclient(request).users.list(instance_id)
|
||||
|
||||
|
||||
def user_delete(request, instance_id, user):
|
||||
return troveclient(request).users.delete(instance_id, user)
|
||||
|
||||
|
||||
def user_list_access(request, instance_id, user):
|
||||
return troveclient(request).users.list_access(instance_id, user)
|
||||
|
||||
|
||||
def datastore_list(request):
|
||||
return troveclient(request).datastores.list()
|
||||
|
||||
|
||||
def datastore_version_list(request, datastore):
|
||||
return troveclient(request).datastore_versions.list(datastore)
|
@ -1,24 +0,0 @@
|
||||
# Copyright 2013 Rackspace Hosting
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
|
||||
|
||||
class Backups(horizon.Panel):
|
||||
name = _("Backups")
|
||||
slug = 'database_backups'
|
||||
permissions = ('openstack.services.database',
|
||||
'openstack.services.object-store',)
|
@ -1,185 +0,0 @@
|
||||
# Copyright 2013 Rackspace Hosting
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template import defaultfilters as d_filters
|
||||
from django.utils.translation import pgettext_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import tables
|
||||
from horizon.utils import filters
|
||||
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
|
||||
|
||||
STATUS_CHOICES = (
|
||||
("BUILDING", None),
|
||||
("COMPLETED", True),
|
||||
("DELETE_FAILED", False),
|
||||
("FAILED", False),
|
||||
("NEW", None),
|
||||
("SAVING", None),
|
||||
)
|
||||
STATUS_DISPLAY_CHOICES = (
|
||||
("BUILDING", pgettext_lazy("Current status of a Database Backup",
|
||||
u"Building")),
|
||||
("COMPLETED", pgettext_lazy("Current status of a Database Backup",
|
||||
u"Completed")),
|
||||
("DELETE_FAILED", pgettext_lazy("Current status of a Database Backup",
|
||||
u"Delete Failed")),
|
||||
("FAILED", pgettext_lazy("Current status of a Database Backup",
|
||||
u"Failed")),
|
||||
("NEW", pgettext_lazy("Current status of a Database Backup",
|
||||
u"New")),
|
||||
("SAVING", pgettext_lazy("Current status of a Database Backup",
|
||||
u"Saving")),
|
||||
)
|
||||
|
||||
|
||||
class LaunchLink(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Backup")
|
||||
url = "horizon:project:database_backups:create"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
icon = "camera"
|
||||
|
||||
|
||||
class RestoreLink(tables.LinkAction):
|
||||
name = "restore"
|
||||
verbose_name = _("Restore Backup")
|
||||
url = "horizon:project:databases:launch"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "cloud-upload"
|
||||
|
||||
def allowed(self, request, backup=None):
|
||||
return backup.status == 'COMPLETED'
|
||||
|
||||
def get_link_url(self, datum):
|
||||
url = reverse(self.url)
|
||||
return url + '?backup=%s' % datum.id
|
||||
|
||||
|
||||
class DownloadBackup(tables.LinkAction):
|
||||
name = "download"
|
||||
verbose_name = _("Download Backup")
|
||||
url = 'horizon:project:containers:object_download'
|
||||
classes = ("btn-download",)
|
||||
|
||||
def get_link_url(self, datum):
|
||||
ref = datum.locationRef.split('/')
|
||||
container_name = ref[5]
|
||||
object_path = '/'.join(ref[6:])
|
||||
return reverse(self.url,
|
||||
kwargs={'container_name': container_name,
|
||||
'object_path': object_path})
|
||||
|
||||
def allowed(self, request, datum):
|
||||
return datum.status == 'COMPLETED'
|
||||
|
||||
|
||||
class DeleteBackup(tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Backup",
|
||||
u"Delete Backups",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Backup",
|
||||
u"Deleted Backups",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
api.trove.backup_delete(request, obj_id)
|
||||
|
||||
|
||||
class UpdateRow(tables.Row):
|
||||
ajax = True
|
||||
|
||||
def get_data(self, request, backup_id):
|
||||
backup = api.trove.backup_get(request, backup_id)
|
||||
try:
|
||||
backup.instance = api.trove.instance_get(request,
|
||||
backup.instance_id)
|
||||
except Exception:
|
||||
pass
|
||||
return backup
|
||||
|
||||
|
||||
def db_link(obj):
|
||||
if not hasattr(obj, 'instance'):
|
||||
return
|
||||
if hasattr(obj.instance, 'name'):
|
||||
return reverse(
|
||||
'horizon:project:databases:detail',
|
||||
kwargs={'instance_id': obj.instance_id})
|
||||
|
||||
|
||||
def db_name(obj):
|
||||
if not hasattr(obj, 'instance') or not hasattr(obj.instance, 'name'):
|
||||
return obj.instance_id
|
||||
return obj.instance.name
|
||||
|
||||
|
||||
def get_datastore(obj):
|
||||
if hasattr(obj, "datastore"):
|
||||
return obj.datastore["type"]
|
||||
return _("Not available")
|
||||
|
||||
|
||||
def get_datastore_version(obj):
|
||||
if hasattr(obj, "datastore"):
|
||||
return obj.datastore["version"]
|
||||
return _("Not available")
|
||||
|
||||
|
||||
def is_incremental(obj):
|
||||
return hasattr(obj, 'parent_id') and obj.parent_id is not None
|
||||
|
||||
|
||||
class BackupsTable(tables.DataTable):
|
||||
name = tables.Column("name",
|
||||
link="horizon:project:database_backups:detail",
|
||||
verbose_name=_("Name"))
|
||||
datastore = tables.Column(get_datastore,
|
||||
verbose_name=_("Datastore"))
|
||||
datastore_version = tables.Column(get_datastore_version,
|
||||
verbose_name=_("Datastore Version"))
|
||||
created = tables.Column("created", verbose_name=_("Created"),
|
||||
filters=[filters.parse_isotime])
|
||||
instance = tables.Column(db_name, link=db_link,
|
||||
verbose_name=_("Database"))
|
||||
incremental = tables.Column(is_incremental,
|
||||
verbose_name=_("Incremental"),
|
||||
filters=(d_filters.yesno,
|
||||
d_filters.capfirst))
|
||||
status = tables.Column("status",
|
||||
verbose_name=_("Status"),
|
||||
status=True,
|
||||
status_choices=STATUS_CHOICES,
|
||||
display_choices=STATUS_DISPLAY_CHOICES)
|
||||
|
||||
class Meta(object):
|
||||
name = "backups"
|
||||
verbose_name = _("Backups")
|
||||
status_columns = ["status"]
|
||||
row_class = UpdateRow
|
||||
table_actions = (LaunchLink, DeleteBackup)
|
||||
row_actions = (RestoreLink, DownloadBackup, DeleteBackup)
|
@ -1,4 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<p>{% blocktrans %}Specify the details for the database backup.{% endblocktrans %}</p>
|
||||
<p>{% blocktrans %}You can perform an incremental backup by specifying a parent backup. <strong>However,</strong> not all databases support incremental backups in which case this operation will result in an error.{% endblocktrans %}</p>
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Backup Database" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'horizon/common/_workflow.html' %}
|
||||
{% endblock %}
|
@ -1,74 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n sizeformat %}
|
||||
{% block title %}{% trans "Backup Details" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h3>{% trans "Backup Overview" %}</h3>
|
||||
|
||||
<div class="status row detail">
|
||||
<h4>{% trans "Information" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ backup.name }}</dd>
|
||||
<dt>{% trans "Description" %}</dt>
|
||||
<dd>{{ backup.description|linebreaksbr }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ backup.id }}</dd>
|
||||
{% if backup.datastore %}
|
||||
<dt>{% trans "Datastore" %}</dt>
|
||||
<dd>{{ backup.datastore.type }}</dd>
|
||||
<dt>{% trans "Datastore Version" %}</dt>
|
||||
<dd>{{ backup.datastore.version }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>{{ backup.status|title }}</dd>
|
||||
<dt>{% trans "Backup File Location" %}</dt>
|
||||
<dd>{{ backup.locationRef }}</dd>
|
||||
<dt>{% trans "Initial Volume Size" %}</dt>
|
||||
<dd>{{ backup.size }} {% trans "GB" %}</dd>
|
||||
<dt>{% trans "Created" %}</dt>
|
||||
<dd>{{ backup.created|parse_isotime }}</dd>
|
||||
<dt>{% trans "Updated" %}</dt>
|
||||
<dd>{{ backup.updated|parse_isotime }}</dd>
|
||||
<dt>{% trans "Backup Duration" %}</dt>
|
||||
<dd>{{ backup.duration }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{% if backup.parent %}
|
||||
<div class="status row detail">
|
||||
<h4>{% trans "Incremental Backup" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Parent Backup" %}</dt>
|
||||
<dd>
|
||||
{% url 'horizon:project:database_backups:detail' backup.parent.id as parent_url %}
|
||||
<a href="{{ parent_url }}">{{ backup.parent.name }}</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if instance %}
|
||||
<div class="addresses row detail">
|
||||
<h4>{% trans "Database Info" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ instance.name }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>
|
||||
{% url 'horizon:project:databases:detail' instance.id as instance_url %}
|
||||
<a href="{{ instance_url }}">{{ instance.id }}</a>
|
||||
</dd>
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>{{ instance.status|title }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Database Backups" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
@ -1,183 +0,0 @@
|
||||
# Copyright 2013 Mirantis Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django import http
|
||||
from mox3.mox import IsA # noqa
|
||||
import six
|
||||
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
INDEX_URL = reverse('horizon:project:database_backups:index')
|
||||
BACKUP_URL = reverse('horizon:project:database_backups:create')
|
||||
DETAILS_URL = reverse('horizon:project:database_backups:detail', args=['id'])
|
||||
|
||||
|
||||
class DatabasesBackupsTests(test.TestCase):
|
||||
@test.create_stubs({api.trove: ('backup_list', 'instance_get')})
|
||||
def test_index(self):
|
||||
api.trove.backup_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.database_backups.list())
|
||||
|
||||
api.trove.instance_get(IsA(http.HttpRequest),
|
||||
IsA(str))\
|
||||
.MultipleTimes()\
|
||||
.AndReturn(self.databases.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
|
||||
self.assertTemplateUsed(res, 'project/database_backups/index.html')
|
||||
|
||||
@test.create_stubs({api.trove: ('backup_list',)})
|
||||
def test_index_exception(self):
|
||||
api.trove.backup_list(IsA(http.HttpRequest))\
|
||||
.AndRaise(self.exceptions.trove)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
|
||||
self.assertTemplateUsed(
|
||||
res, 'project/database_backups/index.html')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertMessageCount(res, error=1)
|
||||
|
||||
@test.create_stubs({api.trove: ('instance_list',
|
||||
'backup_list',
|
||||
'backup_create')})
|
||||
def test_launch_backup(self):
|
||||
api.trove.instance_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.databases.list())
|
||||
api.trove.backup_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.database_backups.list())
|
||||
|
||||
database = self.databases.first()
|
||||
backupName = "NewBackup"
|
||||
backupDesc = "Backup Description"
|
||||
|
||||
api.trove.backup_create(
|
||||
IsA(http.HttpRequest),
|
||||
backupName,
|
||||
database.id,
|
||||
backupDesc,
|
||||
"")
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
post = {
|
||||
'name': backupName,
|
||||
'instance': database.id,
|
||||
'description': backupDesc,
|
||||
'parent': ""
|
||||
}
|
||||
res = self.client.post(BACKUP_URL, post)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.trove: ('instance_list', 'backup_list')})
|
||||
def test_launch_backup_exception(self):
|
||||
api.trove.instance_list(IsA(http.HttpRequest))\
|
||||
.AndRaise(self.exceptions.trove)
|
||||
api.trove.backup_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.database_backups.list())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(BACKUP_URL)
|
||||
self.assertMessageCount(res, error=1)
|
||||
self.assertTemplateUsed(res,
|
||||
'project/database_backups/backup.html')
|
||||
|
||||
@test.create_stubs({api.trove: ('instance_list',
|
||||
'backup_list',
|
||||
'backup_create')})
|
||||
def test_launch_backup_incr(self):
|
||||
api.trove.instance_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.databases.list())
|
||||
api.trove.backup_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.database_backups.list())
|
||||
|
||||
database = self.databases.first()
|
||||
backupName = "NewBackup"
|
||||
backupDesc = "Backup Description"
|
||||
backupParent = self.database_backups.first()
|
||||
|
||||
api.trove.backup_create(
|
||||
IsA(http.HttpRequest),
|
||||
backupName,
|
||||
database.id,
|
||||
backupDesc,
|
||||
backupParent.id)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
post = {
|
||||
'name': backupName,
|
||||
'instance': database.id,
|
||||
'description': backupDesc,
|
||||
'parent': backupParent.id,
|
||||
}
|
||||
res = self.client.post(BACKUP_URL, post)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.trove: ('backup_get', 'instance_get')})
|
||||
def test_detail_backup(self):
|
||||
api.trove.backup_get(IsA(http.HttpRequest),
|
||||
IsA(six.text_type))\
|
||||
.AndReturn(self.database_backups.first())
|
||||
|
||||
api.trove.instance_get(IsA(http.HttpRequest),
|
||||
IsA(str))\
|
||||
.AndReturn(self.databases.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(DETAILS_URL)
|
||||
|
||||
self.assertTemplateUsed(res,
|
||||
'project/database_backups/details.html')
|
||||
|
||||
@test.create_stubs({api.trove: ('backup_get',)})
|
||||
def test_detail_backup_notfound(self):
|
||||
api.trove.backup_get(IsA(http.HttpRequest),
|
||||
IsA(six.text_type))\
|
||||
.AndRaise(self.exceptions.trove)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(DETAILS_URL)
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.trove: ('backup_get', 'instance_get')})
|
||||
def test_detail_backup_incr(self):
|
||||
incr_backup = self.database_backups.list()[2]
|
||||
parent_backup = self.database_backups.list()[1]
|
||||
|
||||
api.trove.backup_get(IsA(http.HttpRequest), IsA(six.text_type))\
|
||||
.AndReturn(incr_backup)
|
||||
api.trove.backup_get(IsA(http.HttpRequest), incr_backup.parent_id) \
|
||||
.AndReturn(parent_backup)
|
||||
api.trove.instance_get(IsA(http.HttpRequest), IsA(str))\
|
||||
.AndReturn(self.databases.list()[1])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
url = reverse('horizon:project:database_backups:detail',
|
||||
args=[incr_backup.id])
|
||||
res = self.client.get(url)
|
||||
self.assertTemplateUsed(res, 'project/database_backups/details.html')
|
@ -1,26 +0,0 @@
|
||||
# Copyright 2013 Rackspace Hosting
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf.urls import patterns
|
||||
from django.conf.urls import url
|
||||
|
||||
from openstack_dashboard.contrib.trove.content.database_backups import views
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^create$', views.BackupView.as_view(), name='create'),
|
||||
url(r'^(?P<backup_id>[^/]+)/$', views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
)
|
@ -1,110 +0,0 @@
|
||||
# Copyright 2013 Rackspace Hosting
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Views for displaying database backups.
|
||||
"""
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables as horizon_tables
|
||||
from horizon.utils import filters
|
||||
from horizon import views as horizon_views
|
||||
from horizon import workflows as horizon_workflows
|
||||
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
from openstack_dashboard.contrib.trove.content.database_backups import tables
|
||||
from openstack_dashboard.contrib.trove.content.database_backups \
|
||||
import workflows
|
||||
|
||||
|
||||
class IndexView(horizon_tables.DataTableView):
|
||||
table_class = tables.BackupsTable
|
||||
template_name = 'project/database_backups/index.html'
|
||||
page_title = _("Backups")
|
||||
|
||||
def _get_extra_data(self, backup):
|
||||
"""Apply extra info to the backup."""
|
||||
instance_id = backup.instance_id
|
||||
# TODO(rdopieralski) It's not clear where this attribute is supposed
|
||||
# to come from. At first glance it looks like it will always be {}.
|
||||
if not hasattr(self, '_instances'):
|
||||
self._instances = {}
|
||||
instance = self._instances.get(instance_id)
|
||||
if instance is None:
|
||||
try:
|
||||
instance = api.trove.instance_get(self.request, instance_id)
|
||||
except Exception:
|
||||
instance = _('Not Found')
|
||||
backup.instance = instance
|
||||
return backup
|
||||
|
||||
def get_data(self):
|
||||
# TODO(rmyers) Add pagination support after it is available
|
||||
# https://blueprints.launchpad.net/trove/+spec/paginate-backup-list
|
||||
try:
|
||||
backups = api.trove.backup_list(self.request)
|
||||
backups = map(self._get_extra_data, backups)
|
||||
except Exception:
|
||||
backups = []
|
||||
msg = _('Error getting database backup list.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return backups
|
||||
|
||||
|
||||
class BackupView(horizon_workflows.WorkflowView):
|
||||
workflow_class = workflows.CreateBackup
|
||||
template_name = "project/database_backups/backup.html"
|
||||
page_title = _("Backup Database")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(BackupView, self).get_context_data(**kwargs)
|
||||
context["instance_id"] = kwargs.get("instance_id")
|
||||
self._instance = context['instance_id']
|
||||
return context
|
||||
|
||||
|
||||
class DetailView(horizon_views.APIView):
|
||||
template_name = "project/database_backups/details.html"
|
||||
page_title = _("Backup Details: {{ backup.name }}")
|
||||
|
||||
def get_data(self, request, context, *args, **kwargs):
|
||||
backup_id = kwargs.get("backup_id")
|
||||
try:
|
||||
backup = api.trove.backup_get(request, backup_id)
|
||||
created_at = filters.parse_isotime(backup.created)
|
||||
updated_at = filters.parse_isotime(backup.updated)
|
||||
backup.duration = updated_at - created_at
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:database_backups:index')
|
||||
msg = _('Unable to retrieve details for backup: %s') % backup_id
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
try:
|
||||
if(hasattr(backup, 'parent_id') and backup.parent_id is not None):
|
||||
backup.parent = api.trove.backup_get(request, backup.parent_id)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:database_backups:index')
|
||||
msg = (_('Unable to retrieve details for parent backup: %s')
|
||||
% backup.parent_id)
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
try:
|
||||
instance = api.trove.instance_get(request, backup.instance_id)
|
||||
except Exception:
|
||||
instance = None
|
||||
context['backup'] = backup
|
||||
context['instance'] = instance
|
||||
return context
|
@ -1,3 +0,0 @@
|
||||
# Importing non-modules that are not used explicitly
|
||||
|
||||
from .create_backup import CreateBackup # noqa
|
@ -1,110 +0,0 @@
|
||||
# Copyright 2013 Rackspace Hosting
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import workflows
|
||||
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
from openstack_dashboard.contrib.trove.content.databases \
|
||||
import tables as project_tables
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BackupDetailsAction(workflows.Action):
|
||||
name = forms.CharField(max_length=80, label=_("Name"))
|
||||
instance = forms.ChoiceField(label=_("Database Instance"))
|
||||
description = forms.CharField(max_length=512, label=_("Description"),
|
||||
widget=forms.TextInput(),
|
||||
required=False,
|
||||
help_text=_("Optional Backup Description"))
|
||||
parent = forms.ChoiceField(label=_("Parent Backup"),
|
||||
required=False,
|
||||
help_text=_("Optional parent backup"))
|
||||
|
||||
class Meta(object):
|
||||
name = _("Details")
|
||||
help_text_template = \
|
||||
"project/database_backups/_backup_details_help.html"
|
||||
|
||||
def populate_instance_choices(self, request, context):
|
||||
LOG.info("Obtaining list of instances.")
|
||||
try:
|
||||
instances = api.trove.instance_list(request)
|
||||
except Exception:
|
||||
instances = []
|
||||
msg = _("Unable to list database instances to backup.")
|
||||
exceptions.handle(request, msg)
|
||||
return [(i.id, i.name) for i in instances
|
||||
if i.status in project_tables.ACTIVE_STATES]
|
||||
|
||||
def populate_parent_choices(self, request, context):
|
||||
try:
|
||||
backups = api.trove.backup_list(request)
|
||||
choices = [(b.id, b.name) for b in backups
|
||||
if b.status == 'COMPLETED']
|
||||
except Exception:
|
||||
choices = []
|
||||
msg = _("Unable to list database backups for parent.")
|
||||
exceptions.handle(request, msg)
|
||||
|
||||
if choices:
|
||||
choices.insert(0, ("", _("Select parent backup")))
|
||||
else:
|
||||
choices.insert(0, ("", _("No backups available")))
|
||||
return choices
|
||||
|
||||
|
||||
class SetBackupDetails(workflows.Step):
|
||||
action_class = BackupDetailsAction
|
||||
contributes = ["name", "description", "instance", "parent"]
|
||||
|
||||
|
||||
class CreateBackup(workflows.Workflow):
|
||||
slug = "create_backup"
|
||||
name = _("Backup Database")
|
||||
finalize_button_name = _("Backup")
|
||||
success_message = _('Scheduled backup "%(name)s".')
|
||||
failure_message = _('Unable to launch %(count)s named "%(name)s".')
|
||||
success_url = "horizon:project:database_backups:index"
|
||||
default_steps = [SetBackupDetails]
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(CreateBackup, self).get_initial()
|
||||
initial['instance_id']
|
||||
|
||||
def format_status_message(self, message):
|
||||
name = self.context.get('name', 'unknown instance')
|
||||
return message % {"count": _("instance"), "name": name}
|
||||
|
||||
def handle(self, request, context):
|
||||
try:
|
||||
LOG.info("Creating backup")
|
||||
api.trove.backup_create(request,
|
||||
context['name'],
|
||||
context['instance'],
|
||||
context['description'],
|
||||
context['parent'])
|
||||
return True
|
||||
except Exception:
|
||||
LOG.exception("Exception while creating backup")
|
||||
msg = _('Error creating database backup.')
|
||||
exceptions.handle(request, msg)
|
||||
return False
|
@ -1,375 +0,0 @@
|
||||
# Copyright 2015 HP Software, LLC
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.decorators.debug import sensitive_variables # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from horizon.utils import memoized
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.contrib.trove import api as trove_api
|
||||
from openstack_dashboard.contrib.trove.content.databases import db_capability
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LaunchForm(forms.SelfHandlingForm):
|
||||
name = forms.CharField(label=_("Cluster Name"),
|
||||
max_length=80)
|
||||
datastore = forms.ChoiceField(
|
||||
label=_("Datastore"),
|
||||
help_text=_("Type and version of datastore."),
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'datastore'
|
||||
}))
|
||||
mongodb_flavor = forms.ChoiceField(
|
||||
label=_("Flavor"),
|
||||
help_text=_("Size of instance to launch."),
|
||||
required=False,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'datastore',
|
||||
}))
|
||||
vertica_flavor = forms.ChoiceField(
|
||||
label=_("Flavor"),
|
||||
help_text=_("Size of instance to launch."),
|
||||
required=False,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'datastore',
|
||||
}))
|
||||
network = forms.ChoiceField(
|
||||
label=_("Network"),
|
||||
help_text=_("Network attached to instance."),
|
||||
required=False)
|
||||
volume = forms.IntegerField(
|
||||
label=_("Volume Size"),
|
||||
min_value=0,
|
||||
initial=1,
|
||||
help_text=_("Size of the volume in GB."))
|
||||
root_password = forms.CharField(
|
||||
label=_("Root Password"),
|
||||
required=False,
|
||||
help_text=_("Password for root user."),
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'datastore',
|
||||
}))
|
||||
num_instances_vertica = forms.IntegerField(
|
||||
label=_("Number of Instances"),
|
||||
min_value=3,
|
||||
initial=3,
|
||||
required=False,
|
||||
help_text=_("Number of instances in the cluster. (Read only)"),
|
||||
widget=forms.TextInput(attrs={
|
||||
'readonly': 'readonly',
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'datastore',
|
||||
}))
|
||||
num_shards = forms.IntegerField(
|
||||
label=_("Number of Shards"),
|
||||
min_value=1,
|
||||
initial=1,
|
||||
required=False,
|
||||
help_text=_("Number of shards. (Read only)"),
|
||||
widget=forms.TextInput(attrs={
|
||||
'readonly': 'readonly',
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'datastore',
|
||||
}))
|
||||
num_instances_per_shards = forms.IntegerField(
|
||||
label=_("Instances Per Shard"),
|
||||
initial=3,
|
||||
required=False,
|
||||
help_text=_("Number of instances per shard. (Read only)"),
|
||||
widget=forms.TextInput(attrs={
|
||||
'readonly': 'readonly',
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'datastore',
|
||||
}))
|
||||
|
||||
# (name of field variable, label)
|
||||
mongodb_fields = [
|
||||
('mongodb_flavor', _('Flavor')),
|
||||
('num_shards', _('Number of Shards')),
|
||||
('num_instances_per_shards', _('Instances Per Shard'))
|
||||
]
|
||||
vertica_fields = [
|
||||
('num_instances_vertica', ('Number of Instances')),
|
||||
('vertica_flavor', _('Flavor')),
|
||||
('root_password', _('Root Password')),
|
||||
]
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(LaunchForm, self).__init__(request, *args, **kwargs)
|
||||
|
||||
self.fields['datastore'].choices = self.populate_datastore_choices(
|
||||
request)
|
||||
self.populate_flavor_choices(request)
|
||||
|
||||
self.fields['network'].choices = self.populate_network_choices(
|
||||
request)
|
||||
|
||||
def clean(self):
|
||||
datastore_field_value = self.data.get("datastore", None)
|
||||
if datastore_field_value:
|
||||
datastore = datastore_field_value.split(',')[0]
|
||||
|
||||
if db_capability.is_mongodb_datastore(datastore):
|
||||
if self.data.get("num_shards", 0) < 1:
|
||||
msg = _("The number of shards must be greater than 1.")
|
||||
self._errors["num_shards"] = self.error_class([msg])
|
||||
|
||||
elif db_capability.is_vertica_datastore(datastore):
|
||||
if not self.data.get("vertica_flavor", None):
|
||||
msg = _("The flavor must be specified.")
|
||||
self._errors["vertica_flavor"] = self.error_class([msg])
|
||||
if not self.data.get("root_password", None):
|
||||
msg = _("Password for root user must be specified.")
|
||||
self._errors["root_password"] = self.error_class([msg])
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
@memoized.memoized_method
|
||||
def datastore_flavors(self, request, datastore_name, datastore_version):
|
||||
try:
|
||||
return trove_api.trove.datastore_flavors(
|
||||
request, datastore_name, datastore_version)
|
||||
except Exception:
|
||||
LOG.exception("Exception while obtaining flavors list")
|
||||
self._flavors = []
|
||||
redirect = reverse('horizon:project:database_clusters:index')
|
||||
exceptions.handle(request,
|
||||
_('Unable to obtain flavors.'),
|
||||
redirect=redirect)
|
||||
|
||||
def populate_flavor_choices(self, request):
|
||||
valid_flavor = []
|
||||
for ds in self.datastores(request):
|
||||
# TODO(michayu): until capabilities lands
|
||||
if db_capability.is_mongodb_datastore(ds.name):
|
||||
versions = self.datastore_versions(request, ds.name)
|
||||
for version in versions:
|
||||
if version.name == "inactive":
|
||||
continue
|
||||
valid_flavor = self.datastore_flavors(request, ds.name,
|
||||
versions[0].name)
|
||||
if valid_flavor:
|
||||
self.fields['mongodb_flavor'].choices = sorted(
|
||||
[(f.id, "%s" % f.name) for f in valid_flavor])
|
||||
|
||||
if db_capability.is_vertica_datastore(ds.name):
|
||||
versions = self.datastore_versions(request, ds.name)
|
||||
for version in versions:
|
||||
if version.name == "inactive":
|
||||
continue
|
||||
valid_flavor = self.datastore_flavors(request, ds.name,
|
||||
versions[0].name)
|
||||
if valid_flavor:
|
||||
self.fields['vertica_flavor'].choices = sorted(
|
||||
[(f.id, "%s" % f.name) for f in valid_flavor])
|
||||
|
||||
@memoized.memoized_method
|
||||
def populate_network_choices(self, request):
|
||||
network_list = []
|
||||
try:
|
||||
if api.base.is_service_enabled(request, 'network'):
|
||||
tenant_id = self.request.user.tenant_id
|
||||
networks = api.neutron.network_list_for_tenant(request,
|
||||
tenant_id)
|
||||
network_list = [(network.id, network.name_or_id)
|
||||
for network in networks]
|
||||
else:
|
||||
self.fields['network'].widget = forms.HiddenInput()
|
||||
except exceptions.ServiceCatalogException:
|
||||
network_list = []
|
||||
redirect = reverse('horizon:project:database_clusters:index')
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve networks.'),
|
||||
redirect=redirect)
|
||||
return network_list
|
||||
|
||||
@memoized.memoized_method
|
||||
def datastores(self, request):
|
||||
try:
|
||||
return trove_api.trove.datastore_list(request)
|
||||
except Exception:
|
||||
LOG.exception("Exception while obtaining datastores list")
|
||||
self._datastores = []
|
||||
redirect = reverse('horizon:project:database_clusters:index')
|
||||
exceptions.handle(request,
|
||||
_('Unable to obtain datastores.'),
|
||||
redirect=redirect)
|
||||
|
||||
def filter_cluster_datastores(self, request):
|
||||
datastores = []
|
||||
for ds in self.datastores(request):
|
||||
# TODO(michayu): until capabilities lands
|
||||
if (db_capability.is_vertica_datastore(ds.name)
|
||||
or db_capability.is_mongodb_datastore(ds.name)):
|
||||
datastores.append(ds)
|
||||
return datastores
|
||||
|
||||
@memoized.memoized_method
|
||||
def datastore_versions(self, request, datastore):
|
||||
try:
|
||||
return trove_api.trove.datastore_version_list(request, datastore)
|
||||
except Exception:
|
||||
LOG.exception("Exception while obtaining datastore version list")
|
||||
self._datastore_versions = []
|
||||
redirect = reverse('horizon:project:database_clusters:index')
|
||||
exceptions.handle(request,
|
||||
_('Unable to obtain datastore versions.'),
|
||||
redirect=redirect)
|
||||
|
||||
def populate_datastore_choices(self, request):
|
||||
choices = ()
|
||||
datastores = self.filter_cluster_datastores(request)
|
||||
if datastores is not None:
|
||||
for ds in datastores:
|
||||
versions = self.datastore_versions(request, ds.name)
|
||||
if versions:
|
||||
# only add to choices if datastore has at least one version
|
||||
version_choices = ()
|
||||
for v in versions:
|
||||
if "inactive" in v.name:
|
||||
continue
|
||||
selection_text = ds.name + ' - ' + v.name
|
||||
widget_text = ds.name + '-' + v.name
|
||||
version_choices = (version_choices +
|
||||
((widget_text, selection_text),))
|
||||
self._add_attr_to_optional_fields(ds.name,
|
||||
widget_text)
|
||||
|
||||
choices = choices + version_choices
|
||||
return choices
|
||||
|
||||
def _add_attr_to_optional_fields(self, datastore, selection_text):
|
||||
fields = []
|
||||
if db_capability.is_mongodb_datastore(datastore):
|
||||
fields = self.mongodb_fields
|
||||
elif db_capability.is_vertica_datastore(datastore):
|
||||
fields = self.vertica_fields
|
||||
|
||||
for field in fields:
|
||||
attr_key = 'data-datastore-' + selection_text
|
||||
widget = self.fields[field[0]].widget
|
||||
if attr_key not in widget.attrs:
|
||||
widget.attrs[attr_key] = field[1]
|
||||
|
||||
@sensitive_variables('data')
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
datastore = data['datastore'].split('-')[0]
|
||||
datastore_version = data['datastore'].split('-')[1]
|
||||
|
||||
final_flavor = data['mongodb_flavor']
|
||||
num_instances = data['num_instances_per_shards']
|
||||
root_password = None
|
||||
if db_capability.is_vertica_datastore(datastore):
|
||||
final_flavor = data['vertica_flavor']
|
||||
root_password = data['root_password']
|
||||
num_instances = data['num_instances_vertica']
|
||||
LOG.info("Launching cluster with parameters "
|
||||
"{name=%s, volume=%s, flavor=%s, "
|
||||
"datastore=%s, datastore_version=%s",
|
||||
data['name'], data['volume'], final_flavor,
|
||||
datastore, datastore_version)
|
||||
|
||||
trove_api.trove.cluster_create(request,
|
||||
data['name'],
|
||||
data['volume'],
|
||||
final_flavor,
|
||||
num_instances,
|
||||
datastore=datastore,
|
||||
datastore_version=datastore_version,
|
||||
nics=data['network'],
|
||||
root_password=root_password)
|
||||
messages.success(request,
|
||||
_('Launched cluster "%s"') % data['name'])
|
||||
return True
|
||||
except Exception as e:
|
||||
redirect = reverse("horizon:project:database_clusters:index")
|
||||
exceptions.handle(request,
|
||||
_('Unable to launch cluster. %s') % e.message,
|
||||
redirect=redirect)
|
||||
|
||||
|
||||
class AddShardForm(forms.SelfHandlingForm):
|
||||
name = forms.CharField(
|
||||
label=_("Cluster Name"),
|
||||
max_length=80,
|
||||
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
||||
num_shards = forms.IntegerField(
|
||||
label=_("Number of Shards"),
|
||||
initial=1,
|
||||
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
||||
num_instances = forms.IntegerField(label=_("Instances Per Shard"),
|
||||
initial=3,
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
cluster_id = forms.CharField(required=False,
|
||||
widget=forms.HiddenInput())
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
LOG.info("Adding shard with parameters "
|
||||
"{name=%s, num_shards=%s, num_instances=%s, "
|
||||
"cluster_id=%s}",
|
||||
data['name'],
|
||||
data['num_shards'],
|
||||
data['num_instances'],
|
||||
data['cluster_id'])
|
||||
trove_api.trove.cluster_add_shard(request, data['cluster_id'])
|
||||
|
||||
messages.success(request,
|
||||
_('Added shard to "%s"') % data['name'])
|
||||
except Exception as e:
|
||||
redirect = reverse("horizon:project:database_clusters:index")
|
||||
exceptions.handle(request,
|
||||
_('Unable to add shard. %s') % e.message,
|
||||
redirect=redirect)
|
||||
return True
|
||||
|
||||
|
||||
class ResetPasswordForm(forms.SelfHandlingForm):
|
||||
cluster_id = forms.CharField(widget=forms.HiddenInput())
|
||||
password = forms.CharField(widget=forms.PasswordInput(),
|
||||
label=_("New Password"),
|
||||
required=True,
|
||||
help_text=_("New password for cluster access."))
|
||||
|
||||
@sensitive_variables('data')
|
||||
def handle(self, request, data):
|
||||
password = data.get("password")
|
||||
cluster_id = data.get("cluster_id")
|
||||
try:
|
||||
trove_api.trove.create_cluster_root(request,
|
||||
cluster_id,
|
||||
password)
|
||||
messages.success(request, _('Root password updated for '
|
||||
'cluster "%s"') % cluster_id)
|
||||
except Exception as e:
|
||||
redirect = reverse("horizon:project:database_clusters:index")
|
||||
exceptions.handle(request, _('Unable to reset password. %s') %
|
||||
e.message, redirect=redirect)
|
||||
return True
|
@ -1,26 +0,0 @@
|
||||
# Copyright (c) 2014 eBay Software Foundation
|
||||
# Copyright 2015 HP Software, LLC
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
|
||||
|
||||
class Clusters(horizon.Panel):
|
||||
name = _("Clusters")
|
||||
slug = 'database_clusters'
|
||||
permissions = ('openstack.services.database',
|
||||
'openstack.services.object-store',)
|
@ -1,205 +0,0 @@
|
||||
# Copyright (c) 2014 eBay Software Foundation
|
||||
# Copyright 2015 HP Software, LLC
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core import urlresolvers
|
||||
from django.template.defaultfilters import title # noqa
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import tables
|
||||
from horizon.templatetags import sizeformat
|
||||
from horizon.utils import filters
|
||||
from horizon.utils import memoized
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
from openstack_dashboard.contrib.trove.content.databases import db_capability
|
||||
|
||||
ACTIVE_STATES = ("ACTIVE",)
|
||||
|
||||
|
||||
class DeleteCluster(tables.BatchAction):
|
||||
name = "delete"
|
||||
icon = "remove"
|
||||
classes = ('btn-danger',)
|
||||
help_text = _("Deleted cluster is not recoverable.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Cluster",
|
||||
u"Delete Clusters",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Scheduled deletion of Cluster",
|
||||
u"Scheduled deletion of Clusters",
|
||||
count
|
||||
)
|
||||
|
||||
def action(self, request, obj_id):
|
||||
api.trove.cluster_delete(request, obj_id)
|
||||
|
||||
|
||||
class LaunchLink(tables.LinkAction):
|
||||
name = "launch"
|
||||
verbose_name = _("Launch Cluster")
|
||||
url = "horizon:project:database_clusters:launch"
|
||||
classes = ("btn-launch", "ajax-modal")
|
||||
icon = "cloud-upload"
|
||||
|
||||
|
||||
class AddShard(tables.LinkAction):
|
||||
name = "add_shard"
|
||||
verbose_name = _("Add Shard")
|
||||
url = "horizon:project:database_clusters:add_shard"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
|
||||
def allowed(self, request, cluster=None):
|
||||
if (cluster and cluster.task["name"] == 'NONE' and
|
||||
db_capability.is_mongodb_datastore(cluster.datastore['type'])):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class ResetPassword(tables.LinkAction):
|
||||
name = "reset_password"
|
||||
verbose_name = _("Reset Root Password")
|
||||
url = "horizon:project:database_clusters:reset_password"
|
||||
classes = ("ajax-modal",)
|
||||
|
||||
def allowed(self, request, cluster=None):
|
||||
if (cluster and cluster.task["name"] == 'NONE' and
|
||||
db_capability.is_vertica_datastore(cluster.datastore['type'])):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_link_url(self, datum):
|
||||
cluster_id = self.table.get_object_id(datum)
|
||||
return urlresolvers.reverse(self.url, args=[cluster_id])
|
||||
|
||||
|
||||
class UpdateRow(tables.Row):
|
||||
ajax = True
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_data(self, request, cluster_id):
|
||||
cluster = api.trove.cluster_get(request, cluster_id)
|
||||
try:
|
||||
# TODO(michayu): assumption that cluster is homogeneous
|
||||
flavor_id = cluster.instances[0]['flavor']['id']
|
||||
cluster.full_flavor = api.trove.flavor_get(request, flavor_id)
|
||||
except Exception:
|
||||
pass
|
||||
return cluster
|
||||
|
||||
|
||||
def get_datastore(cluster):
|
||||
return cluster.datastore["type"]
|
||||
|
||||
|
||||
def get_datastore_version(cluster):
|
||||
return cluster.datastore["version"]
|
||||
|
||||
|
||||
def get_size(cluster):
|
||||
if db_capability.is_vertica_datastore(cluster.datastore['type']):
|
||||
return "3"
|
||||
|
||||
if hasattr(cluster, "full_flavor"):
|
||||
size_string = _("%(name)s | %(RAM)s RAM | %(instances)s instances")
|
||||
vals = {'name': cluster.full_flavor.name,
|
||||
'RAM': sizeformat.mbformat(cluster.full_flavor.ram),
|
||||
'instances': len(cluster.instances)}
|
||||
return size_string % vals
|
||||
return _("Not available")
|
||||
|
||||
|
||||
def get_task(cluster):
|
||||
return cluster.task["name"]
|
||||
|
||||
|
||||
class ClustersTable(tables.DataTable):
|
||||
TASK_CHOICES = (
|
||||
("none", True),
|
||||
)
|
||||
name = tables.Column("name",
|
||||
link=("horizon:project:database_clusters:detail"),
|
||||
verbose_name=_("Cluster Name"))
|
||||
datastore = tables.Column(get_datastore,
|
||||
verbose_name=_("Datastore"))
|
||||
datastore_version = tables.Column(get_datastore_version,
|
||||
verbose_name=_("Datastore Version"))
|
||||
size = tables.Column(get_size,
|
||||
verbose_name=_("Cluster Size"),
|
||||
attrs={'data-type': 'size'})
|
||||
task = tables.Column(get_task,
|
||||
filters=(title, filters.replace_underscores),
|
||||
verbose_name=_("Current Task"),
|
||||
status=True,
|
||||
status_choices=TASK_CHOICES)
|
||||
|
||||
class Meta(object):
|
||||
name = "clusters"
|
||||
verbose_name = _("Clusters")
|
||||
status_columns = ["task"]
|
||||
row_class = UpdateRow
|
||||
table_actions = (LaunchLink, DeleteCluster)
|
||||
row_actions = (AddShard, ResetPassword, DeleteCluster)
|
||||
|
||||
|
||||
def get_instance_size(instance):
|
||||
if hasattr(instance, "full_flavor"):
|
||||
size_string = _("%(name)s | %(RAM)s RAM")
|
||||
vals = {'name': instance.full_flavor.name,
|
||||
'RAM': sizeformat.mbformat(instance.full_flavor.ram)}
|
||||
return size_string % vals
|
||||
return _("Not available")
|
||||
|
||||
|
||||
def get_instance_type(instance):
|
||||
if hasattr(instance, "type"):
|
||||
return instance.type
|
||||
return _("Not available")
|
||||
|
||||
|
||||
def get_host(instance):
|
||||
if hasattr(instance, "hostname"):
|
||||
return instance.hostname
|
||||
elif hasattr(instance, "ip") and instance.ip:
|
||||
return instance.ip[0]
|
||||
return _("Not Assigned")
|
||||
|
||||
|
||||
class InstancesTable(tables.DataTable):
|
||||
name = tables.Column("name",
|
||||
verbose_name=_("Name"))
|
||||
type = tables.Column(get_instance_type,
|
||||
verbose_name=_("Type"))
|
||||
host = tables.Column(get_host,
|
||||
verbose_name=_("Host"))
|
||||
size = tables.Column(get_instance_size,
|
||||
verbose_name=_("Size"),
|
||||
attrs={'data-type': 'size'})
|
||||
status = tables.Column("status",
|
||||
filters=(title, filters.replace_underscores),
|
||||
verbose_name=_("Status"))
|
||||
|
||||
class Meta(object):
|
||||
name = "instances"
|
||||
verbose_name = _("Instances")
|
@ -1,84 +0,0 @@
|
||||
# Copyright (c) 2014 eBay Software Foundation
|
||||
# Copyright 2015 HP Software, LLC
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django import template
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
from openstack_dashboard.contrib.trove.content.database_clusters import tables
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
|
||||
def get_context_data(self, request):
|
||||
return {"cluster": self.tab_group.kwargs['cluster']}
|
||||
|
||||
def get_template_name(self, request):
|
||||
cluster = self.tab_group.kwargs['cluster']
|
||||
template_file = ('project/database_clusters/_detail_overview_%s.html'
|
||||
% cluster.datastore['type'])
|
||||
try:
|
||||
template.loader.get_template(template_file)
|
||||
return template_file
|
||||
except template.TemplateDoesNotExist:
|
||||
# This datastore type does not have a template file
|
||||
# Just use the base template file
|
||||
return ('project/database_clusters/_detail_overview.html')
|
||||
|
||||
|
||||
class InstancesTab(tabs.TableTab):
|
||||
table_classes = (tables.InstancesTable,)
|
||||
name = _("Instances")
|
||||
slug = "instances_tab"
|
||||
cluster = None
|
||||
template_name = "horizon/common/_detail_table.html"
|
||||
preload = True
|
||||
|
||||
def get_instances_data(self):
|
||||
cluster = self.tab_group.kwargs['cluster']
|
||||
data = []
|
||||
try:
|
||||
instances = api.trove.cluster_get(self.request,
|
||||
cluster.id).instances
|
||||
for instance in instances:
|
||||
instance_info = api.trove.instance_get(self.request,
|
||||
instance['id'])
|
||||
flavor_id = instance_info.flavor['id']
|
||||
instance_info.full_flavor = api.trove.flavor_get(self.request,
|
||||
flavor_id)
|
||||
if "type" in instance:
|
||||
instance_info.type = instance["type"]
|
||||
if "ip" in instance:
|
||||
instance_info.ip = instance["ip"]
|
||||
if "hostname" in instance:
|
||||
instance_info.hostname = instance["hostname"]
|
||||
|
||||
data.append(instance_info)
|
||||
except Exception:
|
||||
msg = _('Unable to get instances data.')
|
||||
exceptions.handle(self.request, msg)
|
||||
data = []
|
||||
return data
|
||||
|
||||
|
||||
class ClusterDetailTabs(tabs.TabGroup):
|
||||
slug = "cluster_details"
|
||||
tabs = (OverviewTab, InstancesTab)
|
||||
sticky = True
|
@ -1,25 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}add_shard_form{% endblock %}
|
||||
{% block form_action %}{% url "horizon:project:database_clusters:add_shard" cluster_id %}{% endblock %}
|
||||
|
||||
{% block modal_id %}add_shard_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Add Shard" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<p>{% blocktrans %}Specify the details for adding additional shards.{% endblocktrans %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Add Shard" %}" />
|
||||
<a href="{% url "horizon:project:database_clusters:index" %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -1,23 +0,0 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ cluster.name }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ cluster.id }}</dd>
|
||||
<dt>{% trans "Datastore" %}</dt>
|
||||
<dd>{{ cluster.datastore.type }}</dd>
|
||||
<dt>{% trans "Datastore Version" %}</dt>
|
||||
<dd>{{ cluster.datastore.version }}</dd>
|
||||
<dt>{% trans "Current Task" %}</dt>
|
||||
<dd>{{ cluster.task.name|title }}</dd>
|
||||
<dt>{% trans "RAM" %}</dt>
|
||||
<dd>{{ cluster.full_flavor.ram|mbformat }}</dd>
|
||||
<dt>{% trans "Number of Instances" %}</dt>
|
||||
<dd>{{ cluster.num_instances }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{% block connection_info %}
|
||||
{% endblock %}
|
@ -1,25 +0,0 @@
|
||||
{% extends "project/database_clusters/_detail_overview.html" %}
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
{% block connection_info %}
|
||||
<h4>{% trans "Connection Information" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
{% with cluster.ip.0 as ip %}
|
||||
<dt>{% trans "Host" %}</dt>
|
||||
<dd>
|
||||
{% if not ip %}
|
||||
{% trans "Not Assigned" %}
|
||||
</dd>
|
||||
{% else %}
|
||||
{{ ip }}
|
||||
</dd>
|
||||
<dt>{% trans "Database Port" %}</dt>
|
||||
<dd>27017</dd>
|
||||
<dt>{% trans "Connection Examples" %}</dt>
|
||||
<dd>mongo --host {{ ip }}</dd>
|
||||
<dd>mongodb://[{% trans "USERNAME" %}:{% trans "PASSWORD" %}@]{{ ip }}:27017/{% trans "DATABASE" %}</dd>
|
||||
{% endif %} <!-- ends else block -->
|
||||
{% endwith %}
|
||||
</dl>
|
||||
{% endblock %}
|
@ -1,25 +0,0 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ cluster.name }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ cluster.id }}</dd>
|
||||
<dt>{% trans "Datastore" %}</dt>
|
||||
<dd>{{ cluster.datastore.type }}</dd>
|
||||
<dt>{% trans "Datastore Version" %}</dt>
|
||||
<dd>{{ cluster.datastore.version }}</dd>
|
||||
<dt>{% trans "Current Task" %}</dt>
|
||||
<dd>{{ cluster.task.name|title }}</dd>
|
||||
<dt>{% trans "RAM" %}</dt>
|
||||
<dd>{{ cluster.full_flavor.ram|mbformat }}</dd>
|
||||
<dt>{% trans "Number of Instances" %}</dt>
|
||||
<dd>{{ cluster.num_instances }}</dd>
|
||||
<dt>{% trans "Management Console" %}</dt>
|
||||
<dd><a href="{{ cluster.mgmt_url }}" target="_blank">{{ cluster.mgmt_url }}</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{% block connection_info %}
|
||||
{% endblock %}
|
@ -1,22 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}launch_form{% endblock %}
|
||||
{% block form_action %}{% url "horizon:project:database_clusters:launch" %}{% endblock %}
|
||||
|
||||
{% block modal_id %}launch_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Launch Cluster" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="center">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Launch" %}" />
|
||||
<a href="{% url "horizon:project:database_clusters:index" %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -1,25 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}reset_password_form{% endblock %}
|
||||
{% block form_action %}{% url "horizon:project:database_clusters:reset_password" cluster_id %}{% endblock %}
|
||||
|
||||
{% block modal_id %}reset_password_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Reset Root Password" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<p>{% blocktrans %}Specify the new root password for vertica cluster.{% endblocktrans %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Reset Root Password" %}" />
|
||||
<a href="{% url "horizon:project:database_clusters:index" %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Add Shard" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "project/database_clusters/_add_shard.html" %}
|
||||
{% endblock %}
|
@ -1,11 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Clusters" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Clusters") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
@ -1,11 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Launch Cluster" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Launch Cluster") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/database_clusters/_launch.html' %}
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Reset Root Password" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "project/database_clusters/_reset_password.html" %}
|
||||
{% endblock %}
|
@ -1,295 +0,0 @@
|
||||
# Copyright (c) 2014 eBay Software Foundation
|
||||
# Copyright 2015 HP Software, LLC
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django import http
|
||||
|
||||
from mox3.mox import IsA # noqa
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.contrib.trove import api as trove_api
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
from troveclient import common
|
||||
|
||||
|
||||
INDEX_URL = reverse('horizon:project:database_clusters:index')
|
||||
LAUNCH_URL = reverse('horizon:project:database_clusters:launch')
|
||||
DETAILS_URL = reverse('horizon:project:database_clusters:detail', args=['id'])
|
||||
ADD_SHARD_VIEWNAME = 'horizon:project:database_clusters:add_shard'
|
||||
RESET_PASSWORD_VIEWNAME = 'horizon:project:database_clusters:reset_password'
|
||||
|
||||
|
||||
class ClustersTests(test.TestCase):
|
||||
@test.create_stubs({trove_api.trove: ('cluster_list',
|
||||
'flavor_list')})
|
||||
def test_index(self):
|
||||
clusters = common.Paginated(self.trove_clusters.list())
|
||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
||||
.AndReturn(clusters)
|
||||
trove_api.trove.flavor_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.flavors.list())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
||||
|
||||
@test.create_stubs({trove_api.trove: ('cluster_list',
|
||||
'flavor_list')})
|
||||
def test_index_flavor_exception(self):
|
||||
clusters = common.Paginated(self.trove_clusters.list())
|
||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
||||
.AndReturn(clusters)
|
||||
trove_api.trove.flavor_list(IsA(http.HttpRequest))\
|
||||
.AndRaise(self.exceptions.trove)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
||||
self.assertMessageCount(res, error=1)
|
||||
|
||||
@test.create_stubs({trove_api.trove: ('cluster_list',)})
|
||||
def test_index_list_exception(self):
|
||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
||||
.AndRaise(self.exceptions.trove)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
||||
self.assertMessageCount(res, error=1)
|
||||
|
||||
@test.create_stubs({trove_api.trove: ('cluster_list',
|
||||
'flavor_list')})
|
||||
def test_index_pagination(self):
|
||||
clusters = self.trove_clusters.list()
|
||||
last_record = clusters[0]
|
||||
clusters = common.Paginated(clusters, next_marker="foo")
|
||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
||||
.AndReturn(clusters)
|
||||
trove_api.trove.flavor_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.flavors.list())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
||||
self.assertContains(
|
||||
res, 'marker=' + last_record.id)
|
||||
|
||||
@test.create_stubs({trove_api.trove: ('cluster_list',
|
||||
'flavor_list')})
|
||||
def test_index_flavor_list_exception(self):
|
||||
clusters = common.Paginated(self.trove_clusters.list())
|
||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
||||
.AndReturn(clusters)
|
||||
trove_api.trove.flavor_list(IsA(http.HttpRequest))\
|
||||
.AndRaise(self.exceptions.trove)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
|
||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
||||
self.assertMessageCount(res, error=1)
|
||||
|
||||
@test.create_stubs({trove_api.trove: ('datastore_flavors',
|
||||
'datastore_list',
|
||||
'datastore_version_list'),
|
||||
api.base: ['is_service_enabled']})
|
||||
def test_launch_cluster(self):
|
||||
api.base.is_service_enabled(IsA(http.HttpRequest), 'network')\
|
||||
.AndReturn(False)
|
||||
trove_api.trove.datastore_flavors(IsA(http.HttpRequest),
|
||||
'mongodb', '2.6')\
|
||||
.AndReturn(self.flavors.list())
|
||||
trove_api.trove.datastore_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.datastores.list())
|
||||
trove_api.trove.datastore_version_list(IsA(http.HttpRequest),
|
||||
IsA(str))\
|
||||
.AndReturn(self.datastore_versions.list())
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(LAUNCH_URL)
|
||||
self.assertTemplateUsed(res, 'project/database_clusters/launch.html')
|
||||
|
||||
@test.create_stubs({trove_api.trove: ['datastore_flavors',
|
||||
'cluster_create',
|
||||
'datastore_list',
|
||||
'datastore_version_list'],
|
||||
api.base: ['is_service_enabled']})
|
||||
def test_create_simple_cluster(self):
|
||||
api.base.is_service_enabled(IsA(http.HttpRequest), 'network')\
|
||||
.AndReturn(False)
|
||||
trove_api.trove.datastore_flavors(IsA(http.HttpRequest),
|
||||
'mongodb', '2.6')\
|
||||
.AndReturn(self.flavors.list())
|
||||
trove_api.trove.datastore_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.datastores.list())
|
||||
trove_api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str))\
|
||||
.AndReturn(self.datastore_versions.list())
|
||||
|
||||
cluster_name = u'MyCluster'
|
||||
cluster_volume = 1
|
||||
cluster_flavor = u'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||
cluster_instances = 3
|
||||
cluster_datastore = u'mongodb'
|
||||
cluster_datastore_version = u'2.6'
|
||||
cluster_network = u''
|
||||
trove_api.trove.cluster_create(
|
||||
IsA(http.HttpRequest),
|
||||
cluster_name,
|
||||
cluster_volume,
|
||||
cluster_flavor,
|
||||
cluster_instances,
|
||||
datastore=cluster_datastore,
|
||||
datastore_version=cluster_datastore_version,
|
||||
nics=cluster_network,
|
||||
root_password=None).AndReturn(self.trove_clusters.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
post = {
|
||||
'name': cluster_name,
|
||||
'volume': cluster_volume,
|
||||
'num_instances': cluster_instances,
|
||||
'num_shards': 1,
|
||||
'num_instances_per_shards': cluster_instances,
|
||||
'datastore': cluster_datastore + u'-' + cluster_datastore_version,
|
||||
'mongodb_flavor': cluster_flavor,
|
||||
'network': cluster_network
|
||||
}
|
||||
|
||||
res = self.client.post(LAUNCH_URL, post)
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertMessageCount(success=1)
|
||||
|
||||
@test.create_stubs({trove_api.trove: ['datastore_flavors',
|
||||
'cluster_create',
|
||||
'datastore_list',
|
||||
'datastore_version_list'],
|
||||
api.neutron: ['network_list_for_tenant'],
|
||||
api.base: ['is_service_enabled']})
|
||||
def test_create_simple_cluster_neutron(self):
|
||||
api.base.is_service_enabled(IsA(http.HttpRequest), 'network')\
|
||||
.AndReturn(True)
|
||||
api.neutron.network_list_for_tenant(IsA(http.HttpRequest), '1')\
|
||||
.AndReturn(self.networks.list())
|
||||
trove_api.trove.datastore_flavors(IsA(http.HttpRequest),
|
||||
'mongodb', '2.6')\
|
||||
.AndReturn(self.flavors.list())
|
||||
trove_api.trove.datastore_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.datastores.list())
|
||||
trove_api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str))\
|
||||
.AndReturn(self.datastore_versions.list())
|
||||
|
||||
cluster_name = u'MyCluster'
|
||||
cluster_volume = 1
|
||||
cluster_flavor = u'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||
cluster_instances = 3
|
||||
cluster_datastore = u'mongodb'
|
||||
cluster_datastore_version = u'2.6'
|
||||
cluster_network = u'82288d84-e0a5-42ac-95be-e6af08727e42'
|
||||
trove_api.trove.cluster_create(
|
||||
IsA(http.HttpRequest),
|
||||
cluster_name,
|
||||
cluster_volume,
|
||||
cluster_flavor,
|
||||
cluster_instances,
|
||||
datastore=cluster_datastore,
|
||||
datastore_version=cluster_datastore_version,
|
||||
nics=cluster_network,
|
||||
root_password=None).AndReturn(self.trove_clusters.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
post = {
|
||||
'name': cluster_name,
|
||||
'volume': cluster_volume,
|
||||
'num_instances': cluster_instances,
|
||||
'num_shards': 1,
|
||||
'num_instances_per_shards': cluster_instances,
|
||||
'datastore': cluster_datastore + u'-' + cluster_datastore_version,
|
||||
'mongodb_flavor': cluster_flavor,
|
||||
'network': cluster_network
|
||||
}
|
||||
|
||||
res = self.client.post(LAUNCH_URL, post)
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertMessageCount(success=1)
|
||||
|
||||
@test.create_stubs({trove_api.trove: ['datastore_flavors',
|
||||
'cluster_create',
|
||||
'datastore_list',
|
||||
'datastore_version_list'],
|
||||
api.neutron: ['network_list_for_tenant']})
|
||||
def test_create_simple_cluster_exception(self):
|
||||
api.neutron.network_list_for_tenant(IsA(http.HttpRequest), '1')\
|
||||
.AndReturn(self.networks.list())
|
||||
trove_api.trove.datastore_flavors(IsA(http.HttpRequest),
|
||||
'mongodb', '2.6')\
|
||||
.AndReturn(self.flavors.list())
|
||||
trove_api.trove.datastore_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.datastores.list())
|
||||
trove_api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str))\
|
||||
.AndReturn(self.datastore_versions.list())
|
||||
|
||||
cluster_name = u'MyCluster'
|
||||
cluster_volume = 1
|
||||
cluster_flavor = u'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||
cluster_instances = 3
|
||||
cluster_datastore = u'mongodb'
|
||||
cluster_datastore_version = u'2.6'
|
||||
cluster_network = u'82288d84-e0a5-42ac-95be-e6af08727e42'
|
||||
trove_api.trove.cluster_create(
|
||||
IsA(http.HttpRequest),
|
||||
cluster_name,
|
||||
cluster_volume,
|
||||
cluster_flavor,
|
||||
cluster_instances,
|
||||
datastore=cluster_datastore,
|
||||
datastore_version=cluster_datastore_version,
|
||||
nics=cluster_network,
|
||||
root_password=None).AndReturn(self.trove_clusters.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
post = {
|
||||
'name': cluster_name,
|
||||
'volume': cluster_volume,
|
||||
'num_instances': cluster_instances,
|
||||
'num_shards': 1,
|
||||
'num_instances_per_shards': cluster_instances,
|
||||
'datastore': cluster_datastore + u'-' + cluster_datastore_version,
|
||||
'mongodb_flavor': cluster_flavor,
|
||||
'network': cluster_network
|
||||
}
|
||||
|
||||
res = self.client.post(LAUNCH_URL, post)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({trove_api.trove: ('cluster_get',
|
||||
'instance_get',
|
||||
'flavor_get',)})
|
||||
def test_details(self):
|
||||
cluster = self.trove_clusters.first()
|
||||
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id)\
|
||||
.MultipleTimes().AndReturn(cluster)
|
||||
trove_api.trove.instance_get(IsA(http.HttpRequest), IsA(str))\
|
||||
.MultipleTimes().AndReturn(self.databases.first())
|
||||
trove_api.trove.flavor_get(IsA(http.HttpRequest), IsA(str))\
|
||||
.MultipleTimes().AndReturn(self.flavors.first())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
details_url = reverse('horizon:project:database_clusters:detail',
|
||||
args=[cluster.id])
|
||||
res = self.client.get(details_url)
|
||||
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
|
||||
self.assertContains(res, cluster.ip[0])
|
@ -1,34 +0,0 @@
|
||||
# Copyright (c) 2014 eBay Software Foundation
|
||||
# Copyright 2015 HP Software, LLC
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf.urls import patterns # noqa
|
||||
from django.conf.urls import url # noqa
|
||||
|
||||
from openstack_dashboard.contrib.trove.content.database_clusters import views
|
||||
|
||||
CLUSTERS = r'^(?P<cluster_id>[^/]+)/%s$'
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^launch$', views.LaunchClusterView.as_view(), name='launch'),
|
||||
url(r'^(?P<cluster_id>[^/]+)/$', views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
url(CLUSTERS % 'add_shard', views.AddShardView.as_view(),
|
||||
name='add_shard'),
|
||||
url(CLUSTERS % 'reset_password', views.ResetPasswordView.as_view(),
|
||||
name='reset_password'),
|
||||
)
|
@ -1,215 +0,0 @@
|
||||
# Copyright (c) 2014 eBay Software Foundation
|
||||
# Copyright 2015 HP Software, LLC
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Views for managing database clusters.
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import six
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms as horizon_forms
|
||||
from horizon import tables as horizon_tables
|
||||
from horizon import tabs as horizon_tabs
|
||||
from horizon.utils import memoized
|
||||
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
from openstack_dashboard.contrib.trove.content.database_clusters import forms
|
||||
from openstack_dashboard.contrib.trove.content.database_clusters import tables
|
||||
from openstack_dashboard.contrib.trove.content.database_clusters import tabs
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexView(horizon_tables.DataTableView):
|
||||
table_class = tables.ClustersTable
|
||||
template_name = 'project/database_clusters/index.html'
|
||||
|
||||
def has_more_data(self, table):
|
||||
return self._more
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_flavors(self):
|
||||
try:
|
||||
flavors = api.trove.flavor_list(self.request)
|
||||
except Exception:
|
||||
flavors = []
|
||||
msg = _('Unable to retrieve database size information.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return OrderedDict((six.text_type(flavor.id), flavor)
|
||||
for flavor in flavors)
|
||||
|
||||
def _extra_data(self, cluster):
|
||||
try:
|
||||
cluster_flavor = cluster.instances[0]["flavor"]["id"]
|
||||
flavors = self.get_flavors()
|
||||
flavor = flavors.get(cluster_flavor)
|
||||
if flavor is not None:
|
||||
cluster.full_flavor = flavor
|
||||
except Exception:
|
||||
# ignore any errors and just return cluster unaltered
|
||||
pass
|
||||
return cluster
|
||||
|
||||
def get_data(self):
|
||||
marker = self.request.GET.get(
|
||||
tables.ClustersTable._meta.pagination_param)
|
||||
# Gather our clusters
|
||||
try:
|
||||
clusters = api.trove.cluster_list(self.request, marker=marker)
|
||||
self._more = clusters.next or False
|
||||
except Exception:
|
||||
self._more = False
|
||||
clusters = []
|
||||
msg = _('Unable to retrieve database clusters.')
|
||||
exceptions.handle(self.request, msg)
|
||||
|
||||
map(self._extra_data, clusters)
|
||||
|
||||
return clusters
|
||||
|
||||
|
||||
class LaunchClusterView(horizon_forms.ModalFormView):
|
||||
form_class = forms.LaunchForm
|
||||
template_name = 'project/database_clusters/launch.html'
|
||||
success_url = reverse_lazy('horizon:project:database_clusters:index')
|
||||
|
||||
|
||||
class DetailView(horizon_tabs.TabbedTableView):
|
||||
tab_group_class = tabs.ClusterDetailTabs
|
||||
template_name = 'horizon/common/_detail.html'
|
||||
page_title = "{{ cluster.name|default:cluster.id }}"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
context["url"] = reverse('horizon:project:database_clusters:index')
|
||||
context["cluster"] = self.get_data()
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_data(self):
|
||||
try:
|
||||
cluster_id = self.kwargs['cluster_id']
|
||||
cluster = api.trove.cluster_get(self.request, cluster_id)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:database_clusters:index')
|
||||
msg = _('Unable to retrieve details '
|
||||
'for database cluster: %s') % cluster_id
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
try:
|
||||
cluster.full_flavor = api.trove.flavor_get(
|
||||
self.request, cluster.instances[0]["flavor"]["id"])
|
||||
except Exception:
|
||||
LOG.error('Unable to retrieve flavor details'
|
||||
' for database cluster: %s' % cluster_id)
|
||||
cluster.num_instances = len(cluster.instances)
|
||||
|
||||
# Todo(saurabhs) Set mgmt_url to dispaly Mgmt Console URL on
|
||||
# cluster details page
|
||||
# for instance in cluster.instances:
|
||||
# if instance['type'] == "master":
|
||||
# cluster.mgmt_url = "https://%s:5450/webui" % instance['ip'][0]
|
||||
|
||||
return cluster
|
||||
|
||||
def get_tabs(self, request, *args, **kwargs):
|
||||
cluster = self.get_data()
|
||||
return self.tab_group_class(request, cluster=cluster, **kwargs)
|
||||
|
||||
|
||||
class AddShardView(horizon_forms.ModalFormView):
|
||||
form_class = forms.AddShardForm
|
||||
template_name = 'project/database_clusters/add_shard.html'
|
||||
success_url = reverse_lazy('horizon:project:database_clusters:index')
|
||||
page_title = _("Add Shard")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddShardView, self).get_context_data(**kwargs)
|
||||
context["cluster_id"] = self.kwargs['cluster_id']
|
||||
return context
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
cluster_id = self.kwargs['cluster_id']
|
||||
try:
|
||||
self._object = api.trove.cluster_get(self.request, cluster_id)
|
||||
# TODO(michayu): assumption that cluster is homogeneous
|
||||
flavor_id = self._object.instances[0]['flavor']['id']
|
||||
flavors = self.get_flavors()
|
||||
if flavor_id in flavors:
|
||||
self._object.flavor_name = flavors[flavor_id].name
|
||||
else:
|
||||
flavor = api.trove.flavor_get(self.request, flavor_id)
|
||||
self._object.flavor_name = flavor.name
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:database_clusters:index")
|
||||
msg = _('Unable to retrieve cluster details.')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_flavors(self, *args, **kwargs):
|
||||
if not hasattr(self, "_flavors"):
|
||||
try:
|
||||
flavors = api.trove.flavor_list(self.request)
|
||||
self._flavors = OrderedDict([(str(flavor.id), flavor)
|
||||
for flavor in flavors])
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:database_clusters:index")
|
||||
exceptions.handle(
|
||||
self.request,
|
||||
_('Unable to retrieve flavors.'), redirect=redirect)
|
||||
return self._flavors
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(AddShardView, self).get_initial()
|
||||
_object = self.get_object()
|
||||
if _object:
|
||||
initial.update(
|
||||
{'cluster_id': self.kwargs['cluster_id'],
|
||||
'name': getattr(_object, 'name', None)})
|
||||
return initial
|
||||
|
||||
|
||||
class ResetPasswordView(horizon_forms.ModalFormView):
|
||||
form_class = forms.ResetPasswordForm
|
||||
template_name = 'project/database_clusters/reset_password.html'
|
||||
success_url = reverse_lazy('horizon:project:database_clusters:index')
|
||||
page_title = _("Reset Root Password")
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_object(self, *args, **kwargs):
|
||||
cluster_id = self.kwargs['cluster_id']
|
||||
try:
|
||||
return api.trove.cluster_get(self.request, cluster_id)
|
||||
except Exception:
|
||||
msg = _('Unable to retrieve cluster details.')
|
||||
redirect = reverse('horizon:project:database_clusters:index')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ResetPasswordView, self).get_context_data(**kwargs)
|
||||
context['cluster_id'] = self.kwargs['cluster_id']
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
return {'cluster_id': self.kwargs['cluster_id']}
|
@ -1,25 +0,0 @@
|
||||
# Copyright 2015 Tesora Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
MONGODB = "mongodb"
|
||||
VERTICA = "vertica"
|
||||
|
||||
|
||||
def is_mongodb_datastore(datastore):
|
||||
return (datastore is not None) and (MONGODB in datastore.lower())
|
||||
|
||||
|
||||
def is_vertica_datastore(datastore):
|
||||
return (datastore is not None) and (VERTICA in datastore.lower())
|
@ -1,93 +0,0 @@
|
||||
# Copyright 2014 Tesora Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.forms import ValidationError # noqa
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
|
||||
|
||||
class ResizeVolumeForm(forms.SelfHandlingForm):
|
||||
instance_id = forms.CharField(widget=forms.HiddenInput())
|
||||
orig_size = forms.IntegerField(
|
||||
label=_("Current Size (GB)"),
|
||||
widget=forms.TextInput(attrs={'readonly': 'readonly'}),
|
||||
required=False,
|
||||
)
|
||||
new_size = forms.IntegerField(label=_("New Size (GB)"))
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(ResizeVolumeForm, self).clean()
|
||||
new_size = cleaned_data.get('new_size')
|
||||
if new_size <= self.initial['orig_size']:
|
||||
raise ValidationError(
|
||||
_("New size for volume must be greater than current size."))
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
instance = data.get('instance_id')
|
||||
try:
|
||||
api.trove.instance_resize_volume(request,
|
||||
instance,
|
||||
data['new_size'])
|
||||
|
||||
messages.success(request, _('Resizing volume "%s"') % instance)
|
||||
except Exception as e:
|
||||
redirect = reverse("horizon:project:databases:index")
|
||||
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)
|
||||
|
||||
old_flavor_id = kwargs.get('initial', {}).get('old_flavor_id')
|
||||
choices = kwargs.get('initial', {}).get('flavors')
|
||||
# Remove current flavor from the list of flavor choices
|
||||
choices = [(flavor_id, name) for (flavor_id, name) in choices
|
||||
if flavor_id != old_flavor_id]
|
||||
if choices:
|
||||
choices.insert(0, ("", _("Select a new flavor")))
|
||||
else:
|
||||
choices.insert(0, ("", _("No flavors available")))
|
||||
self.fields['new_flavor'].choices = choices
|
||||
|
||||
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
|
@ -1,23 +0,0 @@
|
||||
# Copyright 2013 Rackspace Hosting
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
|
||||
|
||||
class Databases(horizon.Panel):
|
||||
name = _("Instances")
|
||||
slug = 'databases'
|
||||
permissions = ('openstack.services.database',)
|
@ -1,410 +0,0 @@
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core import urlresolvers
|
||||
from django.template import defaultfilters as d_filters
|
||||
from django.utils.translation import pgettext_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
from horizon.templatetags import sizeformat
|
||||
from horizon.utils import filters
|
||||
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
from openstack_dashboard.contrib.trove.content.database_backups \
|
||||
import tables as backup_tables
|
||||
|
||||
|
||||
ACTIVE_STATES = ("ACTIVE",)
|
||||
|
||||
|
||||
class DeleteInstance(tables.BatchAction):
|
||||
help_text = _("Deleted instances are not recoverable.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Instance",
|
||||
u"Delete Instances",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Scheduled deletion of Instance",
|
||||
u"Scheduled deletion of Instances",
|
||||
count
|
||||
)
|
||||
|
||||
name = "delete"
|
||||
classes = ("btn-danger", )
|
||||
icon = "remove"
|
||||
|
||||
def action(self, request, obj_id):
|
||||
api.trove.instance_delete(request, obj_id)
|
||||
|
||||
|
||||
class RestartInstance(tables.BatchAction):
|
||||
help_text = _("Restarted instances will lose any data not"
|
||||
" saved in persistent storage.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Restart Instance",
|
||||
u"Restart Instances",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Restarted Instance",
|
||||
u"Restarted Instances",
|
||||
count
|
||||
)
|
||||
|
||||
name = "restart"
|
||||
classes = ('btn-danger', 'btn-reboot')
|
||||
|
||||
def allowed(self, request, instance=None):
|
||||
return ((instance.status in ACTIVE_STATES
|
||||
or instance.status == 'SHUTDOWN'))
|
||||
|
||||
def action(self, request, obj_id):
|
||||
api.trove.instance_restart(request, obj_id)
|
||||
|
||||
|
||||
class DetachReplica(tables.BatchAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Detach Replica",
|
||||
u"Detach Replicas",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Replica Detached",
|
||||
u"Replicas Detached",
|
||||
count
|
||||
)
|
||||
|
||||
name = "detach_replica"
|
||||
classes = ('btn-danger', 'btn-detach-replica')
|
||||
|
||||
def allowed(self, request, instance=None):
|
||||
return (instance.status in ACTIVE_STATES
|
||||
and hasattr(instance, 'replica_of'))
|
||||
|
||||
def action(self, request, obj_id):
|
||||
api.trove.instance_detach_replica(request, obj_id)
|
||||
|
||||
|
||||
class DeleteUser(tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete User",
|
||||
u"Delete Users",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted User",
|
||||
u"Deleted Users",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
datum = self.table.get_object_by_id(obj_id)
|
||||
try:
|
||||
api.trove.user_delete(request, datum.instance.id, datum.name)
|
||||
except Exception:
|
||||
msg = _('Error deleting database user.')
|
||||
exceptions.handle(request, msg)
|
||||
|
||||
|
||||
class DeleteDatabase(tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Database",
|
||||
u"Delete Databases",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Database",
|
||||
u"Deleted Databases",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
datum = self.table.get_object_by_id(obj_id)
|
||||
try:
|
||||
api.trove.database_delete(request, datum.instance.id, datum.name)
|
||||
except Exception:
|
||||
msg = _('Error deleting database on instance.')
|
||||
exceptions.handle(request, msg)
|
||||
|
||||
|
||||
class LaunchLink(tables.LinkAction):
|
||||
name = "launch"
|
||||
verbose_name = _("Launch Instance")
|
||||
url = "horizon:project:databases:launch"
|
||||
classes = ("ajax-modal", "btn-launch")
|
||||
icon = "cloud-upload"
|
||||
|
||||
|
||||
class CreateBackup(tables.LinkAction):
|
||||
name = "backup"
|
||||
verbose_name = _("Create Backup")
|
||||
url = "horizon:project:database_backups:create"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "camera"
|
||||
|
||||
def allowed(self, request, instance=None):
|
||||
return (instance.status in ACTIVE_STATES and
|
||||
request.user.has_perm('openstack.services.object-store'))
|
||||
|
||||
def get_link_url(self, datam):
|
||||
url = urlresolvers.reverse(self.url)
|
||||
return url + "?instance=%s" % datam.id
|
||||
|
||||
|
||||
class ResizeVolume(tables.LinkAction):
|
||||
name = "resize_volume"
|
||||
verbose_name = _("Resize Volume")
|
||||
url = "horizon:project:databases:resize_volume"
|
||||
classes = ("ajax-modal", "btn-resize")
|
||||
|
||||
def allowed(self, request, instance=None):
|
||||
return instance.status in ACTIVE_STATES
|
||||
|
||||
def get_link_url(self, datum):
|
||||
instance_id = self.table.get_object_id(datum)
|
||||
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
|
||||
|
||||
def get_data(self, request, instance_id):
|
||||
instance = api.trove.instance_get(request, instance_id)
|
||||
try:
|
||||
flavor_id = instance.flavor['id']
|
||||
instance.full_flavor = api.trove.flavor_get(request, flavor_id)
|
||||
except Exception:
|
||||
pass
|
||||
instance.host = get_host(instance)
|
||||
return instance
|
||||
|
||||
|
||||
def get_datastore(instance):
|
||||
if hasattr(instance, "datastore"):
|
||||
return instance.datastore["type"]
|
||||
return _("Not available")
|
||||
|
||||
|
||||
def get_datastore_version(instance):
|
||||
if hasattr(instance, "datastore"):
|
||||
return instance.datastore["version"]
|
||||
return _("Not available")
|
||||
|
||||
|
||||
def get_host(instance):
|
||||
if hasattr(instance, "hostname"):
|
||||
return instance.hostname
|
||||
elif hasattr(instance, "ip") and instance.ip:
|
||||
return instance.ip[0]
|
||||
return _("Not Assigned")
|
||||
|
||||
|
||||
def get_size(instance):
|
||||
if hasattr(instance, "full_flavor"):
|
||||
size_string = _("%(name)s | %(RAM)s RAM")
|
||||
vals = {'name': instance.full_flavor.name,
|
||||
'RAM': sizeformat.mb_float_format(instance.full_flavor.ram)}
|
||||
return size_string % vals
|
||||
return _("Not available")
|
||||
|
||||
|
||||
def get_volume_size(instance):
|
||||
if hasattr(instance, "volume"):
|
||||
return sizeformat.diskgbformat(instance.volume.get("size"))
|
||||
return _("Not available")
|
||||
|
||||
|
||||
def get_databases(user):
|
||||
if hasattr(user, "access"):
|
||||
databases = [db.name for db in user.access]
|
||||
databases.sort()
|
||||
return ', '.join(databases)
|
||||
return _("-")
|
||||
|
||||
|
||||
class InstancesTable(tables.DataTable):
|
||||
STATUS_CHOICES = (
|
||||
("ACTIVE", True),
|
||||
("BLOCKED", True),
|
||||
("BUILD", None),
|
||||
("FAILED", False),
|
||||
("REBOOT", None),
|
||||
("RESIZE", None),
|
||||
("BACKUP", None),
|
||||
("SHUTDOWN", False),
|
||||
("ERROR", False),
|
||||
("RESTART_REQUIRED", None),
|
||||
)
|
||||
STATUS_DISPLAY_CHOICES = (
|
||||
("ACTIVE", pgettext_lazy("Current status of a Database Instance",
|
||||
u"Active")),
|
||||
("BLOCKED", pgettext_lazy("Current status of a Database Instance",
|
||||
u"Blocked")),
|
||||
("BUILD", pgettext_lazy("Current status of a Database Instance",
|
||||
u"Build")),
|
||||
("FAILED", pgettext_lazy("Current status of a Database Instance",
|
||||
u"Failed")),
|
||||
("REBOOT", pgettext_lazy("Current status of a Database Instance",
|
||||
u"Reboot")),
|
||||
("RESIZE", pgettext_lazy("Current status of a Database Instance",
|
||||
u"Resize")),
|
||||
("BACKUP", pgettext_lazy("Current status of a Database Instance",
|
||||
u"Backup")),
|
||||
("SHUTDOWN", pgettext_lazy("Current status of a Database Instance",
|
||||
u"Shutdown")),
|
||||
("ERROR", pgettext_lazy("Current status of a Database Instance",
|
||||
u"Error")),
|
||||
("RESTART_REQUIRED",
|
||||
pgettext_lazy("Current status of a Database Instance",
|
||||
u"Restart Required")),
|
||||
)
|
||||
name = tables.Column("name",
|
||||
link="horizon:project:databases:detail",
|
||||
verbose_name=_("Instance Name"))
|
||||
datastore = tables.Column(get_datastore,
|
||||
verbose_name=_("Datastore"))
|
||||
datastore_version = tables.Column(get_datastore_version,
|
||||
verbose_name=_("Datastore Version"))
|
||||
host = tables.Column(get_host, verbose_name=_("Host"))
|
||||
size = tables.Column(get_size,
|
||||
verbose_name=_("Size"),
|
||||
attrs={'data-type': 'size'})
|
||||
volume = tables.Column(get_volume_size,
|
||||
verbose_name=_("Volume Size"),
|
||||
attrs={'data-type': 'size'})
|
||||
status = tables.Column("status",
|
||||
verbose_name=_("Status"),
|
||||
status=True,
|
||||
status_choices=STATUS_CHOICES,
|
||||
display_choices=STATUS_DISPLAY_CHOICES)
|
||||
|
||||
class Meta(object):
|
||||
name = "databases"
|
||||
verbose_name = _("Instances")
|
||||
status_columns = ["status"]
|
||||
row_class = UpdateRow
|
||||
table_actions = (LaunchLink, DeleteInstance)
|
||||
row_actions = (CreateBackup,
|
||||
ResizeVolume,
|
||||
ResizeInstance,
|
||||
RestartInstance,
|
||||
DetachReplica,
|
||||
DeleteInstance)
|
||||
|
||||
|
||||
class UsersTable(tables.DataTable):
|
||||
name = tables.Column("name", verbose_name=_("User Name"))
|
||||
host = tables.Column("host", verbose_name=_("Allowed Host"))
|
||||
databases = tables.Column(get_databases, verbose_name=_("Databases"))
|
||||
|
||||
class Meta(object):
|
||||
name = "users"
|
||||
verbose_name = _("Users")
|
||||
table_actions = [DeleteUser]
|
||||
row_actions = [DeleteUser]
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return datum.name
|
||||
|
||||
|
||||
class DatabaseTable(tables.DataTable):
|
||||
name = tables.Column("name", verbose_name=_("Database Name"))
|
||||
|
||||
class Meta(object):
|
||||
name = "databases"
|
||||
verbose_name = _("Databases")
|
||||
table_actions = [DeleteDatabase]
|
||||
row_actions = [DeleteDatabase]
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return datum.name
|
||||
|
||||
|
||||
def is_incremental(obj):
|
||||
return hasattr(obj, 'parent_id') and obj.parent_id is not None
|
||||
|
||||
|
||||
class InstanceBackupsTable(tables.DataTable):
|
||||
name = tables.Column("name",
|
||||
link="horizon:project:database_backups:detail",
|
||||
verbose_name=_("Name"))
|
||||
created = tables.Column("created", verbose_name=_("Created"),
|
||||
filters=[filters.parse_isotime])
|
||||
location = tables.Column(lambda obj: _("Download"),
|
||||
link=lambda obj: obj.locationRef,
|
||||
verbose_name=_("Backup File"))
|
||||
incremental = tables.Column(is_incremental,
|
||||
verbose_name=_("Incremental"),
|
||||
filters=(d_filters.yesno,
|
||||
d_filters.capfirst))
|
||||
status = tables.Column(
|
||||
"status",
|
||||
verbose_name=_("Status"),
|
||||
status=True,
|
||||
status_choices=backup_tables.STATUS_CHOICES,
|
||||
display_choices=backup_tables.STATUS_DISPLAY_CHOICES)
|
||||
|
||||
class Meta(object):
|
||||
name = "backups"
|
||||
verbose_name = _("Backups")
|
||||
status_columns = ["status"]
|
||||
row_class = UpdateRow
|
||||
table_actions = (backup_tables.LaunchLink, backup_tables.DeleteBackup)
|
||||
row_actions = (backup_tables.RestoreLink, backup_tables.DeleteBackup)
|
@ -1,127 +0,0 @@
|
||||
# Copyright 2013 Rackspace Hosting
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf import settings
|
||||
from django import template
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
from openstack_dashboard.contrib.trove.content.databases import tables
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
|
||||
def get_context_data(self, request):
|
||||
return {"instance": self.tab_group.kwargs['instance']}
|
||||
|
||||
def get_template_name(self, request):
|
||||
instance = self.tab_group.kwargs['instance']
|
||||
template_file = ('project/databases/_detail_overview_%s.html'
|
||||
% instance.datastore['type'])
|
||||
try:
|
||||
template.loader.get_template(template_file)
|
||||
return template_file
|
||||
except template.TemplateDoesNotExist:
|
||||
# This datastore type does not have a template file
|
||||
# Just use the base template file
|
||||
return ('project/databases/_detail_overview.html')
|
||||
|
||||
|
||||
class UserTab(tabs.TableTab):
|
||||
table_classes = [tables.UsersTable]
|
||||
name = _("Users")
|
||||
slug = "users_tab"
|
||||
instance = None
|
||||
template_name = "horizon/common/_detail_table.html"
|
||||
preload = False
|
||||
|
||||
def get_users_data(self):
|
||||
instance = self.tab_group.kwargs['instance']
|
||||
try:
|
||||
data = api.trove.users_list(self.request, instance.id)
|
||||
for user in data:
|
||||
user.instance = instance
|
||||
user.access = api.trove.user_list_access(self.request,
|
||||
instance.id,
|
||||
user.name)
|
||||
except Exception:
|
||||
msg = _('Unable to get user data.')
|
||||
exceptions.handle(self.request, msg)
|
||||
data = []
|
||||
return data
|
||||
|
||||
def allowed(self, request):
|
||||
perms = getattr(settings, 'TROVE_ADD_USER_PERMS', [])
|
||||
if perms:
|
||||
return request.user.has_perms(perms)
|
||||
return True
|
||||
|
||||
|
||||
class DatabaseTab(tabs.TableTab):
|
||||
table_classes = [tables.DatabaseTable]
|
||||
name = _("Databases")
|
||||
slug = "database_tab"
|
||||
instance = None
|
||||
template_name = "horizon/common/_detail_table.html"
|
||||
preload = False
|
||||
|
||||
def get_databases_data(self):
|
||||
instance = self.tab_group.kwargs['instance']
|
||||
try:
|
||||
data = api.trove.database_list(self.request, instance.id)
|
||||
add_instance = lambda d: setattr(d, 'instance', instance)
|
||||
map(add_instance, data)
|
||||
except Exception:
|
||||
msg = _('Unable to get databases data.')
|
||||
exceptions.handle(self.request, msg)
|
||||
data = []
|
||||
return data
|
||||
|
||||
def allowed(self, request):
|
||||
perms = getattr(settings, 'TROVE_ADD_DATABASE_PERMS', [])
|
||||
if perms:
|
||||
return request.user.has_perms(perms)
|
||||
return True
|
||||
|
||||
|
||||
class BackupsTab(tabs.TableTab):
|
||||
table_classes = [tables.InstanceBackupsTable]
|
||||
name = _("Backups")
|
||||
slug = "backups_tab"
|
||||
instance = None
|
||||
template_name = "horizon/common/_detail_table.html"
|
||||
preload = False
|
||||
|
||||
def get_backups_data(self):
|
||||
instance = self.tab_group.kwargs['instance']
|
||||
try:
|
||||
data = api.trove.instance_backups(self.request, instance.id)
|
||||
except Exception:
|
||||
msg = _('Unable to get database backup data.')
|
||||
exceptions.handle(self.request, msg)
|
||||
data = []
|
||||
return data
|
||||
|
||||
def allowed(self, request):
|
||||
return request.user.has_perm('openstack.services.object-store')
|
||||
|
||||
|
||||
class InstanceDetailTabs(tabs.TabGroup):
|
||||
slug = "instance_details"
|
||||
tabs = (OverviewTab, UserTab, DatabaseTab, BackupsTab)
|
||||
sticky = True
|
@ -1,60 +0,0 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ instance.name }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ instance.id }}</dd>
|
||||
<dt>{% trans "Datastore" %}</dt>
|
||||
<dd>{{ instance.datastore.type }}</dd>
|
||||
<dt>{% trans "Datastore Version" %}</dt>
|
||||
<dd>{{ instance.datastore.version }}</dd>
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>{{ instance.status|title }}</dd>
|
||||
</dl>
|
||||
|
||||
<h4>{% trans "Specs" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Flavor" %}</dt>
|
||||
<dd>{{ instance.full_flavor.name }}</dd>
|
||||
<dt>{% trans "RAM" %}</dt>
|
||||
<dd>{{ instance.full_flavor.ram|mb_float_format }}</dd>
|
||||
{% if instance.volume %}
|
||||
<dt>{% trans "Volume Size" %}</dt>
|
||||
<dd>{{ instance.volume.size|diskgbformat }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Created" %}</dt>
|
||||
<dd>{{ instance.created|parse_isotime }}</dd>
|
||||
<dt>{% trans "Updated" %}</dt>
|
||||
<dd>{{ instance.updated|parse_isotime }}</dd>
|
||||
</dl>
|
||||
|
||||
{% block connection_info %}
|
||||
{% endblock %}
|
||||
|
||||
{% if instance.replica_of or instance.replicas %}
|
||||
<h4>{% trans "Replication" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
{% if instance.replica_of %}
|
||||
<dt>{% trans "Is a Replica Of" %}</dt>
|
||||
<dd>
|
||||
{% url 'horizon:project:databases:detail' instance.replica_of.id as instance_url %}
|
||||
<a href="{{ instance_url }}">{{ instance.replica_of.id }}</a>
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if instance.replicas %}
|
||||
<dt>{% trans "Replicas" %}</dt>
|
||||
{% for replica in instance.replicas %}
|
||||
<dd>
|
||||
{% url 'horizon:project:databases:detail' replica.id as instance_url %}
|
||||
<a href="{{ instance_url }}">{{ replica.id }}</a>
|
||||
</dd>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</dl>
|
||||
{% endif %}
|
||||
</div>
|
@ -1,21 +0,0 @@
|
||||
{% extends "project/databases/_detail_overview.html" %}
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
{% block connection_info %}
|
||||
<h4>{% trans "Connection Information" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
{% with instance.host as host %}
|
||||
<dt>{% trans "Host" %}</dt>
|
||||
{% if not host %}
|
||||
<dd>{% trans "Not Assigned" %}</dd>
|
||||
{% else %}
|
||||
<dd>{{ host }}</dd>
|
||||
<dt>{% trans "Database Port" %}</dt>
|
||||
<dd>9042</dd>
|
||||
<dt>{% trans "Connection Examples" %}</dt>
|
||||
<dd>cqlsh {{ host }} 9042</dd>
|
||||
{% endif %} <!-- ends else block -->
|
||||
{% endwith %}
|
||||
</dl>
|
||||
{% endblock %}
|
@ -1,19 +0,0 @@
|
||||
{% extends "project/databases/_detail_overview.html" %}
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
{% block connection_info %}
|
||||
<h4>{% trans "Connection Info" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
{% with instance.host as host %}
|
||||
<dt>{% trans "Host" %}</dt>
|
||||
{% if not host %}
|
||||
<dd>{% trans "Not Assigned" %}</dd>
|
||||
{% else %}
|
||||
<dd>{{ host }}</dd>
|
||||
<dt>{% trans "Connection Examples" %}</dt>
|
||||
<dd>http://{{ host }}:8091</dd>
|
||||
{% endif %} <!-- ends else block -->
|
||||
{% endwith %}
|
||||
</dl>
|
||||
{% endblock %}
|
@ -1,30 +0,0 @@
|
||||
{% extends "project/databases/_detail_overview.html" %}
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
{% block connection_info %}
|
||||
<h4>{% trans "Connection Information" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
{% if instance.cluster_id %}
|
||||
<a href="/project/clusters/{{instance.cluster_id}}">Link</a> to Cluster Details for Connection Info
|
||||
{% else %}
|
||||
<dl>
|
||||
{% with instance.host as host %}
|
||||
<dt>{% trans "Host" %}</dt>
|
||||
<dd>
|
||||
{% if not host %}
|
||||
{% trans "Not Assigned" %}
|
||||
</dd>
|
||||
{% else %}
|
||||
{{ host }}
|
||||
</dd>
|
||||
<dt>{% trans "Database Port" %}</dt>
|
||||
<dd>27017</dd>
|
||||
<dt>{% trans "Connection Examples" %}</dt>
|
||||
<dd>mongo --host {{ host }}</dd>
|
||||
<dd>mongodb://[{% trans "USERNAME" %}:{% trans "PASSWORD" %}@]{{ host }}:27017/{% trans "DATABASE" %}</dd>
|
||||
{% endif %} <!-- ends else block -->
|
||||
{% endwith %}
|
||||
</dl>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -1,22 +0,0 @@
|
||||
{% extends "project/databases/_detail_overview.html" %}
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
{% block connection_info %}
|
||||
<h4>{% trans "Connection Info" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
{% with instance.host as host %}
|
||||
<dt>{% trans "Host" %}</dt>
|
||||
{% if not host %}
|
||||
<dd>{% trans "Not Assigned" %}</dd>
|
||||
{% else %}
|
||||
<dd>{{ host }}</dd>
|
||||
<dt>{% trans "Database Port" %}</dt>
|
||||
<dd>3306</dd>
|
||||
<dt>{% trans "Connection Examples" %}</dt>
|
||||
<dd>mysql -h {{ host }} -u {% trans "USERNAME" %} -p</dd>
|
||||
<dd>mysql://{% trans "USERNAME" %}:{% trans "PASSWORD" %}@{{ host }}:3306/{% trans "DATABASE"%}</dd>
|
||||
{% endif %} <!-- ends else block -->
|
||||
{% endwith %}
|
||||
</dl>
|
||||
{% endblock %}
|
@ -1,19 +0,0 @@
|
||||
{% extends "project/databases/_detail_overview.html" %}
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
{% block connection_info %}
|
||||
<h4>{% trans "Connection Info" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
{% with instance.host as host %}
|
||||
<dt>{% trans "Host" %}</dt>
|
||||
{% if not host %}
|
||||
<dd>{% trans "Not Assigned" %}</dd>
|
||||
{% else %}
|
||||
<dd>{{ host }}</dd>
|
||||
<dt>{% trans "Connection Examples" %}</dt>
|
||||
<dd>redis-cli -h {{ host }}</dd>
|
||||
{% endif %} <!-- ends else block -->
|
||||
{% endwith %}
|
||||
</dl>
|
||||
{% endblock %}
|
@ -1,3 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<p>{% blocktrans %}Optionally choose to create this database using a previous backup, or as a replica of another database instance.{% endblocktrans %}</p>
|
@ -1,4 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<p>{% blocktrans %}Specify the details for launching an instance.{% endblocktrans %}</p>
|
||||
<p>{% blocktrans %}<strong>Please note:</strong> The value specified in the Volume Size field should be greater than 0, however, some configurations do not support specifying volume size. If specifying the volume size results in an error stating volume support is not enabled, enter 0.{% endblocktrans %}</p>
|
@ -1,18 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<h4>{% trans "Initial Databases" %}</h4>
|
||||
<p>{% trans "Optionally provide a comma separated list of databases to create:" %}</p>
|
||||
<pre>database1, database2, database3</pre>
|
||||
|
||||
<h4>{% trans "Initial Admin User" %}</h4>
|
||||
<p>{% blocktrans %}Create an optional initial user.
|
||||
This user will have access to all databases you create.{% endblocktrans %}</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>{% trans "Username (required)" %}</strong></li>
|
||||
<li><strong>{% trans "Password (required)" %}</strong></li>
|
||||
<li><strong>{% trans "Allowed Host (optional)" %}</strong>
|
||||
{% blocktrans %}Allow the user to connect from this host
|
||||
only. If not provided this user will be allowed to connect from anywhere.
|
||||
{% endblocktrans %}</li>
|
||||
</ul>
|
@ -1,9 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<p>
|
||||
{% blocktrans %}
|
||||
Move networks from 'Available Networks' to 'Selected Networks' by
|
||||
clicking the button, or dragging and dropping. You can change the
|
||||
NIC order by dragging and dropping as well.
|
||||
{% endblocktrans %}
|
||||
</p>
|
@ -1,36 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<noscript><h3>{{ step }}</h3></noscript>
|
||||
<div id="networkListSortContainer">
|
||||
<div class="col-sm-6">
|
||||
<label id="selected_network_label">{% trans "Selected networks" %}</label>
|
||||
<ul id="selected_network" class="networklist"></ul>
|
||||
<label>{% trans "Available networks" %}</label>
|
||||
<ul id="available_network" class="networklist"></ul>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
{% include "project/databases/_launch_network_help.html" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="networkListIdContainer">
|
||||
<div class="actions">
|
||||
<div id="networkListId">
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="help_text">
|
||||
{{ step.get_help_text }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
if (typeof $ !== 'undefined') {
|
||||
horizon.instances.workflow_init($(".workflow"));
|
||||
} else {
|
||||
addHorizonLoadEvent(function() {
|
||||
horizon.instances.workflow_init($(".workflow"));
|
||||
});
|
||||
}
|
||||
</script>
|
@ -1,25 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% 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 %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<p>{% blocktrans %}Specify a new flavor for the database instance.{% endblocktrans %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Resize Database Instance" %}" />
|
||||
<a href="{% url "horizon:project:databases:index" %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
||||
|
@ -1,26 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}resize_volume_form{% endblock %}
|
||||
{% block form_action %}{% url "horizon:project:databases:resize_volume" instance_id %}{% endblock %}
|
||||
|
||||
{% block modal_id %}resize_volume_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Resize Database Volume" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<p>{% blocktrans %}Specify the new volume size for the database instance.{% endblocktrans %}</p>
|
||||
<p>{% blocktrans %}<strong>Please note:</strong> The new value must be greater than the existing volume size.{% endblocktrans %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Resize Database Volume" %}" />
|
||||
<a href="{% url "horizon:project:databases:index" %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
||||
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Instances" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Launch Instance" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'horizon/common/_workflow.html' %}
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Resize Database Instance" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "project/databases/_resize_instance.html" %}
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Resize Database Volume" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "project/databases/_resize_volume.html" %}
|
||||
{% endblock %}
|
@ -1,544 +0,0 @@
|
||||
# Copyright 2013 Mirantis Inc.
|
||||
# Copyright 2013 Rackspace Hosting.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
import django
|
||||
from django.core.urlresolvers import reverse
|
||||
from django import http
|
||||
from django.utils import unittest
|
||||
|
||||
from mox3.mox import IsA # noqa
|
||||
import six
|
||||
|
||||
from horizon import exceptions
|
||||
from openstack_dashboard import api as dash_api
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
from troveclient import common
|
||||
|
||||
|
||||
INDEX_URL = reverse('horizon:project:databases:index')
|
||||
LAUNCH_URL = reverse('horizon:project:databases:launch')
|
||||
DETAILS_URL = reverse('horizon:project:databases:detail', args=['id'])
|
||||
|
||||
|
||||
class DatabaseTests(test.TestCase):
|
||||
@test.create_stubs(
|
||||
{api.trove: ('instance_list', 'flavor_list')})
|
||||
def test_index(self):
|
||||
# Mock database instances
|
||||
databases = common.Paginated(self.databases.list())
|
||||
api.trove.instance_list(IsA(http.HttpRequest), marker=None)\
|
||||
.AndReturn(databases)
|
||||
# Mock flavors
|
||||
api.trove.flavor_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.flavors.list())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'project/databases/index.html')
|
||||
# Check the Host column displaying ip or hostname
|
||||
self.assertContains(res, '10.0.0.3')
|
||||
self.assertContains(res, 'trove.instance-2.com')
|
||||
|
||||
@test.create_stubs(
|
||||
{api.trove: ('instance_list', 'flavor_list')})
|
||||
def test_index_flavor_exception(self):
|
||||
# Mock database instances
|
||||
databases = common.Paginated(self.databases.list())
|
||||
api.trove.instance_list(IsA(http.HttpRequest), marker=None)\
|
||||
.AndReturn(databases)
|
||||
# Mock flavors
|
||||
api.trove.flavor_list(IsA(http.HttpRequest))\
|
||||
.AndRaise(self.exceptions.trove)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'project/databases/index.html')
|
||||
self.assertMessageCount(res, error=1)
|
||||
|
||||
@test.create_stubs(
|
||||
{api.trove: ('instance_list',)})
|
||||
def test_index_list_exception(self):
|
||||
# Mock database instances
|
||||
api.trove.instance_list(IsA(http.HttpRequest), marker=None)\
|
||||
.AndRaise(self.exceptions.trove)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'project/databases/index.html')
|
||||
self.assertMessageCount(res, error=1)
|
||||
|
||||
@test.create_stubs(
|
||||
{api.trove: ('instance_list', 'flavor_list')})
|
||||
def test_index_pagination(self):
|
||||
# Mock database instances
|
||||
databases = self.databases.list()
|
||||
last_record = databases[1]
|
||||
databases = common.Paginated(databases, next_marker="foo")
|
||||
api.trove.instance_list(IsA(http.HttpRequest), marker=None)\
|
||||
.AndReturn(databases)
|
||||
# Mock flavors
|
||||
api.trove.flavor_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.flavors.list())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'project/databases/index.html')
|
||||
self.assertContains(
|
||||
res, 'marker=' + last_record.id)
|
||||
|
||||
@test.create_stubs(
|
||||
{api.trove: ('instance_list', 'flavor_list')})
|
||||
def test_index_flavor_list_exception(self):
|
||||
# Mocking instances.
|
||||
databases = common.Paginated(self.databases.list())
|
||||
api.trove.instance_list(
|
||||
IsA(http.HttpRequest),
|
||||
marker=None,
|
||||
).AndReturn(databases)
|
||||
# Mocking flavor list with raising an exception.
|
||||
api.trove.flavor_list(
|
||||
IsA(http.HttpRequest),
|
||||
).AndRaise(self.exceptions.trove)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
|
||||
self.assertTemplateUsed(res, 'project/databases/index.html')
|
||||
self.assertMessageCount(res, error=1)
|
||||
|
||||
@test.create_stubs({
|
||||
api.trove: ('flavor_list', 'backup_list',
|
||||
'datastore_list', 'datastore_version_list',
|
||||
'instance_list'),
|
||||
dash_api.neutron: ('network_list',)})
|
||||
def test_launch_instance(self):
|
||||
api.trove.flavor_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.flavors.list())
|
||||
api.trove.backup_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.database_backups.list())
|
||||
api.trove.instance_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.databases.list())
|
||||
# Mock datastores
|
||||
api.trove.datastore_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.datastores.list())
|
||||
# Mock datastore versions
|
||||
api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str)).\
|
||||
MultipleTimes().AndReturn(self.datastore_versions.list())
|
||||
|
||||
dash_api.neutron.network_list(IsA(http.HttpRequest),
|
||||
tenant_id=self.tenant.id,
|
||||
shared=False).AndReturn(
|
||||
self.networks.list()[:1])
|
||||
|
||||
dash_api.neutron.network_list(IsA(http.HttpRequest),
|
||||
shared=True).AndReturn(
|
||||
self.networks.list()[1:])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(LAUNCH_URL)
|
||||
self.assertTemplateUsed(res, 'project/databases/launch.html')
|
||||
|
||||
# django 1.7 and later does not handle the thrown Http302
|
||||
# exception well enough.
|
||||
# TODO(mrunge): re-check when django-1.8 is stable
|
||||
@unittest.skipIf(django.VERSION >= (1, 7, 0),
|
||||
'Currently skipped with Django >= 1.7')
|
||||
@test.create_stubs({api.trove: ('flavor_list',)})
|
||||
def test_launch_instance_exception_on_flavors(self):
|
||||
trove_exception = self.exceptions.nova
|
||||
api.trove.flavor_list(IsA(http.HttpRequest)).AndRaise(trove_exception)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
toSuppress = ["openstack_dashboard.dashboards.project.databases."
|
||||
"workflows.create_instance",
|
||||
"horizon.workflows.base"]
|
||||
|
||||
# Suppress expected log messages in the test output
|
||||
loggers = []
|
||||
for cls in toSuppress:
|
||||
logger = logging.getLogger(cls)
|
||||
loggers.append((logger, logger.getEffectiveLevel()))
|
||||
logger.setLevel(logging.CRITICAL)
|
||||
|
||||
try:
|
||||
with self.assertRaises(exceptions.Http302):
|
||||
self.client.get(LAUNCH_URL)
|
||||
|
||||
finally:
|
||||
# Restore the previous log levels
|
||||
for (log, level) in loggers:
|
||||
log.setLevel(level)
|
||||
|
||||
@test.create_stubs({
|
||||
api.trove: ('flavor_list', 'backup_list', 'instance_create',
|
||||
'datastore_list', 'datastore_version_list',
|
||||
'instance_list'),
|
||||
dash_api.neutron: ('network_list',)})
|
||||
def test_create_simple_instance(self):
|
||||
api.trove.flavor_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.flavors.list())
|
||||
|
||||
api.trove.backup_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.database_backups.list())
|
||||
|
||||
api.trove.instance_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.databases.list())
|
||||
|
||||
# Mock datastores
|
||||
api.trove.datastore_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.datastores.list())
|
||||
|
||||
# Mock datastore versions
|
||||
api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str))\
|
||||
.MultipleTimes().AndReturn(self.datastore_versions.list())
|
||||
|
||||
dash_api.neutron.network_list(IsA(http.HttpRequest),
|
||||
tenant_id=self.tenant.id,
|
||||
shared=False).AndReturn(
|
||||
self.networks.list()[:1])
|
||||
|
||||
dash_api.neutron.network_list(IsA(http.HttpRequest),
|
||||
shared=True).AndReturn(
|
||||
self.networks.list()[1:])
|
||||
|
||||
nics = [{"net-id": self.networks.first().id, "v4-fixed-ip": ''}]
|
||||
|
||||
# Actual create database call
|
||||
api.trove.instance_create(
|
||||
IsA(http.HttpRequest),
|
||||
IsA(six.text_type),
|
||||
IsA(int),
|
||||
IsA(six.text_type),
|
||||
databases=None,
|
||||
datastore=IsA(six.text_type),
|
||||
datastore_version=IsA(six.text_type),
|
||||
restore_point=None,
|
||||
replica_of=None,
|
||||
users=None,
|
||||
nics=nics).AndReturn(self.databases.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
post = {
|
||||
'name': "MyDB",
|
||||
'volume': '1',
|
||||
'flavor': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
'network': self.networks.first().id,
|
||||
'datastore': 'mysql,5.5',
|
||||
}
|
||||
|
||||
res = self.client.post(LAUNCH_URL, post)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({
|
||||
api.trove: ('flavor_list', 'backup_list', 'instance_create',
|
||||
'datastore_list', 'datastore_version_list',
|
||||
'instance_list'),
|
||||
dash_api.neutron: ('network_list',)})
|
||||
def test_create_simple_instance_exception(self):
|
||||
trove_exception = self.exceptions.nova
|
||||
api.trove.flavor_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.flavors.list())
|
||||
|
||||
api.trove.backup_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.database_backups.list())
|
||||
|
||||
api.trove.instance_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.databases.list())
|
||||
|
||||
# Mock datastores
|
||||
api.trove.datastore_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.datastores.list())
|
||||
|
||||
# Mock datastore versions
|
||||
api.trove.datastore_version_list(IsA(http.HttpRequest), IsA(str))\
|
||||
.MultipleTimes().AndReturn(self.datastore_versions.list())
|
||||
|
||||
dash_api.neutron.network_list(IsA(http.HttpRequest),
|
||||
tenant_id=self.tenant.id,
|
||||
shared=False).AndReturn(
|
||||
self.networks.list()[:1])
|
||||
|
||||
dash_api.neutron.network_list(IsA(http.HttpRequest),
|
||||
shared=True).AndReturn(
|
||||
self.networks.list()[1:])
|
||||
|
||||
nics = [{"net-id": self.networks.first().id, "v4-fixed-ip": ''}]
|
||||
|
||||
# Actual create database call
|
||||
api.trove.instance_create(
|
||||
IsA(http.HttpRequest),
|
||||
IsA(six.text_type),
|
||||
IsA(int),
|
||||
IsA(six.text_type),
|
||||
databases=None,
|
||||
datastore=IsA(six.text_type),
|
||||
datastore_version=IsA(six.text_type),
|
||||
restore_point=None,
|
||||
replica_of=None,
|
||||
users=None,
|
||||
nics=nics).AndRaise(trove_exception)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
post = {
|
||||
'name': "MyDB",
|
||||
'volume': '1',
|
||||
'flavor': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
'network': self.networks.first().id,
|
||||
'datastore': 'mysql,5.5',
|
||||
}
|
||||
|
||||
res = self.client.post(LAUNCH_URL, post)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs(
|
||||
{api.trove: ('instance_get', 'flavor_get',)})
|
||||
def _test_details(self, database, with_designate=False):
|
||||
api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))\
|
||||
.AndReturn(database)
|
||||
api.trove.flavor_get(IsA(http.HttpRequest), IsA(str))\
|
||||
.AndReturn(self.flavors.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(DETAILS_URL)
|
||||
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
|
||||
if with_designate:
|
||||
self.assertContains(res, database.hostname)
|
||||
else:
|
||||
self.assertContains(res, database.ip[0])
|
||||
|
||||
def test_details_with_ip(self):
|
||||
database = self.databases.first()
|
||||
self._test_details(database, with_designate=False)
|
||||
|
||||
def test_details_with_hostname(self):
|
||||
database = self.databases.list()[1]
|
||||
self._test_details(database, with_designate=True)
|
||||
|
||||
@test.create_stubs(
|
||||
{api.trove: ('instance_get', 'flavor_get', 'users_list',
|
||||
'user_list_access', 'user_delete')})
|
||||
def test_user_delete(self):
|
||||
database = self.databases.first()
|
||||
user = self.database_users.first()
|
||||
user_db = self.database_user_dbs.first()
|
||||
|
||||
database_id = database.id
|
||||
# Instead of using the user's ID, the api uses the user's name. BOOO!
|
||||
user_id = user.name
|
||||
|
||||
# views.py: DetailView.get_data
|
||||
api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))\
|
||||
.AndReturn(database)
|
||||
api.trove.flavor_get(IsA(http.HttpRequest), IsA(str))\
|
||||
.AndReturn(self.flavors.first())
|
||||
|
||||
# tabs.py: UserTab.get_user_data
|
||||
api.trove.users_list(IsA(http.HttpRequest),
|
||||
database_id).AndReturn([user])
|
||||
api.trove.user_list_access(IsA(http.HttpRequest),
|
||||
database_id,
|
||||
user_id).AndReturn([user_db])
|
||||
|
||||
# tables.py: DeleteUser.delete
|
||||
api.trove.user_delete(IsA(http.HttpRequest),
|
||||
database_id,
|
||||
user_id).AndReturn(None)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
details_url = reverse('horizon:project:databases:detail',
|
||||
args=[database_id])
|
||||
url = details_url + '?tab=instance_details__users_tab'
|
||||
action_string = u"users__delete__%s" % user_id
|
||||
form_data = {'action': action_string}
|
||||
res = self.client.post(url, form_data)
|
||||
self.assertRedirectsNoFollow(res, url)
|
||||
|
||||
@test.create_stubs({
|
||||
api.trove: ('instance_get', 'instance_resize_volume')})
|
||||
def test_resize_volume(self):
|
||||
database = self.databases.first()
|
||||
database_id = database.id
|
||||
database_size = database.volume.get('size')
|
||||
|
||||
# views.py: DetailView.get_data
|
||||
api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))\
|
||||
.AndReturn(database)
|
||||
|
||||
# forms.py: ResizeVolumeForm.handle
|
||||
api.trove.instance_resize_volume(IsA(http.HttpRequest),
|
||||
database_id,
|
||||
IsA(int)).AndReturn(None)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
url = reverse('horizon:project:databases:resize_volume',
|
||||
args=[database_id])
|
||||
post = {
|
||||
'instance_id': database_id,
|
||||
'orig_size': database_size,
|
||||
'new_size': database_size + 1,
|
||||
}
|
||||
res = self.client.post(url, post)
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.trove: ('instance_get', )})
|
||||
def test_resize_volume_bad_value(self):
|
||||
database = self.databases.first()
|
||||
database_id = database.id
|
||||
database_size = database.volume.get('size')
|
||||
|
||||
# views.py: DetailView.get_data
|
||||
api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))\
|
||||
.AndReturn(database)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
url = reverse('horizon:project:databases:resize_volume',
|
||||
args=[database_id])
|
||||
post = {
|
||||
'instance_id': database_id,
|
||||
'orig_size': database_size,
|
||||
'new_size': database_size,
|
||||
}
|
||||
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')})
|
||||
def test_resize_instance_get(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())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
url = reverse('horizon:project:databases:resize_instance',
|
||||
args=[database.id])
|
||||
|
||||
res = self.client.get(url)
|
||||
self.assertTemplateUsed(res, 'project/databases/resize_instance.html')
|
||||
option = '<option value="%s">%s</option>'
|
||||
for flavor in self.database_flavors.list():
|
||||
if flavor.id == database.flavor['id']:
|
||||
self.assertNotContains(res, option % (flavor.id, flavor.name))
|
||||
else:
|
||||
self.assertContains(res, option % (flavor.id, flavor.name))
|
||||
|
||||
@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: ('flavor_list', 'backup_list', 'instance_create',
|
||||
'datastore_list', 'datastore_version_list',
|
||||
'instance_list', 'instance_get'),
|
||||
dash_api.neutron: ('network_list',)})
|
||||
def test_create_replica_instance(self):
|
||||
api.trove.flavor_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.flavors.list())
|
||||
|
||||
api.trove.backup_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.database_backups.list())
|
||||
|
||||
api.trove.instance_list(IsA(http.HttpRequest)).AndReturn(
|
||||
self.databases.list())
|
||||
|
||||
api.trove.datastore_list(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.datastores.list())
|
||||
|
||||
api.trove.datastore_version_list(IsA(http.HttpRequest),
|
||||
IsA(str))\
|
||||
.MultipleTimes().AndReturn(self.datastore_versions.list())
|
||||
|
||||
dash_api.neutron.network_list(IsA(http.HttpRequest),
|
||||
tenant_id=self.tenant.id,
|
||||
shared=False).\
|
||||
AndReturn(self.networks.list()[:1])
|
||||
|
||||
dash_api.neutron.network_list(IsA(http.HttpRequest),
|
||||
shared=True).\
|
||||
AndReturn(self.networks.list()[1:])
|
||||
|
||||
nics = [{"net-id": self.networks.first().id, "v4-fixed-ip": ''}]
|
||||
|
||||
api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))\
|
||||
.AndReturn(self.databases.first())
|
||||
|
||||
# Actual create database call
|
||||
api.trove.instance_create(
|
||||
IsA(http.HttpRequest),
|
||||
IsA(six.text_type),
|
||||
IsA(int),
|
||||
IsA(six.text_type),
|
||||
databases=None,
|
||||
datastore=IsA(six.text_type),
|
||||
datastore_version=IsA(six.text_type),
|
||||
restore_point=None,
|
||||
replica_of=self.databases.first().id,
|
||||
users=None,
|
||||
nics=nics).AndReturn(self.databases.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
post = {
|
||||
'name': "MyDB",
|
||||
'volume': '1',
|
||||
'flavor': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
'network': self.networks.first().id,
|
||||
'datastore': 'mysql,5.5',
|
||||
'initial_state': 'master',
|
||||
'master': self.databases.first().id
|
||||
}
|
||||
|
||||
res = self.client.post(LAUNCH_URL, post)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
@ -1,33 +0,0 @@
|
||||
# Copyright 2013 Rackspace Hosting
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf.urls import patterns
|
||||
from django.conf.urls import url
|
||||
|
||||
from openstack_dashboard.contrib.trove.content.databases import views
|
||||
|
||||
|
||||
INSTANCES = r'^(?P<instance_id>[^/]+)/%s$'
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
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'),
|
||||
url(INSTANCES % 'resize_instance', views.ResizeInstanceView.as_view(),
|
||||
name='resize_instance')
|
||||
)
|
@ -1,224 +0,0 @@
|
||||
# Copyright 2013 Rackspace Hosting
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Views for managing database instances.
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import six
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms as horizon_forms
|
||||
from horizon import tables as horizon_tables
|
||||
from horizon import tabs as horizon_tabs
|
||||
from horizon.utils import memoized
|
||||
from horizon import workflows as horizon_workflows
|
||||
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
from openstack_dashboard.contrib.trove.content.databases import forms
|
||||
from openstack_dashboard.contrib.trove.content.databases import tables
|
||||
from openstack_dashboard.contrib.trove.content.databases import tabs
|
||||
from openstack_dashboard.contrib.trove.content.databases import workflows
|
||||
|
||||
from openstack_dashboard.dashboards.project.instances \
|
||||
import utils as instance_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexView(horizon_tables.DataTableView):
|
||||
table_class = tables.InstancesTable
|
||||
template_name = 'project/databases/index.html'
|
||||
page_title = _("Instances")
|
||||
|
||||
def has_more_data(self, table):
|
||||
return self._more
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_flavors(self):
|
||||
try:
|
||||
flavors = api.trove.flavor_list(self.request)
|
||||
except Exception:
|
||||
flavors = []
|
||||
msg = _('Unable to retrieve database size information.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return OrderedDict((six.text_type(flavor.id), flavor)
|
||||
for flavor in flavors)
|
||||
|
||||
def _extra_data(self, instance):
|
||||
flavor = self.get_flavors().get(instance.flavor["id"])
|
||||
if flavor is not None:
|
||||
instance.full_flavor = flavor
|
||||
instance.host = tables.get_host(instance)
|
||||
return instance
|
||||
|
||||
def get_data(self):
|
||||
marker = self.request.GET.get(
|
||||
tables.InstancesTable._meta.pagination_param)
|
||||
# Gather our instances
|
||||
try:
|
||||
instances = api.trove.instance_list(self.request, marker=marker)
|
||||
self._more = instances.next or False
|
||||
except Exception:
|
||||
self._more = False
|
||||
instances = []
|
||||
msg = _('Unable to retrieve database instances.')
|
||||
exceptions.handle(self.request, msg)
|
||||
map(self._extra_data, instances)
|
||||
return instances
|
||||
|
||||
|
||||
class LaunchInstanceView(horizon_workflows.WorkflowView):
|
||||
workflow_class = workflows.LaunchInstance
|
||||
template_name = "project/databases/launch.html"
|
||||
page_title = _("Launch Database")
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(LaunchInstanceView, self).get_initial()
|
||||
initial['project_id'] = self.request.user.project_id
|
||||
initial['user_id'] = self.request.user.id
|
||||
return initial
|
||||
|
||||
|
||||
class DetailView(horizon_tabs.TabbedTableView):
|
||||
tab_group_class = tabs.InstanceDetailTabs
|
||||
template_name = 'horizon/common/_detail.html'
|
||||
page_title = "{{ instance.name }}"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
instance = self.get_data()
|
||||
table = tables.InstancesTable(self.request)
|
||||
context["instance"] = instance
|
||||
context["url"] = self.get_redirect_url()
|
||||
context["actions"] = table.render_row_actions(instance)
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_data(self):
|
||||
try:
|
||||
LOG.info("Obtaining instance for detailed view ")
|
||||
instance_id = self.kwargs['instance_id']
|
||||
instance = api.trove.instance_get(self.request, instance_id)
|
||||
instance.host = tables.get_host(instance)
|
||||
except Exception:
|
||||
msg = _('Unable to retrieve details '
|
||||
'for database instance: %s') % instance_id
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=self.get_redirect_url())
|
||||
try:
|
||||
instance.full_flavor = api.trove.flavor_get(
|
||||
self.request, instance.flavor["id"])
|
||||
except Exception:
|
||||
LOG.error('Unable to retrieve flavor details'
|
||||
' for database instance: %s' % instance_id)
|
||||
return instance
|
||||
|
||||
def get_tabs(self, request, *args, **kwargs):
|
||||
instance = self.get_data()
|
||||
return self.tab_group_class(request, instance=instance, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_redirect_url():
|
||||
return reverse('horizon:project:databases:index')
|
||||
|
||||
|
||||
class ResizeVolumeView(horizon_forms.ModalFormView):
|
||||
form_class = forms.ResizeVolumeForm
|
||||
template_name = 'project/databases/resize_volume.html'
|
||||
success_url = reverse_lazy('horizon:project:databases:index')
|
||||
page_title = _("Resize Database Volume")
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_object(self, *args, **kwargs):
|
||||
instance_id = self.kwargs['instance_id']
|
||||
try:
|
||||
return api.trove.instance_get(self.request, instance_id)
|
||||
except Exception:
|
||||
msg = _('Unable to retrieve instance details.')
|
||||
redirect = reverse('horizon:project:databases:index')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ResizeVolumeView, self).get_context_data(**kwargs)
|
||||
context['instance_id'] = self.kwargs['instance_id']
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
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')
|
||||
page_title = _("Resize Database Instance")
|
||||
|
||||
@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
|
@ -1,3 +0,0 @@
|
||||
# Importing non-modules that are not used explicitly
|
||||
|
||||
from .create_instance import LaunchInstance # noqa
|
@ -1,420 +0,0 @@
|
||||
# Copyright 2013 Rackspace Hosting
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon.utils import memoized
|
||||
from horizon import workflows
|
||||
from openstack_dashboard import api as dash_api
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
|
||||
from openstack_dashboard.dashboards.project.instances \
|
||||
import utils as instance_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SetInstanceDetailsAction(workflows.Action):
|
||||
name = forms.CharField(max_length=80, label=_("Instance Name"))
|
||||
flavor = forms.ChoiceField(label=_("Flavor"),
|
||||
help_text=_("Size of image to launch."))
|
||||
volume = forms.IntegerField(label=_("Volume Size"),
|
||||
min_value=0,
|
||||
initial=1,
|
||||
help_text=_("Size of the volume in GB."))
|
||||
datastore = forms.ChoiceField(label=_("Datastore"),
|
||||
help_text=_(
|
||||
"Type and version of datastore."))
|
||||
|
||||
class Meta(object):
|
||||
name = _("Details")
|
||||
help_text_template = "project/databases/_launch_details_help.html"
|
||||
|
||||
def clean(self):
|
||||
if self.data.get("datastore", None) == "select_datastore_type_version":
|
||||
msg = _("You must select a datastore type and version.")
|
||||
self._errors["datastore"] = self.error_class([msg])
|
||||
return self.cleaned_data
|
||||
|
||||
@memoized.memoized_method
|
||||
def flavors(self, request):
|
||||
try:
|
||||
return api.trove.flavor_list(request)
|
||||
except Exception:
|
||||
LOG.exception("Exception while obtaining flavors list")
|
||||
redirect = reverse("horizon:project:databases:index")
|
||||
exceptions.handle(request,
|
||||
_('Unable to obtain flavors.'),
|
||||
redirect=redirect)
|
||||
|
||||
def populate_flavor_choices(self, request, context):
|
||||
flavors = self.flavors(request)
|
||||
if flavors:
|
||||
return instance_utils.sort_flavor_list(request, flavors)
|
||||
return []
|
||||
|
||||
@memoized.memoized_method
|
||||
def datastores(self, request):
|
||||
try:
|
||||
return api.trove.datastore_list(request)
|
||||
except Exception:
|
||||
LOG.exception("Exception while obtaining datastores list")
|
||||
self._datastores = []
|
||||
|
||||
@memoized.memoized_method
|
||||
def datastore_versions(self, request, datastore):
|
||||
try:
|
||||
return api.trove.datastore_version_list(request, datastore)
|
||||
except Exception:
|
||||
LOG.exception("Exception while obtaining datastore version list")
|
||||
self._datastore_versions = []
|
||||
|
||||
def populate_datastore_choices(self, request, context):
|
||||
choices = ()
|
||||
set_initial = False
|
||||
datastores = self.datastores(request)
|
||||
if datastores is not None:
|
||||
num_datastores_with_one_version = 0
|
||||
for ds in datastores:
|
||||
versions = self.datastore_versions(request, ds.name)
|
||||
if not set_initial:
|
||||
if len(versions) >= 2:
|
||||
set_initial = True
|
||||
elif len(versions) == 1:
|
||||
num_datastores_with_one_version += 1
|
||||
if num_datastores_with_one_version > 1:
|
||||
set_initial = True
|
||||
if versions:
|
||||
# only add to choices if datastore has at least one version
|
||||
version_choices = ()
|
||||
for v in versions:
|
||||
version_choices = (version_choices +
|
||||
((ds.name + ',' + v.name, v.name),))
|
||||
datastore_choices = (ds.name, version_choices)
|
||||
choices = choices + (datastore_choices,)
|
||||
if set_initial:
|
||||
# prepend choice to force user to choose
|
||||
initial = (('select_datastore_type_version',
|
||||
_('Select datastore type and version')))
|
||||
choices = (initial,) + choices
|
||||
return choices
|
||||
|
||||
|
||||
TROVE_ADD_USER_PERMS = getattr(settings, 'TROVE_ADD_USER_PERMS', [])
|
||||
TROVE_ADD_DATABASE_PERMS = getattr(settings, 'TROVE_ADD_DATABASE_PERMS', [])
|
||||
TROVE_ADD_PERMS = TROVE_ADD_USER_PERMS + TROVE_ADD_DATABASE_PERMS
|
||||
|
||||
|
||||
class SetInstanceDetails(workflows.Step):
|
||||
action_class = SetInstanceDetailsAction
|
||||
contributes = ("name", "volume", "flavor", "datastore")
|
||||
|
||||
|
||||
class SetNetworkAction(workflows.Action):
|
||||
network = forms.MultipleChoiceField(label=_("Networks"),
|
||||
widget=forms.CheckboxSelectMultiple(),
|
||||
error_messages={
|
||||
'required': _(
|
||||
"At least one network must"
|
||||
" be specified.")},
|
||||
help_text=_("Launch instance with"
|
||||
" these networks"))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(SetNetworkAction, self).__init__(request, *args, **kwargs)
|
||||
network_list = self.fields["network"].choices
|
||||
if len(network_list) == 1:
|
||||
self.fields['network'].initial = [network_list[0][0]]
|
||||
|
||||
class Meta(object):
|
||||
name = _("Networking")
|
||||
permissions = ('openstack.services.network',)
|
||||
help_text = _("Select networks for your instance.")
|
||||
|
||||
def populate_network_choices(self, request, context):
|
||||
try:
|
||||
tenant_id = self.request.user.tenant_id
|
||||
networks = dash_api.neutron.network_list_for_tenant(request,
|
||||
tenant_id)
|
||||
network_list = [(network.id, network.name_or_id)
|
||||
for network in networks]
|
||||
except Exception:
|
||||
network_list = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve networks.'))
|
||||
return network_list
|
||||
|
||||
|
||||
class SetNetwork(workflows.Step):
|
||||
action_class = SetNetworkAction
|
||||
template_name = "project/databases/_launch_networks.html"
|
||||
contributes = ("network_id",)
|
||||
|
||||
def contribute(self, data, context):
|
||||
if data:
|
||||
networks = self.workflow.request.POST.getlist("network")
|
||||
# If no networks are explicitly specified, network list
|
||||
# contains an empty string, so remove it.
|
||||
networks = [n for n in networks if n != '']
|
||||
if networks:
|
||||
context['network_id'] = networks
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class AddDatabasesAction(workflows.Action):
|
||||
"""Initialize the database with users/databases. This tab will honor
|
||||
the settings which should be a list of permissions required:
|
||||
|
||||
* TROVE_ADD_USER_PERMS = []
|
||||
* TROVE_ADD_DATABASE_PERMS = []
|
||||
"""
|
||||
databases = forms.CharField(label=_('Initial Databases'),
|
||||
required=False,
|
||||
help_text=_('Comma separated list of '
|
||||
'databases to create'))
|
||||
user = forms.CharField(label=_('Initial Admin User'),
|
||||
required=False,
|
||||
help_text=_("Initial admin user to add"))
|
||||
password = forms.CharField(widget=forms.PasswordInput(),
|
||||
label=_("Password"),
|
||||
required=False)
|
||||
host = forms.CharField(label=_("Allowed Host (optional)"),
|
||||
required=False,
|
||||
help_text=_("Host or IP that the user is allowed "
|
||||
"to connect through."))
|
||||
|
||||
class Meta(object):
|
||||
name = _("Initialize Databases")
|
||||
permissions = TROVE_ADD_PERMS
|
||||
help_text_template = "project/databases/_launch_initialize_help.html"
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AddDatabasesAction, self).clean()
|
||||
if cleaned_data.get('user'):
|
||||
if not cleaned_data.get('password'):
|
||||
msg = _('You must specify a password if you create a user.')
|
||||
self._errors["password"] = self.error_class([msg])
|
||||
if not cleaned_data.get('databases'):
|
||||
msg = _('You must specify at least one database if '
|
||||
'you create a user.')
|
||||
self._errors["databases"] = self.error_class([msg])
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class InitializeDatabase(workflows.Step):
|
||||
action_class = AddDatabasesAction
|
||||
contributes = ["databases", 'user', 'password', 'host']
|
||||
|
||||
|
||||
class AdvancedAction(workflows.Action):
|
||||
initial_state = forms.ChoiceField(
|
||||
label=_('Source for Initial State'),
|
||||
required=False,
|
||||
help_text=_("Choose initial state."),
|
||||
choices=[
|
||||
('', _('None')),
|
||||
('backup', _('Restore from Backup')),
|
||||
('master', _('Replicate from Instance'))],
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'initial_state'
|
||||
}))
|
||||
backup = forms.ChoiceField(
|
||||
label=_('Backup Name'),
|
||||
required=False,
|
||||
help_text=_('Select a backup to restore'),
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'initial_state',
|
||||
'data-initial_state-backup': _('Backup Name')
|
||||
}))
|
||||
master = forms.ChoiceField(
|
||||
label=_('Master Instance Name'),
|
||||
required=False,
|
||||
help_text=_('Select a master instance'),
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'initial_state',
|
||||
'data-initial_state-master': _('Master Instance Name')
|
||||
}))
|
||||
|
||||
class Meta(object):
|
||||
name = _("Advanced")
|
||||
help_text_template = "project/databases/_launch_advanced_help.html"
|
||||
|
||||
def populate_backup_choices(self, request, context):
|
||||
try:
|
||||
backups = api.trove.backup_list(request)
|
||||
choices = [(b.id, b.name) for b in backups
|
||||
if b.status == 'COMPLETED']
|
||||
except Exception:
|
||||
choices = []
|
||||
|
||||
if choices:
|
||||
choices.insert(0, ("", _("Select backup")))
|
||||
else:
|
||||
choices.insert(0, ("", _("No backups available")))
|
||||
return choices
|
||||
|
||||
def populate_master_choices(self, request, context):
|
||||
try:
|
||||
instances = api.trove.instance_list(request)
|
||||
choices = [(i.id, i.name) for i in
|
||||
instances if i.status == 'ACTIVE']
|
||||
except Exception:
|
||||
choices = []
|
||||
|
||||
if choices:
|
||||
choices.insert(0, ("", _("Select instance")))
|
||||
else:
|
||||
choices.insert(0, ("", _("No instances available")))
|
||||
return choices
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AdvancedAction, self).clean()
|
||||
|
||||
initial_state = cleaned_data.get("initial_state")
|
||||
|
||||
if initial_state == 'backup':
|
||||
backup = self.cleaned_data['backup']
|
||||
if backup:
|
||||
try:
|
||||
bkup = api.trove.backup_get(self.request, backup)
|
||||
self.cleaned_data['backup'] = bkup.id
|
||||
except Exception:
|
||||
raise forms.ValidationError(_("Unable to find backup!"))
|
||||
else:
|
||||
raise forms.ValidationError(_("A backup must be selected!"))
|
||||
|
||||
cleaned_data['master'] = None
|
||||
elif initial_state == 'master':
|
||||
master = self.cleaned_data['master']
|
||||
if master:
|
||||
try:
|
||||
api.trove.instance_get(self.request, master)
|
||||
except Exception:
|
||||
raise forms.ValidationError(
|
||||
_("Unable to find master instance!"))
|
||||
else:
|
||||
raise forms.ValidationError(
|
||||
_("A master instance must be selected!"))
|
||||
|
||||
cleaned_data['backup'] = None
|
||||
else:
|
||||
cleaned_data['master'] = None
|
||||
cleaned_data['backup'] = None
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class Advanced(workflows.Step):
|
||||
action_class = AdvancedAction
|
||||
contributes = ['backup', 'master']
|
||||
|
||||
|
||||
class LaunchInstance(workflows.Workflow):
|
||||
slug = "launch_instance"
|
||||
name = _("Launch Instance")
|
||||
finalize_button_name = _("Launch")
|
||||
success_message = _('Launched %(count)s named "%(name)s".')
|
||||
failure_message = _('Unable to launch %(count)s named "%(name)s".')
|
||||
success_url = "horizon:project:databases:index"
|
||||
default_steps = (SetInstanceDetails,
|
||||
SetNetwork,
|
||||
InitializeDatabase,
|
||||
Advanced)
|
||||
|
||||
def __init__(self, request=None, context_seed=None, entry_point=None,
|
||||
*args, **kwargs):
|
||||
super(LaunchInstance, self).__init__(request, context_seed,
|
||||
entry_point, *args, **kwargs)
|
||||
self.attrs['autocomplete'] = (
|
||||
settings.HORIZON_CONFIG.get('password_autocomplete'))
|
||||
|
||||
def format_status_message(self, message):
|
||||
name = self.context.get('name', 'unknown instance')
|
||||
return message % {"count": _("instance"), "name": name}
|
||||
|
||||
def _get_databases(self, context):
|
||||
"""Returns the initial databases for this instance."""
|
||||
databases = None
|
||||
if context.get('databases'):
|
||||
dbs = context['databases']
|
||||
databases = [{'name': d.strip()} for d in dbs.split(',')]
|
||||
return databases
|
||||
|
||||
def _get_users(self, context):
|
||||
users = None
|
||||
if context.get('user'):
|
||||
user = {
|
||||
'name': context['user'],
|
||||
'password': context['password'],
|
||||
'databases': self._get_databases(context),
|
||||
}
|
||||
if context['host']:
|
||||
user['host'] = context['host']
|
||||
users = [user]
|
||||
return users
|
||||
|
||||
def _get_backup(self, context):
|
||||
backup = None
|
||||
if context.get('backup'):
|
||||
backup = {'backupRef': context['backup']}
|
||||
return backup
|
||||
|
||||
def _get_nics(self, context):
|
||||
netids = context.get('network_id', None)
|
||||
if netids:
|
||||
return [{"net-id": netid, "v4-fixed-ip": ""}
|
||||
for netid in netids]
|
||||
else:
|
||||
return None
|
||||
|
||||
def handle(self, request, context):
|
||||
try:
|
||||
datastore = self.context['datastore'].split(',')[0]
|
||||
datastore_version = self.context['datastore'].split(',')[1]
|
||||
LOG.info("Launching database instance with parameters "
|
||||
"{name=%s, volume=%s, flavor=%s, "
|
||||
"datastore=%s, datastore_version=%s, "
|
||||
"dbs=%s, users=%s, "
|
||||
"backups=%s, nics=%s, replica_of=%s}",
|
||||
context['name'], context['volume'], context['flavor'],
|
||||
datastore, datastore_version,
|
||||
self._get_databases(context), self._get_users(context),
|
||||
self._get_backup(context), self._get_nics(context),
|
||||
context.get('master'))
|
||||
api.trove.instance_create(request,
|
||||
context['name'],
|
||||
context['volume'],
|
||||
context['flavor'],
|
||||
datastore=datastore,
|
||||
datastore_version=datastore_version,
|
||||
databases=self._get_databases(context),
|
||||
users=self._get_users(context),
|
||||
restore_point=self._get_backup(context),
|
||||
nics=self._get_nics(context),
|
||||
replica_of=context.get('master'))
|
||||
return True
|
||||
except Exception:
|
||||
exceptions.handle(request)
|
||||
return False
|
@ -1,38 +0,0 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from troveclient import client as trove_client
|
||||
|
||||
from openstack_dashboard.test import helpers
|
||||
|
||||
from openstack_dashboard.contrib.trove import api
|
||||
|
||||
|
||||
class TroveAPITestCase(helpers.APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TroveAPITestCase, self).setUp()
|
||||
|
||||
self._original_troveclient = api.trove.client
|
||||
api.trove.client = lambda request: self.stub_troveclient()
|
||||
|
||||
def tearDown(self):
|
||||
super(TroveAPITestCase, self).tearDown()
|
||||
|
||||
api.trove.client = self._original_troveclient
|
||||
|
||||
def stub_troveclient(self):
|
||||
if not hasattr(self, "troveclient"):
|
||||
self.mox.StubOutWithMock(trove_client, 'Client')
|
||||
self.troveclient = self.mox.CreateMock(trove_client.Client)
|
||||
return self.troveclient
|
@ -72,7 +72,6 @@ class SystemInfoViewTests(test.BaseAdminViewTests):
|
||||
'<Service: ec2>',
|
||||
'<Service: metering>',
|
||||
'<Service: orchestration>',
|
||||
'<Service: database>',
|
||||
])
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
@ -1,8 +0,0 @@
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
# The slug of the panel group to be added to HORIZON_CONFIG. Required.
|
||||
PANEL_GROUP = 'database'
|
||||
# The display name of the PANEL_GROUP. Required.
|
||||
PANEL_GROUP_NAME = _('Database')
|
||||
# The slug of the dashboard the PANEL_GROUP associated with. Required.
|
||||
PANEL_GROUP_DASHBOARD = 'project'
|
@ -1,10 +0,0 @@
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'databases'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'project'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'database'
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = ('openstack_dashboard.contrib.trove.'
|
||||
'content.databases.panel.Databases')
|
@ -1,10 +0,0 @@
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'database_backups'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'project'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'database'
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = ('openstack_dashboard.contrib.trove.'
|
||||
'content.database_backups.panel.Backups')
|
@ -1,25 +0,0 @@
|
||||
# Copyright [2015] Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'database_clusters'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'project'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'database'
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = ('openstack_dashboard.contrib.trove.'
|
||||
'content.database_clusters.panel.Clusters')
|
@ -24,7 +24,6 @@ from neutronclient.common import exceptions as neutronclient
|
||||
from novaclient import exceptions as novaclient
|
||||
from requests import exceptions as requests
|
||||
from swiftclient import client as swiftclient
|
||||
from troveclient import exceptions as troveclient
|
||||
|
||||
|
||||
UNAUTHORIZED = (
|
||||
@ -34,7 +33,6 @@ UNAUTHORIZED = (
|
||||
glanceclient.Unauthorized,
|
||||
neutronclient.Unauthorized,
|
||||
heatclient.HTTPUnauthorized,
|
||||
troveclient.Unauthorized,
|
||||
)
|
||||
|
||||
|
||||
@ -45,7 +43,6 @@ NOT_FOUND = (
|
||||
glanceclient.NotFound,
|
||||
neutronclient.NotFound,
|
||||
heatclient.HTTPNotFound,
|
||||
troveclient.NotFound,
|
||||
)
|
||||
|
||||
|
||||
@ -67,6 +64,5 @@ RECOVERABLE = (
|
||||
swiftclient.ClientException,
|
||||
heatclient.HTTPForbidden,
|
||||
heatclient.HTTPException,
|
||||
troveclient.ClientException,
|
||||
requests.RequestException,
|
||||
)
|
||||
|
@ -401,6 +401,9 @@ TIME_ZONE = "UTC"
|
||||
# 'telemetry': 'ceilometer_policy.json',
|
||||
#}
|
||||
|
||||
# TODO: (david-lyle) remove when plugins support adding settings.
|
||||
# Note: Only used when trove-dashboard plugin is configured to be used by
|
||||
# Horizon.
|
||||
# Trove user and database extension support. By default support for
|
||||
# creating users and databases on database instances is turned on.
|
||||
# To disable these extensions set the permission here to something
|
||||
@ -486,11 +489,6 @@ LOGGING = {
|
||||
'level': 'DEBUG',
|
||||
'propagate': False,
|
||||
},
|
||||
'troveclient': {
|
||||
'handlers': ['console'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': False,
|
||||
},
|
||||
'swiftclient': {
|
||||
'handlers': ['console'],
|
||||
'level': 'DEBUG',
|
||||
@ -643,6 +641,9 @@ SECURITY_GROUP_RULES = {
|
||||
#
|
||||
# See Metadata Definitions on: http://docs.openstack.org/developer/glance/
|
||||
|
||||
# TODO: (david-lyle) remove when plugins support settings natively
|
||||
# Note: This is only used when the Sahara plugin is configured and enabled
|
||||
# for use in Horizon.
|
||||
# Indicate to the Sahara data processing service whether or not
|
||||
# automatic floating IP allocation is in effect. If it is not
|
||||
# in effect, the user will be prompted to choose a floating IP
|
||||
|
@ -20,7 +20,6 @@ from neutronclient.common import exceptions as neutron_exceptions
|
||||
from novaclient import exceptions as nova_exceptions
|
||||
import six
|
||||
from swiftclient import client as swift_exceptions
|
||||
from troveclient import exceptions as trove_exceptions
|
||||
|
||||
from openstack_dashboard.test.test_data import utils
|
||||
|
||||
@ -85,10 +84,3 @@ def data(TEST):
|
||||
|
||||
cinder_exception = cinder_exceptions.BadRequest
|
||||
TEST.exceptions.cinder = create_stubbed_exception(cinder_exception)
|
||||
|
||||
trove_exception = trove_exceptions.ClientException
|
||||
TEST.exceptions.trove = create_stubbed_exception(trove_exception)
|
||||
|
||||
trove_auth = trove_exceptions.Unauthorized
|
||||
TEST.exceptions.trove_unauthorized = \
|
||||
create_stubbed_exception(trove_auth)
|
||||
|
@ -127,15 +127,7 @@ SERVICE_CATALOG = [
|
||||
{"region": "RegionOne",
|
||||
"adminURL": "http://admin.heat.example.com:8004/v1",
|
||||
"publicURL": "http://public.heat.example.com:8004/v1",
|
||||
"internalURL": "http://int.heat.example.com:8004/v1"}]},
|
||||
{"type": "database",
|
||||
"name": "Trove",
|
||||
"endpoints_links": [],
|
||||
"endpoints": [
|
||||
{"region": "RegionOne",
|
||||
"adminURL": "http://admin.trove.example.com:8779/v1.0",
|
||||
"publicURL": "http://public.trove.example.com:8779/v1.0",
|
||||
"internalURL": "http://int.trove.example.com:8779/v1.0"}]}
|
||||
"internalURL": "http://int.heat.example.com:8004/v1"}]}
|
||||
]
|
||||
|
||||
|
||||
|
@ -1,344 +0,0 @@
|
||||
# Copyright 2013 Rackspace Hosting.
|
||||
# Copyright 2015 HP Software, LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from troveclient.v1 import backups
|
||||
from troveclient.v1 import clusters
|
||||
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
|
||||
|
||||
from openstack_dashboard.test.test_data import utils
|
||||
|
||||
|
||||
CLUSTER_DATA_ONE = {
|
||||
"status": "ACTIVE",
|
||||
"id": "dfbbd9ca-b5e1-4028-adb7-f78643e17998",
|
||||
"name": "Test Cluster",
|
||||
"created": "2014-04-25T20:19:23",
|
||||
"updated": "2014-04-25T20:19:23",
|
||||
"links": [],
|
||||
"datastore": {
|
||||
"type": "mongodb",
|
||||
"version": "2.6"
|
||||
},
|
||||
"ip": ["10.0.0.1"],
|
||||
"instances": [
|
||||
{
|
||||
"id": "416b0b16-ba55-4302-bbd3-ff566032e1c1",
|
||||
"shard_id": "5415b62f-f301-4e84-ba90-8ab0734d15a7",
|
||||
"flavor": {
|
||||
"id": "7",
|
||||
"links": []
|
||||
},
|
||||
"volume": {
|
||||
"size": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "965ef811-7c1d-47fc-89f2-a89dfdd23ef2",
|
||||
"shard_id": "5415b62f-f301-4e84-ba90-8ab0734d15a7",
|
||||
"flavor": {
|
||||
"id": "7",
|
||||
"links": []
|
||||
},
|
||||
"volume": {
|
||||
"size": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3642f41c-e8ad-4164-a089-3891bf7f2d2b",
|
||||
"shard_id": "5415b62f-f301-4e84-ba90-8ab0734d15a7",
|
||||
"flavor": {
|
||||
"id": "7",
|
||||
"links": []
|
||||
},
|
||||
"volume": {
|
||||
"size": 100
|
||||
}
|
||||
}
|
||||
],
|
||||
"task": {
|
||||
"name": "test_task"
|
||||
}
|
||||
}
|
||||
|
||||
CLUSTER_DATA_TWO = {
|
||||
"status": "ACTIVE",
|
||||
"id": "dfbbd9ca-b5e1-4028-adb7-f78643e17998",
|
||||
"name": "Test Cluster",
|
||||
"created": "2014-04-25T20:19:23",
|
||||
"updated": "2014-04-25T20:19:23",
|
||||
"links": [],
|
||||
"datastore": {
|
||||
"type": "vertica",
|
||||
"version": "7.1"
|
||||
},
|
||||
"ip": ["10.0.0.1"],
|
||||
"instances": [
|
||||
{
|
||||
"id": "416b0b16-ba55-4302-bbd3-ff566032e1c1",
|
||||
"flavor": {
|
||||
"id": "7",
|
||||
"links": []
|
||||
},
|
||||
"volume": {
|
||||
"size": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "965ef811-7c1d-47fc-89f2-a89dfdd23ef2",
|
||||
"flavor": {
|
||||
"id": "7",
|
||||
"links": []
|
||||
},
|
||||
"volume": {
|
||||
"size": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3642f41c-e8ad-4164-a089-3891bf7f2d2b",
|
||||
"flavor": {
|
||||
"id": "7",
|
||||
"links": []
|
||||
},
|
||||
"volume": {
|
||||
"size": 100
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
DATABASE_DATA_ONE = {
|
||||
"status": "ACTIVE",
|
||||
"updated": "2013-08-12T22:00:09",
|
||||
"name": "Test Database",
|
||||
"links": [],
|
||||
"created": "2013-08-12T22:00:03",
|
||||
"ip": [
|
||||
"10.0.0.3",
|
||||
],
|
||||
"volume": {
|
||||
"used": 0.13,
|
||||
"size": 1,
|
||||
},
|
||||
"flavor": {
|
||||
"id": "1",
|
||||
"links": [],
|
||||
},
|
||||
"datastore": {
|
||||
"type": "mysql",
|
||||
"version": "5.5"
|
||||
},
|
||||
"id": "6ddc36d9-73db-4e23-b52e-368937d72719",
|
||||
}
|
||||
|
||||
DATABASE_DATA_TWO = {
|
||||
"status": "ACTIVE",
|
||||
"updated": "2013-08-12T22:00:09",
|
||||
"name": "Test Database With DNS",
|
||||
"links": [],
|
||||
"created": "2013-08-12T22:00:03",
|
||||
"hostname": "trove.instance-2.com",
|
||||
"volume": {
|
||||
"used": 0.13,
|
||||
"size": 1,
|
||||
},
|
||||
"flavor": {
|
||||
"id": "1",
|
||||
"links": [],
|
||||
},
|
||||
"datastore": {
|
||||
"type": "mysql",
|
||||
"version": "5.6"
|
||||
},
|
||||
"id": "4d7b3f57-44f5-41d2-8e86-36b88cad572a",
|
||||
}
|
||||
|
||||
BACKUP_ONE = {
|
||||
"instance_id": "6ddc36d9-73db-4e23-b52e-368937d72719",
|
||||
"status": "COMPLETED",
|
||||
"updated": "2013-08-13T19:39:38",
|
||||
"locationRef": "http://swift/v1/AUTH/database_backups/0edb.tar.gz",
|
||||
"name": "backup1",
|
||||
"created": "2013-08-15T18:10:14",
|
||||
"size": 0.13,
|
||||
"id": "0edb3c14-8919-4583-9add-00df9e524081",
|
||||
"description": "Long description of backup",
|
||||
}
|
||||
|
||||
|
||||
BACKUP_TWO = {
|
||||
"instance_id": "4d7b3f57-44f5-41d2-8e86-36b88cad572a",
|
||||
"status": "COMPLETED",
|
||||
"updated": "2013-08-10T20:20:44",
|
||||
"locationRef": "http://swift/v1/AUTH/database_backups/e460.tar.gz",
|
||||
"name": "backup2",
|
||||
"created": "2013-08-10T20:20:37",
|
||||
"size": 0.13,
|
||||
"id": "e4602a3c-2bca-478f-b059-b6c215510fb4",
|
||||
"description": "Longer description of backup",
|
||||
}
|
||||
|
||||
|
||||
BACKUP_TWO_INC = {
|
||||
"instance_id": "4d7b3f57-44f5-41d2-8e86-36b88cad572a",
|
||||
"status": "COMPLETED",
|
||||
"updated": "2013-08-10T20:20:55",
|
||||
"locationRef": "http://swift/v1/AUTH/database_backups/f145.tar.gz",
|
||||
"name": "backup2-Incr",
|
||||
"created": "2013-08-10T20:20:37",
|
||||
"size": 0.13,
|
||||
"id": "e4602a3c-2bca-478f-b059-b6c215510fb5",
|
||||
"description": "Longer description of backup",
|
||||
"parent_id": "e4602a3c-2bca-478f-b059-b6c215510fb4",
|
||||
}
|
||||
|
||||
USER_ONE = {
|
||||
"name": "Test_User",
|
||||
"host": "%",
|
||||
"databases": [DATABASE_DATA_ONE["name"]],
|
||||
}
|
||||
|
||||
|
||||
USER_DB_ONE = {
|
||||
"name": "db1",
|
||||
}
|
||||
|
||||
DATASTORE_ONE = {
|
||||
"id": "537fb940-b5eb-40d9-bdbd-91a3dcb9c17d",
|
||||
"links": [],
|
||||
"name": "mysql"
|
||||
}
|
||||
|
||||
DATASTORE_TWO = {
|
||||
"id": "ccb31517-c472-409d-89b4-1a13db6bdd36",
|
||||
"links": [],
|
||||
"name": "mysql"
|
||||
}
|
||||
|
||||
DATASTORE_MONGODB = {
|
||||
"id": "ccb31517-c472-409d-89b4-1a13db6bdd37",
|
||||
"links": [],
|
||||
"name": "mongodb"
|
||||
}
|
||||
|
||||
VERSION_ONE = {
|
||||
"name": "5.5",
|
||||
"links": [],
|
||||
"image": "b7956bb5-920e-4299-b68e-2347d830d939",
|
||||
"active": 1,
|
||||
"datastore": "537fb940-b5eb-40d9-bdbd-91a3dcb9c17d",
|
||||
"packages": "5.5",
|
||||
"id": "390a6d52-8347-4e00-8e4c-f4fa9cf96ae9"
|
||||
}
|
||||
|
||||
VERSION_TWO = {
|
||||
"name": "5.6",
|
||||
"links": [],
|
||||
"image": "c7956bb5-920e-4299-b68e-2347d830d938",
|
||||
"active": 1,
|
||||
"datastore": "537fb940-b5eb-40d9-bdbd-91a3dcb9c17d",
|
||||
"packages": "5.6",
|
||||
"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"
|
||||
}
|
||||
|
||||
VERSION_MONGODB_2_6 = {
|
||||
"name": "2.6",
|
||||
"links": [],
|
||||
"image": "c7956bb5-920e-4299-b68e-2347d830d937",
|
||||
"active": 1,
|
||||
"datastore": "ccb31517-c472-409d-89b4-1a13db6bdd37",
|
||||
"packages": "2.6",
|
||||
"id": "600a6d52-8347-4e00-8e4c-f4fa9cf96ae9"
|
||||
}
|
||||
|
||||
|
||||
def data(TEST):
|
||||
cluster1 = clusters.Cluster(clusters.Clusters(None),
|
||||
CLUSTER_DATA_ONE)
|
||||
cluster2 = clusters.Cluster(clusters.Clusters(None),
|
||||
CLUSTER_DATA_TWO)
|
||||
database1 = instances.Instance(instances.Instances(None),
|
||||
DATABASE_DATA_ONE)
|
||||
database2 = instances.Instance(instances.Instances(None),
|
||||
DATABASE_DATA_TWO)
|
||||
bkup1 = backups.Backup(backups.Backups(None), BACKUP_ONE)
|
||||
bkup2 = backups.Backup(backups.Backups(None), BACKUP_TWO)
|
||||
bkup3 = backups.Backup(backups.Backups(None), BACKUP_TWO_INC)
|
||||
user1 = users.User(users.Users(None), USER_ONE)
|
||||
user_db1 = databases.Database(databases.Databases(None),
|
||||
USER_DB_ONE)
|
||||
|
||||
datastore1 = datastores.Datastore(datastores.Datastores(None),
|
||||
DATASTORE_ONE)
|
||||
version1 = datastores.\
|
||||
DatastoreVersion(datastores.DatastoreVersions(None),
|
||||
VERSION_ONE)
|
||||
|
||||
flavor1 = flavors.Flavor(flavors.Flavors(None), FLAVOR_ONE)
|
||||
flavor2 = flavors.Flavor(flavors.Flavors(None), FLAVOR_TWO)
|
||||
flavor3 = flavors.Flavor(flavors.Flavors(None), FLAVOR_THREE)
|
||||
datastore_mongodb = datastores.Datastore(datastores.Datastores(None),
|
||||
DATASTORE_MONGODB)
|
||||
version_mongodb_2_6 = datastores.\
|
||||
DatastoreVersion(datastores.DatastoreVersions(None),
|
||||
VERSION_MONGODB_2_6)
|
||||
|
||||
TEST.trove_clusters = utils.TestDataContainer()
|
||||
TEST.trove_clusters.add(cluster1)
|
||||
TEST.trove_clusters.add(cluster2)
|
||||
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)
|
||||
TEST.database_backups.add(bkup2)
|
||||
TEST.database_backups.add(bkup3)
|
||||
TEST.database_users.add(user1)
|
||||
TEST.database_user_dbs.add(user_db1)
|
||||
TEST.datastores = utils.TestDataContainer()
|
||||
TEST.datastores.add(datastore1)
|
||||
TEST.datastores.add(datastore_mongodb)
|
||||
TEST.database_flavors.add(flavor1, flavor2, flavor3)
|
||||
TEST.datastore_versions = utils.TestDataContainer()
|
||||
TEST.datastore_versions.add(version_mongodb_2_6)
|
||||
TEST.datastore_versions.add(version1)
|
@ -23,7 +23,6 @@ def load_test_data(load_onto=None):
|
||||
from openstack_dashboard.test.test_data import neutron_data
|
||||
from openstack_dashboard.test.test_data import nova_data
|
||||
from openstack_dashboard.test.test_data import swift_data
|
||||
from openstack_dashboard.test.test_data import trove_data
|
||||
|
||||
# The order of these loaders matters, some depend on others.
|
||||
loaders = (
|
||||
@ -36,7 +35,6 @@ def load_test_data(load_onto=None):
|
||||
swift_data.data,
|
||||
heat_data.data,
|
||||
ceilometer_data.data,
|
||||
trove_data.data,
|
||||
)
|
||||
if load_onto:
|
||||
for data_func in loaders:
|
||||
|
Loading…
Reference in New Issue
Block a user