Merge "Excise Trove from Horizon"

This commit is contained in:
Jenkins 2015-12-30 18:04:45 +00:00 committed by Gerrit Code Review
commit 948fa99ae5
75 changed files with 7 additions and 5057 deletions

View File

@ -1,5 +0,0 @@
from openstack_dashboard.contrib.trove.api import trove
__all__ = [
"trove"
]

View File

@ -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)

View File

@ -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',)

View File

@ -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)

View File

@ -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>

View File

@ -1,7 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Backup Database" %}{% endblock %}
{% block main %}
{% include 'horizon/common/_workflow.html' %}
{% endblock %}

View File

@ -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 %}

View File

@ -1,7 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Database Backups" %}{% endblock %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -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')

View File

@ -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'),
)

View File

@ -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

View File

@ -1,3 +0,0 @@
# Importing non-modules that are not used explicitly
from .create_backup import CreateBackup # noqa

View File

@ -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

View File

@ -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

View File

@ -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',)

View File

@ -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")

View File

@ -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

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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])

View File

@ -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'),
)

View File

@ -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']}

View File

@ -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())

View File

@ -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

View File

@ -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',)

View File

@ -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)

View File

@ -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

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View File

@ -1,7 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Instances" %}{% endblock %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -1,7 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Launch Instance" %}{% endblock %}
{% block main %}
{% include 'horizon/common/_workflow.html' %}
{% endblock %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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)

View File

@ -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')
)

View File

@ -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

View File

@ -1,3 +0,0 @@
# Importing non-modules that are not used explicitly
from .create_instance import LaunchInstance # noqa

View File

@ -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

View File

@ -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

View File

@ -72,7 +72,6 @@ class SystemInfoViewTests(test.BaseAdminViewTests):
'<Service: ec2>',
'<Service: metering>',
'<Service: orchestration>',
'<Service: database>',
])
self.mox.VerifyAll()

View File

@ -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'

View File

@ -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')

View File

@ -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')

View File

@ -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')

View File

@ -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,
)

View File

@ -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

View File

@ -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)

View File

@ -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"}]}
]

View File

@ -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)

View File

@ -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: