support for Masakari VMove

It supports for Masakari VMove API in microversion 1.3.

Change-Id: Id06f4d1dd8a4e59760bd63f5f7df6a73319d6a9c
This commit is contained in:
suzhengwei 2023-02-13 17:40:36 +08:00
parent eb4d103c3f
commit 91a51dfb12
16 changed files with 417 additions and 2 deletions

View File

@ -192,3 +192,16 @@ def get_notification_with_progress_details(request, notification_id):
return openstack_connection(
request, version='1.1').get_notification(
notification_id)
@handle_errors(_("Unable to get vmoves list"), [])
def get_vmoves_list(request, notification_id, filters):
"""return vmoves list """
return openstack_connection(
request, version='1.3').vmoves(notification_id, **filters)
def get_vmove(request, notification_id, vmove_id):
"""return single vmove"""
return openstack_connection(
request, version='1.3').get_vmove(vmove_id, notification_id)

View File

@ -7,3 +7,4 @@ os_masakari_api:extensions: rule:admin_api
os_masakari_api:segments: rule:admin_api
os_masakari_api:os-hosts: rule:admin_api
os_masakari_api:notifications: rule:admin_api
os_masakari_api:vmoves: rule:admin_api

View File

@ -28,7 +28,7 @@ LOG = logging.getLogger(__name__)
class MasakariDashboard(horizon.Dashboard):
slug = "masakaridashboard"
name = _("Instance-ha")
panels = ('default', 'segments', 'hosts', 'notifications')
panels = ('default', 'segments', 'hosts', 'notifications', 'vmoves')
default_panel = 'default'
policy_rules = (('instance-ha', 'context_is_admin'),)

View File

@ -19,6 +19,7 @@ from horizon import tabs
from masakaridashboard.api import api
from masakaridashboard.notifications import tables as notification_tab
from masakaridashboard.vmoves import tables as vmove_table
class OverviewTab(tabs.Tab):
@ -66,6 +67,29 @@ class NotificationProgressDetailsTab(tabs.TableTab):
return []
class VMoveTab(tabs.TableTab):
table_classes = (vmove_table.VMoveTable,)
name = _("VMoves")
slug = "vmove_tab"
template_name = "horizon/common/_detail_table.html"
preload = False
def get_vmove_data(self):
vmove_list = []
notification_type = self.tab_group.kwargs['type']
if notification_type != "COMPUTE_HOST":
return vmove_list
notification_id = self.tab_group.kwargs['notification_uuid']
vmove_gen = api.get_vmoves_list(
self.request, notification_id, filters={})
for item in vmove_gen:
vmove_list.append(item)
return vmove_list
class NotificationDetailTabs(tabs.DetailTabsGroup):
slug = "notification_details"
tabs = (OverviewTab, NotificationProgressDetailsTab)
tabs = (OverviewTab, NotificationProgressDetailsTab, VMoveTab)

View File

@ -19,6 +19,7 @@ from oslo_utils import timeutils
from openstack.instance_ha.v1 import host
from openstack.instance_ha.v1 import notification
from openstack.instance_ha.v1 import segment
from openstack.instance_ha.v1 import vmove
from openstack_dashboard.test.test_data import utils as test_data_utils
from masakaridashboard.test import uuidsentinel
@ -97,3 +98,18 @@ def data(TEST):
TEST.masakari_notification.add(notification1)
TEST.masakari_notification.add(notification2)
TEST.masakari_notification.add(notification3)
TEST.masakari_vmove = test_data_utils.TestDataContainer()
vmove1 = vmove.VMove(
uuid=uuidsentinel.vmove1,
notification_id=uuidsentinel.notification1,
server_id=uuidsentinel.server1,
server_name="vm",
source_host='host1',
dest_host='host2',
start_time=(NOW - datetime.timedelta(seconds=3)),
end_time=(NOW - datetime.timedelta(seconds=1)),
status='succeeded',
type='evacuation')
TEST.masakari_vmove.add(vmove1)

View File

View File

@ -0,0 +1,27 @@
# Copyright(c) 2022 Inspur
#
# 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 gettext_lazy as _
import horizon
from masakaridashboard import dashboard
class VMoves(horizon.Panel):
name = _("VMoves")
slug = 'vmoves'
dashboard.MasakariDashboard.register(VMoves)

View File

