Horizon implementation for sessions endpoint

Implements: blueprint freezer-api-web-ui

Change-Id: I2ff6b4bb77f50c98bb96fb812c67a391f7410789
This commit is contained in:
Memo Garcia 2015-07-05 16:04:41 +01:00
parent 206a0ec9b7
commit e5ec8452a8
16 changed files with 670 additions and 9 deletions

View File

@ -1 +0,0 @@
__author__ = 'jonas'

View File

@ -90,6 +90,18 @@ class ActionJob(object):
self.backup_name = backup_name
class Session(object):
def __init__(self, session_id, description, status, jobs,
start_datetime, interval, end_datetime):
self.session_id = session_id
self.description = description
self.status = status
self.jobs = jobs
self.start_datetime = start_datetime
self.interval = interval
self.end_datetime = end_datetime
@memoized
def get_service_url(request):
""" Get Freezer API url from keystone catalog.
@ -263,3 +275,74 @@ def client_list(request):
clients = _freezerclient(request).registration.list()
clients = [Client(client) for client in clients]
return clients
def add_job_to_session(request, session_id, job_id):
"""This function will add a job to a session and the API will handle the
copy of job information to the session """
return _freezerclient(request).sessions.add_job(session_id, job_id)
def remove_job_from_session(request, session_id, job_id):
"""Remove a job from a session will delete the job information but not the
job itself """
return _freezerclient(request).sessions.remove_job(session_id, job_id)
def session_create(request, context):
"""A session is a group of jobs who share the same scheduling time. """
session = create_dict_action(**context)
session['description'] = session.pop('description', None)
schedule = {
'end_datetime': session.pop('end_datetime', None),
'interval': session.pop('interval', None),
'start_datetime': session.pop('start_datetime', None),
}
session['schedule'] = schedule
return _freezerclient(request).sessions.create(session)
def session_update(request, context):
"""Update session information """
session = create_dict_action(**context)
session_id = session.pop('session_id', None)
session['description'] = session.pop('description', None)
schedule = {
'end_datetime': session.pop('end_datetime', None),
'interval': session.pop('interval', None),
'start_datetime': session.pop('start_datetime', None),
}
session['schedule'] = schedule
return _freezerclient(request).sessions.update(session_id, session)
def session_delete(request, session_id):
"""Delete session from API """
return _freezerclient(request).sessions.delete(session_id)
def session_list(request):
"""List all sessions """
sessions = _freezerclient(request).sessions.list_all()
sessions = [Session(s['session_id'],
s['description'],
s['status'],
s['jobs'],
s['schedule']['start_datetime'],
s['schedule']['interval'],
s['schedule']['end_datetime'])
for s in sessions]
return sessions
def session_get(request, session_id):
"""Get a single session """
session = _freezerclient(request).sessions.get(session_id)
session = Session(session['session_id'],
session['description'],
session['status'],
session['jobs'],
session['schedule']['start_datetime'],
session['schedule']['interval'],
session['schedule']['end_datetime'])
return session

View File

@ -18,7 +18,7 @@ import horizon
class FreezerDR(horizon.PanelGroup):
slug = "freezerdr"
name = _("Backup and Restore")
panels = ('jobs',)
panels = ('jobs', 'sessions')
class Freezer(horizon.Dashboard):

View File

@ -47,6 +47,20 @@ def format_last_backup(last_backup):
'en="true"></span> {}</span>'.format(colour, icon, text))
class AttachJobToSession(tables.LinkAction):
name = "attach_job_to_session"
verbose_name = _("Attach To Session")
classes = ("ajax-modal")
url = "horizon:freezer_ui:sessions:attach"
def allowed(self, request, instance):
return True
def get_link_url(self, datum):
return reverse("horizon:freezer_ui:sessions:attach",
kwargs={'job_id': datum.job_id})
class Restore(tables.Action):
name = "restore"
verbose_name = _("Restore")
@ -149,6 +163,7 @@ class JobsTable(tables.DataTable):
multi_select = False
row_actions = (CreateAction,
EditJob,
AttachJobToSession,
CloneJob,
DeleteJob,)

View File

@ -1,7 +0,0 @@
{% load i18n horizon humanize %}
{% block help_message %}
{% endblock %}
<!-- Jquery code to hide freezer inputs -->
<script type='text/javascript' src='{{ STATIC_URL }}freezer/js/freezer.actions.snapshot.js'></script>

View File

View File

@ -0,0 +1,27 @@
# Copyright 2012 Nebula, Inc.
# Copyright 2015 Hewlett-Packard
#
# 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 browsers
from horizon_web_ui.freezer_ui.sessions import tables
class SessionBrowser(browsers.ResourceBrowser):
name = "session_configuration"
verbose_name = _("Session Configuration")
navigation_table_class = tables.SessionsTable
content_table_class = tables.JobsTable
navigable_item_name = _("Sessions")
navigation_kwarg_name = "session_id"

View File

@ -0,0 +1,24 @@
# 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 horizon_web_ui.freezer_ui import dashboard
class SessionsPanel(horizon.Panel):
name = _("Sessions")
slug = "sessions"
dashboard.Freezer.register(SessionsPanel)

View File

@ -0,0 +1,154 @@
# 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 datetime
from django import shortcuts
from django.utils import safestring
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import messages
from horizon import tables
from horizon.utils.urlresolvers import reverse
import horizon_web_ui.freezer_ui.api.api as freezer_api
from horizon_web_ui.freezer_ui.django_utils import timestamp_to_string
def get_link(session):
return reverse('horizon:freezer_ui:sessions:index',
kwargs={'session_id': session.session_id})
class CreateJob(tables.LinkAction):
name = "create_session"
verbose_name = _("Create Session")
url = "horizon:freezer_ui:sessions:create"
classes = ("ajax-modal",)
icon = "plus"
class DeleteSession(tables.DeleteAction):
name = "delete"
classes = ("btn-danger",)
icon = "remove"
help_text = _("Delete sessions is not recoverable.")
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Session",
u"Delete Sessions",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Deleted Session",
u"Deleted Sessions",
count
)
def delete(self, request, session_id):
return freezer_api.session_delete(request, session_id)
class EditSession(tables.LinkAction):
name = "edit_session"
verbose_name = _("Edit Session")
classes = ("ajax-modal",)
icon = "pencil"
def get_link_url(self, datum=None):
return reverse("horizon:freezer_ui:sessions:edit",
kwargs={'session_id': datum.session_id})
class DeleteMultipleActions(DeleteSession):
name = "delete_multiple_actions"
class DeleteJobFromSession(tables.DeleteAction):
name = "delete_job_from_session"
classes = ("btn-danger",)
icon = "remove"
help_text = _("Delete jobs is not recoverable.")
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Job",
u"Delete Jobs",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Deleted Job",
u"Deleted Jobs",
count
)
def delete(self, request, session):
job_id, session_id = session.split('===')
return freezer_api.remove_job_from_session(
request,
session_id,
job_id)
class JobsTable(tables.DataTable):
client_id = tables.Column(
'client_id',
verbose_name=_("Client ID"))
status = tables.Column(
'status',
verbose_name=_("Status"))
def get_object_id(self, job):
# this is used to pass to values as an url
# TODO: look for a way to improve this
ids = '{0}==={1}'.format(job.job_id, job.session_id)
return ids
class Meta(object):
name = "jobs"
verbose_name = _("Jobs")
table_actions = ()
row_actions = (DeleteJobFromSession,)
footer = False
multi_select = True
class SessionsTable(tables.DataTable):
description = tables.Column('description',
link=get_link,
verbose_name=_("Session"))
status = tables.Column('status',
verbose_name=_("Status"))
def get_object_id(self, session):
return session.session_id
class Meta(object):
name = "sessions"
verbose_name = _("Sessions")
table_actions = (CreateJob,
DeleteMultipleActions)
row_actions = (EditSession,
DeleteSession,)
footer = False
multi_select = True

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Session Configurations" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Session Configurations") %}
{% endblock page_header %}
{% block main %}
{{ session_configuration_browser.render }}
{% endblock %}

View File

@ -0,0 +1,38 @@
# 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 horizon_web_ui.freezer_ui.sessions import views
urlpatterns = patterns(
'',
url(r'^(?P<session_id>[^/]+)?$',
views.SessionsView.as_view(),
name='index'),
url(r'^attach_to_session/(?P<job_id>[^/]+)?$',
views.AttachToSessionWorkflow.as_view(),
name='attach'),
url(r'^create/$',
views.CreateSessionWorkflow.as_view(),
name='create'),
url(r'^edit/(?P<session_id>[^/]+)?$',
views.CreateSessionWorkflow.as_view(),
name='edit'),
)

View File

@ -0,0 +1,114 @@
# 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.utils.translation import ugettext_lazy as _
from horizon import browsers
from horizon import exceptions
from horizon import workflows
import horizon_web_ui.freezer_ui.sessions.browsers as project_browsers
from horizon_web_ui.freezer_ui.sessions.workflows import attach
from horizon_web_ui.freezer_ui.sessions.workflows import create_session
import horizon_web_ui.freezer_ui.api.api as freezer_api
from horizon_web_ui.freezer_ui.utils import SessionJob
class AttachToSessionWorkflow(workflows.WorkflowView):
workflow_class = attach.AttachJobToSession
def get_object(self, *args, **kwargs):
job_id = self.kwargs['job_id']
try:
return freezer_api.job_get(self.request, job_id)
except Exception:
redirect = reverse("horizon:freezer_ui:jobs:index")
msg = _('Unable to retrieve details.')
exceptions.handle(self.request, msg, redirect=redirect)
def is_update(self):
return 'job_id' in self.kwargs and \
bool(self.kwargs['job_id'])
def get_initial(self):
initial = super(AttachToSessionWorkflow, self).get_initial()
job = self.get_object()[0]
initial.update({'job_id': job.id})
return initial
class SessionsView(browsers.ResourceBrowserView):
browser_class = project_browsers.SessionBrowser
template_name = "freezer_ui/sessions/browser.html"
def get_sessions_data(self):
sessions = []
try:
sessions = freezer_api.session_list(self.request)
except Exception:
msg = _('Unable to retrieve sessions list.')
exceptions.handle(self.request, msg)
return sessions
def get_jobs_data(self):
jobs = []
session = None
try:
if self.kwargs['session_id']:
session = freezer_api.session_get(
self.request,
self.kwargs['session_id'])
try:
jobs = [SessionJob(k,
self.kwargs['session_id'],
v['client_id'],
v['status'])
for k, v in session.jobs.iteritems()]
except AttributeError:
pass
except Exception:
msg = _('Unable to retrieve session information.')
exceptions.handle(self.request, msg)
return jobs
class CreateSessionWorkflow(workflows.WorkflowView):
workflow_class = create_session.CreateSession
def get_object(self, *args, **kwargs):
session_id = self.kwargs['session_id']
try:
return freezer_api.session_get(self.request, session_id)
except Exception:
redirect = reverse("horizon:freezer_ui:sessions:index")
msg = _('Unable to retrieve session.')
exceptions.handle(self.request, msg, redirect=redirect)
def get_initial(self):
initial = super(CreateSessionWorkflow, self).get_initial()
if self.is_update():
session = self.get_object()
initial.update({
'description': session.description,
'session_id': session.session_id,
'start_datetime': session.start_datetime,
'interval': session.interval,
'end_datetime': session.end_datetime
})
return initial
def is_update(self):
return 'session_id' in self.kwargs and \
bool(self.kwargs['session_id'])

View File

@ -0,0 +1,72 @@
# 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 exceptions
from horizon import forms
from horizon import workflows
import horizon_web_ui.freezer_ui.api.api as freezer_api
class SessionConfigurationAction(workflows.Action):
session_id = forms.ChoiceField(
help_text=_("Set a session to attach this job"),
label=_("Session Name"),
required=True)
job_id = forms.CharField(
widget=forms.HiddenInput(),
required=True)
def populate_session_id_choices(self, request, context):
sessions = []
try:
sessions = freezer_api.session_list(request)
except Exception:
exceptions.handle(request, _('Error getting session list'))
sessions = [(s.session_id, s.description) for s in sessions]
sessions.insert(0, ('', _('Select A Session')))
return sessions
class Meta:
name = _("Sessions")
slug = "sessions"
class SessionConfiguration(workflows.Step):
action_class = SessionConfigurationAction
contributes = ('session_id',
'job_id')
class AttachJobToSession(workflows.Workflow):
slug = "attach_job"
name = _("Attach To Session")
finalize_button_name = _("Attach")
success_message = _('Job saved successfully.')
failure_message = _('Unable to attach to session.')
success_url = "horizon:freezer_ui:jobs:index"
default_steps = (SessionConfiguration,)
def handle(self, request, context):
try:
freezer_api.add_job_to_session(
request,
context['session_id'],
context['job_id'])
return True
except Exception:
exceptions.handle(request)
return False

View File

@ -0,0 +1,123 @@
# 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 datetime
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import workflows
import horizon_web_ui.freezer_ui.api.api as freezer_api
class SessionConfigurationAction(workflows.Action):
description = forms.CharField(
label=_("Session Name"),
help_text=_("Define a name for this session"),
required=True)
session_id = forms.CharField(
widget=forms.HiddenInput(),
required=False)
class Meta:
name = _("Session Information")
slug = "sessions"
class SessionConfiguration(workflows.Step):
action_class = SessionConfigurationAction
contributes = ('description',
'session_id')
class SchedulingConfigurationAction(workflows.Action):
start_datetime = forms.CharField(
label=_("Start Date and Time"),
required=False,
help_text=_(""))
interval = forms.CharField(
label=_("Interval"),
required=False,
help_text=_(""))
end_datetime = forms.CharField(
label=_("End Date and Time"),
required=False,
help_text=_(""))
def __init__(self, request, context, *args, **kwargs):
self.request = request
self.context = context
super(SchedulingConfigurationAction, self).__init__(
request, context, *args, **kwargs)
def clean(self):
cleaned_data = super(SchedulingConfigurationAction, self).clean()
self._check_start_datetime(cleaned_data)
self._check_end_datetime(cleaned_data)
return cleaned_data
def _validate_iso_format(self, start_date):
try:
return datetime.datetime.strptime(
start_date, "%Y-%m-%dT%H:%M:%S")
except ValueError:
return False
def _check_start_datetime(self, cleaned_data):
if cleaned_data.get('start_datetime') and not \
self._validate_iso_format(cleaned_data.get('start_datetime')):
msg = _("Start date time is not in ISO format.")
self._errors['start_datetime'] = self.error_class([msg])
def _check_end_datetime(self, cleaned_data):
if cleaned_data.get('end_datetime') and not \
self._validate_iso_format(cleaned_data.get('end_datetime')):
msg = _("End date time is not in ISO format.")
self._errors['end_datetime'] = self.error_class([msg])
class Meta(object):
name = _("Scheduling")
slug = "scheduling"
help_text_template = "freezer_ui/jobs" \
"/_scheduling.html"
class SchedulingConfiguration(workflows.Step):
action_class = SchedulingConfigurationAction
contributes = ('start_datetime',
'interval',
'end_datetime')
class CreateSession(workflows.Workflow):
slug = "create_session"
name = _("Create Session")
finalize_button_name = _("Create")
success_message = _('Session created successfully.')
failure_message = _('Unable to create session.')
success_url = "horizon:freezer_ui:sessions:index"
default_steps = (SessionConfiguration,
SchedulingConfiguration)
def handle(self, request, context):
try:
if context['session_id'] == '':
return freezer_api.session_create(request, context)
else:
return freezer_api.session_update(request, context)
except Exception:
exceptions.handle(request)
return False

View File

@ -17,3 +17,11 @@ def create_dict_action(**kwargs):
None values
"""
return {k: v for k, v in kwargs.items() if v}
class SessionJob(object):
def __init__(self, job_id, session_id, client_id, status):
self.job_id = job_id
self.session_id = session_id
self.client_id = client_id
self.status = status