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:
nirajsingh 2018-01-30 17:09:37 +05:30
parent b18ad52f0e
commit a83abbd2b5
16 changed files with 426 additions and 2 deletions

View File

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

View File

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

View File

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Add Host" %}{% endblock %}
{% block main %}
{% include 'masakaridashboard/segment/_addhost.html' %}
{% endblock %}

View File

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

View File

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

View File

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