@ -0,0 +1,61 @@
# Copyright(c) 2022 Inspur
#
# 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 gettext_lazy as _
from horizon import tables
VMOVE_FILTER_CHOICES = (
('notification_id', _("Notification UUId ="), True),
('type', _("Type ="), True),
('status', _("Status ="), True),
)
class VMoveFilterAction(tables.FilterAction):
filter_type = "server"
filter_choices = VMOVE_FILTER_CHOICES
class VMoveTable(tables.DataTable):
uuid = tables.Column('uuid', verbose_name=_("UUID"),
link="horizon:masakaridashboard:vmoves:detail")
notification_id = tables.Column(
'notification_id', verbose_name=_("Notification UUID"),
link="horizon:masakaridashboard:notifications:detail")
server_id = tables.Column(
'server_id', verbose_name=_("Server ID"))
server_name = tables.Column(
'server_name', verbose_name=_("Server Name"))
type = tables.Column('type', verbose_name=_("Type"))
source_host = tables.Column(
'source_host', verbose_name=_("Source Host"))
dest_host = tables.Column(
'dest_host', verbose_name=_("Dest Host"))
start_time = tables.Column(
'start_time', verbose_name=_("Start Time"))
end_time = tables.Column(
'end_time', verbose_name=_("End Time"))
status = tables.Column(
'status', verbose_name=_("Status"))
def get_object_id(self, datum):
return datum.uuid + ',' + datum.notification_id
class Meta(object):
name = "vmove"
verbose_name = _("VMove")
table_actions = (VMoveFilterAction,)

View File

@ -0,0 +1,31 @@
# Copyright(c) 2022 Inspur
#
# 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 gettext_lazy as _
from horizon import tabs
class OverviewTab(tabs.Tab):
name = _("VMoves")
slug = "vmoves"
template_name = ("masakaridashboard/vmoves/_detail_overview.html")
def get_context_data(self, request):
return {"vmove": self.tab_group.kwargs['vmove']}
class VMoveDetailTabs(tabs.DetailTabsGroup):
slug = "vmove_details"
tabs = (OverviewTab,)

View File

