diff --git a/blazar_dashboard/api/client.py b/blazar_dashboard/api/client.py index e0dd738..0d696b8 100644 --- a/blazar_dashboard/api/client.py +++ b/blazar_dashboard/api/client.py @@ -10,9 +10,13 @@ # License for the specific language governing permissions and limitations # under the License. +from datetime import datetime +from itertools import chain import json import logging +from pytz import UTC +from blazar_dashboard import conf from horizon import exceptions from horizon.utils.memoized import memoized from openstack_dashboard.api import base @@ -57,6 +61,11 @@ class Host(base.APIDictWrapper): return excaps +class Allocation(base.APIDictWrapper): + + _attrs = ['resource_id', 'reservations'] + + @memoized def blazarclient(request): try: @@ -129,3 +138,62 @@ def host_update(request, host_id, values): def host_delete(request, host_id): """Delete a host.""" blazarclient(request).host.delete(host_id) + + +def host_allocations_list(request): + """List allocations for all hosts.""" + request_manager = blazarclient(request).host.request_manager + resp, body = request_manager.get('/os-hosts/allocations') + allocations = body['allocations'] + return [Allocation(a) for a in allocations] + + +def reservation_calendar(request): + """Return a list of all scheduled leases.""" + + def compute_host2dict(h): + dictionary = dict( + hypervisor_hostname=h.hypervisor_hostname, vcpus=h.vcpus, + memory_mb=h.memory_mb, local_gb=h.local_gb, cpu_info=h.cpu_info, + hypervisor_type=h.hypervisor_type,) + # Ensure config attribute is copied over + calendar_attribute = conf.host_reservation.get('calendar_attribute') + dictionary[calendar_attribute] = ( + h[calendar_attribute] + ) + return dictionary + + # NOTE: This filters by reservable hosts + hosts_by_id = {h.id: h for h in host_list(request) if h.reservable} + + def host_reservation_dict(reservation, resource_id): + host_reservation = dict( + start_date=_parse_api_datestr(reservation['start_date']), + end_date=_parse_api_datestr(reservation['end_date']), + reservation_id=reservation['id'], + ) + calendar_attribute = conf.host_reservation.get('calendar_attribute') + host_reservation[calendar_attribute] = ( + hosts_by_id[resource_id][calendar_attribute] + ) + + return {k: v for k, v in host_reservation.items() if v is not None} + + host_reservations = [ + [host_reservation_dict(r, alloc.resource_id) + for r in alloc.reservations + if alloc.resource_id in hosts_by_id] + for alloc in host_allocations_list(request)] + + compute_hosts = [compute_host2dict(h) for h in hosts_by_id.values()] + + return compute_hosts, list(chain(*host_reservations)) + + +def _parse_api_datestr(datestr): + if datestr is None: + return datestr + + dateobj = datetime.strptime(datestr, "%Y-%m-%dT%H:%M:%S.%f") + + return dateobj.replace(tzinfo=UTC) diff --git a/blazar_dashboard/conf.py b/blazar_dashboard/conf.py new file mode 100644 index 0000000..047deb9 --- /dev/null +++ b/blazar_dashboard/conf.py @@ -0,0 +1,23 @@ +# 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 + +host_reservation = ( + getattr(settings, 'OPENSTACK_BLAZAR_HOST_RESERVATION', { + 'enabled': True, + 'calendar_attribute': 'hypervisor_hostname', + })) + +floatingip_reservation = ( + getattr(settings, 'OPENSTACK_BLAZAR_FLOATINGIP_RESERVATION', { + 'enabled': False, })) diff --git a/blazar_dashboard/content/leases/tables.py b/blazar_dashboard/content/leases/tables.py index 42a0d29..4dc92ae 100644 --- a/blazar_dashboard/content/leases/tables.py +++ b/blazar_dashboard/content/leases/tables.py @@ -24,6 +24,7 @@ from horizon.utils import filters import pytz from blazar_dashboard import api +from blazar_dashboard import conf class CreateLease(tables.LinkAction): @@ -47,6 +48,14 @@ class UpdateLease(tables.LinkAction): return False +class ViewHostReservationCalendar(tables.LinkAction): + name = "calendar" + verbose_name = _("Host Calendar") + url = "calendar/host/" + classes = ("btn-default", ) + icon = "calendar" + + class DeleteLease(tables.DeleteAction): name = "delete" data_type_singular = _("Lease") @@ -92,5 +101,7 @@ class LeasesTable(tables.DataTable): class Meta(object): name = "leases" verbose_name = _("Leases") - table_actions = (CreateLease, DeleteLease, ) + table_actions = [CreateLease, DeleteLease, ] + if conf.host_reservation.get('enabled'): + table_actions.insert(0, ViewHostReservationCalendar) row_actions = (UpdateLease, DeleteLease, ) diff --git a/blazar_dashboard/content/leases/templates/leases/calendar.html b/blazar_dashboard/content/leases/templates/leases/calendar.html new file mode 100644 index 0000000..400291c --- /dev/null +++ b/blazar_dashboard/content/leases/templates/leases/calendar.html @@ -0,0 +1,36 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Leases" %}{% endblock %} + +{% block page_header %} +{% include "horizon/common/_page_header.html" with title=calendar_title %} +{% endblock page_header %} + +{% block main %} +
+