Add host panel
Added host panel and implemented add and list host functionality. Also added test cases that actually not covering the line of code but tested the add and list host functionally. Partial-Implements: blueprint masakari-dashboard Change-Id: I756ab397bd9c84a4520c4ed12a576832784ab41f
This commit is contained in:
parent
b18ad52f0e
commit
a83abbd2b5
|
@ -23,6 +23,7 @@ from horizon.utils import memoized
|
|||
from keystoneauth1.identity.generic import token
|
||||
from keystoneauth1 import session as ks_session
|
||||
from openstack import connection
|
||||
from openstack_dashboard.api import nova as nova_api
|
||||
|
||||
from masakaridashboard.handle_errors import handle_errors
|
||||
|
||||
|
@ -40,6 +41,10 @@ def openstack_connection(request):
|
|||
return conn.instance_ha
|
||||
|
||||
|
||||
def get_hypervisor_list(request):
|
||||
return nova_api.hypervisor_list(request)
|
||||
|
||||
|
||||
@handle_errors(_("Unable to retrieve segments"), [])
|
||||
def get_segment_list(request, marker='', paginate=False, filters=None):
|
||||
"""Returns segments as per page size."""
|
||||
|
@ -123,3 +128,21 @@ def segment_update(request, segment_id, fields_to_update):
|
|||
"""Update segment."""
|
||||
return openstack_connection(request).update_segment(
|
||||
segment_id, **fields_to_update)
|
||||
|
||||
|
||||
def create_host(request, data):
|
||||
"""Create Host."""
|
||||
attrs = {'name': data['name'],
|
||||
'reserved': data['reserved'],
|
||||
'type': data['type'],
|
||||
'control_attributes': data['control_attributes'],
|
||||
'on_maintenance': data['on_maintenance']}
|
||||
|
||||
return openstack_connection(request).create_host(
|
||||
data['segment_id'], **attrs)
|
||||
|
||||
|
||||
@handle_errors(_("Unable to get host list"), [])
|
||||
def get_host_list(request, segment_id, filters):
|
||||
"""Returns host list."""
|
||||
return openstack_connection(request).hosts(segment_id, **filters)
|
||||
|
|
|
@ -23,7 +23,7 @@ from masakaridashboard.default import panel
|
|||
class MasakariDashboard(horizon.Dashboard):
|
||||
slug = "masakaridashboard"
|
||||
name = _("Instance-ha")
|
||||
panels = ('default', 'segments')
|
||||
panels = ('default', 'segments', 'hosts')
|
||||
default_panel = 'default'
|
||||
policy_rules = (('instance-ha', 'context_is_admin'),)
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# Copyright (c) 2018 NTT DATA
|
||||
#
|
||||
# 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
|
||||
|
||||
from masakaridashboard import dashboard
|
||||
|
||||
|
||||
class Hosts(horizon.Panel):
|
||||
name = _("Hosts")
|
||||
slug = 'hosts'
|
||||
|
||||
|
||||
dashboard.MasakariDashboard.register(Hosts)
|
|
@ -0,0 +1,53 @@
|
|||
# Copyright (c) 2018 NTT DATA
|
||||
#
|
||||
# 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 _
|
||||
from horizon import tables
|
||||
|
||||
HOST_FILTER_CHOICES = (
|
||||
('failover_segment_id', _("Segment Id ="), True),
|
||||
('type', _("Type ="), True),
|
||||
('on_maintenance', _("On Maintenance ="), True),
|
||||
('reserved', _("Reserved ="), True),
|
||||
)
|
||||
|
||||
|
||||
class HostFilterAction(tables.FilterAction):
|
||||
filter_type = "server"
|
||||
filter_choices = HOST_FILTER_CHOICES
|
||||
|
||||
|
||||
class HostTable(tables.DataTable):
|
||||
|
||||
name = tables.Column('name', verbose_name=_("Name"))
|
||||
uuid = tables.Column('uuid', verbose_name=_("UUID"))
|
||||
reserved = tables.Column(
|
||||
'reserved', verbose_name=_("Reserved"))
|
||||
type = tables.Column(
|
||||
'type', verbose_name=_("Type"))
|
||||
control_attributes = tables.Column(
|
||||
'control_attributes', verbose_name=_(
|
||||
"Control Attribute"), truncate=40)
|
||||
on_maintenance = tables.Column(
|
||||
'on_maintenance', verbose_name=_("On Maintenance"))
|
||||
failover_segment_id = tables.Column(
|
||||
'failover_segment_id', verbose_name=_("Failover Segment"))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return datum.uuid + ',' + datum.failover_segment_id
|
||||
|
||||
class Meta(object):
|
||||
name = "host"
|
||||
verbose_name = _("Host")
|
||||
table_actions = (HostFilterAction,)
|
|
@ -0,0 +1,7 @@
|
|||
{% extends 'masakaridashboard/default/table.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Hosts" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Hosts") %}
|
||||
{% endblock page_header %}
|
|
@ -0,0 +1,72 @@
|
|||
# Copyright (C) 2018 NTT DATA
|
||||
# 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
|
||||
import mock
|
||||
|
||||
from masakaridashboard.test import helpers as test
|
||||
|
||||
|
||||
INDEX_URL = reverse('horizon:masakaridashboard:hosts:index')
|
||||
|
||||
|
||||
class HostTest(test.TestCase):
|
||||
|
||||
def test_index(self):
|
||||
hosts = self.masakari_host.list()
|
||||
segments = self.masakari_segment.list()
|
||||
with mock.patch('masakaridashboard.api.api.segment_list',
|
||||
return_value=segments), mock.patch(
|
||||
'masakaridashboard.api.api.get_segment',
|
||||
return_value=segments[0]), mock.patch(
|
||||
'masakaridashboard.api.api.get_host_list',
|
||||
return_value=hosts):
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'masakaridashboard/hosts/index.html')
|
||||
|
||||
def test_create_post(self):
|
||||
segment = self.masakari_segment.list()
|
||||
host = self.masakari_host.list()[0]
|
||||
hypervisors = self.hypervisors.list()
|
||||
create_url = reverse('horizon:masakaridashboard:segments:addhost',
|
||||
args=[segment[0].uuid])
|
||||
form_data = {
|
||||
'segment_id': host.failover_segment_id,
|
||||
'segment_name': segment[0].name,
|
||||
'name': host.name,
|
||||
'type': host.type,
|
||||
'reserved': '1',
|
||||
'control_attributes': host.control_attributes,
|
||||
'on_maintenance': '0'
|
||||
}
|
||||
with mock.patch('masakaridashboard.api.api.segment_list',
|
||||
return_value=segment), mock.patch(
|
||||
'masakaridashboard.api.api.get_host_list',
|
||||
return_value=[]), mock.patch(
|
||||
'masakaridashboard.api.api.get_hypervisor_list',
|
||||
return_value=hypervisors), mock.patch(
|
||||
'masakaridashboard.api.api.get_segment',
|
||||
return_value=segment[0]), mock.patch(
|
||||
'masakaridashboard.api.api.create_host',
|
||||
return_value=host) as mocked_create:
|
||||
res = self.client.post(create_url, form_data)
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertEqual(res.status_code, 302)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
mocked_create.assert_called_once_with(
|
||||
mock.ANY,
|
||||
form_data
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
# Copyright (c) 2018 NTT DATA
|
||||
#
|
||||
# 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 url
|
||||
|
||||
from masakaridashboard.hosts import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
]
|
|
@ -0,0 +1,47 @@
|
|||
# Copyright (c) 2018 NTT DATA
|
||||
#
|
||||
# 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 horizon import tables
|
||||
|
||||
from masakaridashboard.api import api
|
||||
from masakaridashboard.hosts import tables as masakari_tab
|
||||
|
||||
|
||||
class IndexView(tables.DataTableView):
|
||||
table_class = masakari_tab.HostTable
|
||||
template_name = 'masakaridashboard/hosts/index.html'
|
||||
|
||||
def needs_filter_first(self, table):
|
||||
return self._needs_filter_first
|
||||
|
||||
def get_data(self):
|
||||
segments = api.segment_list(self.request)
|
||||
host_list = []
|
||||
filters = self.get_filters()
|
||||
self._needs_filter_first = True
|
||||
|
||||
filter_first = getattr(settings, 'FILTER_DATA_FIRST', {})
|
||||
if filter_first.get('masakaridashboard.hosts', False) and len(
|
||||
filters) == 0:
|
||||
self._needs_filter_first = True
|
||||
self._more = False
|
||||
return host_list
|
||||
|
||||
for segment in segments:
|
||||
host_gen = api.get_host_list(self.request, segment.uuid, filters)
|
||||
for item in host_gen:
|
||||
host_list.append(item)
|
||||
|
||||
return host_list
|
|
@ -106,3 +106,70 @@ class UpdateForm(forms.SelfHandlingForm):
|
|||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class AddHostForm(forms.SelfHandlingForm):
|
||||
|
||||
segment_id = forms.CharField(widget=forms.HiddenInput())
|
||||
segment_name = forms.CharField(
|
||||
label=_('Segment Name'), widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}), required=False)
|
||||
name = forms.ChoiceField(label=_('Host Name'),
|
||||
choices=[])
|
||||
reserved = forms.ChoiceField(
|
||||
label=_('Reserved'),
|
||||
choices=[('0', 'False'),
|
||||
('1', 'True')],
|
||||
widget=forms.Select(
|
||||
attrs={'class': 'switchable',
|
||||
'data-slug': 'available host'}),
|
||||
required=False,
|
||||
help_text=_("A boolean indicating whether this host is reserved or"
|
||||
" not. Default value is set to False."))
|
||||
type = forms.CharField(
|
||||
label=_('Type'),
|
||||
widget=forms.TextInput(attrs={'maxlength': 255}),
|
||||
help_text=_("Type of host."))
|
||||
control_attributes = forms.CharField(
|
||||
label=_('Control Attribute'),
|
||||
widget=forms.TextInput(),
|
||||
help_text=_("Attributes to control host."))
|
||||
on_maintenance = forms.ChoiceField(
|
||||
label=_('On Maintenance'),
|
||||
choices=[('0', 'False'),
|
||||
('1', 'True')],
|
||||
widget=forms.Select(
|
||||
attrs={'class': 'switchable',
|
||||
'data-slug': 'available host'}),
|
||||
required=False,
|
||||
help_text=_("A boolean indicating whether this host is on maintenance"
|
||||
" or not. Default value is set to False."))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AddHostForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# Populate hypervisor name choices
|
||||
hypervisor_list = kwargs.get('initial', {}).get("hypervisor_list", [])
|
||||
hypervisor_name_list = []
|
||||
for hypervisor in hypervisor_list:
|
||||
hypervisor_name_list.append(
|
||||
(hypervisor.hypervisor_hostname, '%(name)s (%(id)s)'
|
||||
% {"name": hypervisor.hypervisor_hostname,
|
||||
"id": hypervisor.id}))
|
||||
if hypervisor_name_list:
|
||||
hypervisor_name_list.insert(0, ("", _("Select a host")))
|
||||
else:
|
||||
hypervisor_name_list.insert(0, ("", _("No host available")))
|
||||
self.fields['name'].choices = hypervisor_name_list
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
api.create_host(request, data)
|
||||
msg = _('Host created successfully.')
|
||||
messages.success(request, msg)
|
||||
except Exception:
|
||||
msg = _('Failed to create host.')
|
||||
redirect = reverse('horizon:masakaridashboard:segments:index')
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
return True
|
||||
|
|
|
@ -22,6 +22,18 @@ from masakaridashboard.api import api
|
|||
from horizon import tables
|
||||
|
||||
|
||||
class AddHost(tables.LinkAction):
|
||||
name = "add_host"
|
||||
verbose_name = _("Add Host")
|
||||
classes = ("ajax-modal",)
|
||||
|
||||
def get_link_url(self, datum):
|
||||
obj_id = datum.uuid
|
||||
url = "horizon:masakaridashboard:segments:addhost"
|
||||
|
||||
return reverse(url, args=[obj_id])
|
||||
|
||||
|
||||
class CreateSegment(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Segment")
|
||||
|
@ -96,4 +108,4 @@ class FailoverSegmentTable(tables.DataTable):
|
|||
name = "failover_segment"
|
||||
verbose_name = _("FailoverSegment")
|
||||
table_actions = (DeleteSegment, CreateSegment, SegmentFilterAction)
|
||||
row_actions = (UpdateSegment,)
|
||||
row_actions = (UpdateSegment, AddHost)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "Create a Host under a given segment with name, type and control_attributes."%}</p>
|
||||
<p>{% trans "Reserved : User can set specific host as reserved by checking on reserved parameter. On this particular host, compute service must be disabled. Default value is set to False." %}</p>
|
||||
<p>{% trans "On Maintenance: Boolean parameter indicating whether this host is under maintenance or not. Default value is set to False." %}</p>
|
||||
{% endblock %}
|
|
@ -0,0 +1,7 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Add Host" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'masakaridashboard/segment/_addhost.html' %}
|
||||
{% endblock %}
|
|
@ -25,4 +25,5 @@ urlpatterns = [
|
|||
name='create_segment'),
|
||||
url(SEGMENT % 'detail', views.DetailView.as_view(), name='detail'),
|
||||
url(SEGMENT % 'update', views.UpdateView.as_view(), name='update'),
|
||||
url(SEGMENT % 'addhost', views.AddHostView.as_view(), name='addhost'),
|
||||
]
|
||||
|
|
|
@ -170,3 +170,60 @@ class UpdateView(forms.ModalFormView):
|
|||
'name': segment.name,
|
||||
'recovery_method': segment.recovery_method,
|
||||
'description': segment.description}
|
||||
|
||||
|
||||
class AddHostView(forms.ModalFormView):
|
||||
template_name = 'masakaridashboard/segments/addhost.html'
|
||||
modal_header = _("Add Host")
|
||||
form_id = "add_host"
|
||||
form_class = segment_forms.AddHostForm
|
||||
submit_label = _("Add Host")
|
||||
submit_url = "horizon:masakaridashboard:segments:addhost"
|
||||
success_url = reverse_lazy("horizon:masakaridashboard:hosts:index")
|
||||
page_title = _("Add Host")
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_object(self):
|
||||
|
||||
segments = api.segment_list(self.request)
|
||||
host_list = []
|
||||
for segment in segments:
|
||||
host_gen = api.get_host_list(
|
||||
self.request, segment.uuid, filters={})
|
||||
for item in host_gen:
|
||||
host_list.append(item.name)
|
||||
try:
|
||||
available_host_list = []
|
||||
hypervisor_list = api.get_hypervisor_list(self.request)
|
||||
for hypervisor in hypervisor_list:
|
||||
if hypervisor.hypervisor_hostname not in host_list:
|
||||
available_host_list.append(hypervisor)
|
||||
return available_host_list
|
||||
except Exception:
|
||||
msg = _('Unable to retrieve host list.')
|
||||
redirect = reverse('horizon:masakaridashboard:segments:index')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddHostView, self).get_context_data(**kwargs)
|
||||
context['submit_url'] = reverse(
|
||||
self.submit_url,
|
||||
args=[self.kwargs["segment_id"]]
|
||||
)
|
||||
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
hypervisor_list = self.get_object()
|
||||
segment_name = api.get_segment(
|
||||
self.request, self.kwargs['segment_id']).name
|
||||
initial = {'segment_id': self.kwargs['segment_id'],
|
||||
'segment_name': segment_name,
|
||||
'hypervisor_list': hypervisor_list,
|
||||
'reserved': self.kwargs.get('reserved'),
|
||||
'type': self.kwargs.get('service_type'),
|
||||
'control_attributes': self.kwargs.get('control_attributes'),
|
||||
'on_maintenance': self.kwargs.get('on_maintenance')
|
||||
}
|
||||
|
||||
return initial
|
||||
|
|
|
@ -13,14 +13,18 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from openstack.instance_ha.v1 import host
|
||||
from openstack.instance_ha.v1 import segment
|
||||
|
||||
from openstack_dashboard.test.test_data import utils as test_data_utils
|
||||
|
||||
from masakaridashboard.test import uuidsentinel
|
||||
from novaclient.v2.hypervisors import Hypervisor
|
||||
from novaclient.v2.hypervisors import HypervisorManager
|
||||
|
||||
|
||||
def data(TEST):
|
||||
|
||||
TEST.masakari_segment = test_data_utils.TestDataContainer()
|
||||
|
||||
segment1 = segment.Segment(uuid=uuidsentinel.segment1, name='test',
|
||||
|
@ -36,3 +40,20 @@ def data(TEST):
|
|||
TEST.masakari_segment.add(segment1)
|
||||
TEST.masakari_segment.add(segment2)
|
||||
TEST.masakari_segment.add(segment3)
|
||||
|
||||
TEST.masakari_host = test_data_utils.TestDataContainer()
|
||||
|
||||
host1 = host.Host(uuid=uuidsentinel.host1, name="test",
|
||||
reserved=True, type='service',
|
||||
control_attributes='test',
|
||||
failover_segment_id=uuidsentinel.segment1,
|
||||
on_maintenance=False)
|
||||
|
||||
TEST.masakari_host.add(host1)
|
||||
|
||||
TEST.hypervisors = test_data_utils.TestDataContainer()
|
||||
|
||||
hypervisor1 = Hypervisor(
|
||||
HypervisorManager, {'id': '1', 'hypervisor_hostname': "test"})
|
||||
|
||||
TEST.hypervisors.add(hypervisor1)
|
||||
|
|
Loading…
Reference in New Issue