@ -0,0 +1,25 @@
{% load i18n sizeformat parse_date %}
<div class="detail">
<h4>{% trans "VMove Detail" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt>{% trans "UUID" %}</dt>
<dd>{{ vmove.uuid }}</dd>
<dt>{% trans "Server ID" %}</dt>
<dd>{{ vmove.server_id }}</dd>
<dt>{% trans "Server Name" %}</dt>
<dd>{{ vmove.server_name }}</dd>
<dt>{% trans "Type" %}</dt>
<dd>{{ vmove.type }}</dd>
<dt>{% trans "Source Host" %}</dt>
<dd>{{ vmove.source_host }}</dd>
<dt>{% trans "Dest Host" %}</dt>
<dd>{{ vmove.dest_host }}</dd>
<dt>{% trans "Start Time" %}</dt>
<dd>{{ vmove.start_time }}</dd>
<dt>{% trans "End Time" %}</dt>
<dd>{{ vmove.end_time }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>{{ vmove.status }}</dd>
</dl>
</div>

View File

@ -0,0 +1,38 @@
<span class="masakari-wrapper detail-screen">
{% extends 'masakaridashboard/default/base.html' %}
{% load i18n %}
{% block title %}{% trans "VMove Detail" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("VMove Detail") %}
{% endblock page_header %}
{% block main %}
{% load i18n sizeformat parse_date %}
<div class="detail">
<h4>{% trans "VMove Detail" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt>{% trans "UUID" %}</dt>
<dd>{{ vmove.uuid }}</dd>
<dt>{% trans "Server ID" %}</dt>
<dd>{{ vmove.server_id }}</dd>
<dt>{% trans "Server Name" %}</dt>
<dd>{{ vmove.server_name }}</dd>
<dt>{% trans "Type" %}</dt>
<dd>{{ vmove.type }}</dd>
<dt>{% trans "Source Host" %}</dt>
<dd>{{ vmove.source_host }}</dd>
<dt>{% trans "Dest Host" %}</dt>
<dd>{{ vmove.dest_host }}</dd>
<dt>{% trans "Start Time" %}</dt>
<dd>{{ vmove.start_time }}</dd>
<dt>{% trans "End Time" %}</dt>
<dd>{{ vmove.end_time }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>{{ vmove.status }}</dd>
<dt>{% trans "Message" %}</dt>
<dd>{{ vmove.message }}</dd>
</dl>
</div>
{% endblock %}
</span>

View File

@ -0,0 +1,7 @@
{% extends 'masakaridashboard/default/table.html' %}
{% load i18n %}
{% block title %}{% trans "VMoves" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("VMoves") %}
{% endblock page_header %}

View File

@ -0,0 +1,51 @@
# Copyright(c) 2022 Inspur
#
# 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 unittest import mock
from django.urls import reverse
from masakaridashboard.test import helpers as test
INDEX_URL = reverse('horizon:masakaridashboard:vmoves:index')
class VMoveTest(test.TestCase):
def test_index(self):
vmoves = self.masakari_vmove.list()
notifications = self.masakari_notification.list()
with mock.patch('masakaridashboard.api.api.notification_list',
return_value=notifications), mock.patch(
'masakaridashboard.api.api.get_notification',
return_value=notifications[0]), mock.patch(
'masakaridashboard.api.api.get_vmoves_list',
return_value=vmoves):
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'masakaridashboard/vmoves/index.html')
self.assertEqual(res.status_code, 200)
def test_detail(self):
vmove = self.masakari_vmove.list()[0]
id_to_update = vmove.uuid + ',' + vmove.notification_id
detail_url = reverse('horizon:masakaridashboard:vmoves:detail',
args=[id_to_update])
with mock.patch('masakaridashboard.api.api.get_vmove',
return_value=self.masakari_vmove.list()[0]):
res = self.client.get(detail_url)
self.assertNoFormErrors(res)
self.assertEqual(200, res.status_code)
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
self.assertTemplateUsed(
res, 'masakaridashboard/vmoves/_detail_overview.html')

View File

@ -0,0 +1,24 @@
# Copyright(c) 2022 Inspur
#
# 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.urls import re_path
from masakaridashboard.vmoves import views
VMOVE = r'^(?P<vmove_id>[^/]+)/%s$'
urlpatterns = [
re_path(r'^$', views.IndexView.as_view(), name='index'),
re_path(VMOVE % 'detail', views.DetailView.as_view(), name='detail'),
]

View File

@ -0,0 +1,92 @@
# Copyright(c) 2022 Inspur
#
# 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.urls import reverse
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import tables
from horizon import tabs
from horizon.utils import memoized
from masakaridashboard.api import api
from masakaridashboard.vmoves import tables as masakari_tab
from masakaridashboard.vmoves import tabs as vmove_tab
class IndexView(tables.DataTableView):
table_class = masakari_tab.VMoveTable
template_name = 'masakaridashboard/vmoves/index.html'
page_title = _("VMoves")
def needs_filter_first(self, table):
return self._needs_filter_first
def get_data(self):
notifications = api.notification_list(self.request)
vmove_list = []
filters = self.get_filters()
self._needs_filter_first = True
filter_first = getattr(settings, 'FILTER_DATA_FIRST', {})
if filter_first.get('masakaridashboard.vmoves', False) and len(
filters) == 0:
self._needs_filter_first = True
self._more = False
return vmove_list
for notification in notifications:
vmove_gen = api.get_vmoves_list(
self.request, notification.notification_uuid, filters)
for item in vmove_gen:
vmove_list.append(item)
return vmove_list
class DetailView(tabs.TabbedTableView):
tab_group_class = vmove_tab.VMoveDetailTabs
template_name = 'horizon/common/_detail.html'
page_title = "{{ vmove.server_name|default:vmove.server_name }}"
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
vmove = self.get_data()
table = masakari_tab.VMoveTable(self.request)
context["vmove"] = vmove
context["url"] = self.get_redirect_url()
context["actions"] = table.render_row_actions(vmove)
return context
@memoized.memoized_method
def get_data(self):
try:
row_data = self.kwargs['vmove_id'].split(',')
notification_id = row_data[1]
vmove_id = row_data[0]
vmove = api.get_vmove(self.request, vmove_id, notification_id)
except Exception:
msg = _('Unable to get vmove "%s".') % vmove_id
redirect = reverse('horizon:masakaridashboard:vmoves:index')
exceptions.handle(self.request, msg, redirect=redirect)
return vmove
def get_redirect_url(self):
return reverse('horizon:masakaridashboard:vmoves:index')
def get_tabs(self, request, *args, **kwargs):
vmove = self.get_data()
return self.tab_group_class(request, vmove=vmove, **kwargs)

View File

@ -0,0 +1,5 @@
---
features:
- |
Adds support for Masakari VMove API in microversion 1.3.
`Blueprint vm-evacuations-for-host-recovery <https://blueprints.launchpad.net/masakari/+spec/vm-evacuations-for-host-recovery>`__