1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2024-12-25 15:45:23 +00:00

Merge pull request #263 from catborise/master

Some addition & Fixes
This commit is contained in:
Anatoliy Guskov 2019-11-21 12:28:37 +02:00 committed by GitHub
commit 3bae40c8aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 494 additions and 226 deletions

View file

@ -53,7 +53,7 @@
</head> </head>
<body> <body>
<nav class="navbar navbar-inverse navbar-static-top" role="navigation"> <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container"> <div class="container">
<div class="navbar-header"> <div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">

View file

@ -80,7 +80,7 @@
{% block content %} {% block content %}
<div id="noVNC_fallback_error" class="noVNC_center"> <div id="noVNC_fallback_error" class="noVNC_center">
<div> <div>
<div>noVNC encountered an error:</div> <div>{% trans 'noVNC encountered an error' %}:</div>
<br> <br>
<div id="noVNC_fallback_errormsg"></div> <div id="noVNC_fallback_errormsg"></div>
</div> </div>

View file

@ -294,7 +294,7 @@
{% block content %} {% block content %}
<div id="noVNC_status_bar"> <div id="noVNC_status_bar">
<div id="noVNC_left_dummy_elem"></div> <div id="noVNC_left_dummy_elem"></div>
<div id="noVNC_status">Loading</div> <div id="noVNC_status">{% trans 'Loading' %}</div>
<div id="noVNC_buttons"> <div id="noVNC_buttons">
<input type=button value="Send CtrlAltDel" id="sendCtrlAltDelButton" class="noVNC_shown"> <input type=button value="Send CtrlAltDel" id="sendCtrlAltDelButton" class="noVNC_shown">
<span id="noVNC_power_buttons" class="noVNC_hidden"> <span id="noVNC_power_buttons" class="noVNC_hidden">

View file

@ -43,7 +43,7 @@ def create_instance(request, compute_id):
compute.type) compute.type)
instances = conn.get_instances() instances = conn.get_instances()
videos = conn.get_video() videos = conn.get_video_models()
cache_modes = sorted(conn.get_cache_modes().items()) cache_modes = sorted(conn.get_cache_modes().items())
default_cache = INSTANCE_VOLUME_DEFAULT_CACHE default_cache = INSTANCE_VOLUME_DEFAULT_CACHE
listener_addr = QEMU_CONSOLE_LISTEN_ADDRESSES listener_addr = QEMU_CONSOLE_LISTEN_ADDRESSES

View file

@ -1,6 +1,6 @@
#cloud-config #cloud-config
{% if instance_keys %} {% if instance_keys %}
ssh_authorized_keys: ssh_authorized_keys:
{% for key in instance_keys %} - {{ key }}{% endfor %} {% for key in instance_keys %} - {{ key }}
{% endfor %}
{% endif %} {% endif %}

View file

@ -432,11 +432,7 @@
<small><input type="checkbox" class="js-custom__checkbox" /> {% trans "Custom value" %}</small> <small><input type="checkbox" class="js-custom__checkbox" /> {% trans "Custom value" %}</small>
</div> </div>
</div> </div>
{% ifequal status 5 %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="resizevm_mem">{% trans "Resize" %}</button> <button type="submit" class="btn btn-lg btn-success pull-right" name="resizevm_mem">{% trans "Resize" %}</button>
{% else %}
<button class="btn btn-lg btn-success pull-right disabled">{% trans "Resize" %}</button>
{% endifequal %}
</form> </form>
{% else %} {% else %}
{% trans "You don't have permission for resizing instance" %} {% trans "You don't have permission for resizing instance" %}
@ -851,25 +847,47 @@
</p> </p>
<div class="col-xs-12 col-sm-12"> <div class="col-xs-12 col-sm-12">
<form method="post" role="form">{% csrf_token %}
{% for network in networks %}
{% if forloop.first %}
<p><strong>{% trans "Network Devices" %}</strong></p> <p><strong>{% trans "Network Devices" %}</strong></p>
{% endif %} <table class="table table-hover">
<div class="panel panel-default"> <thead>
<div class="panel-heading"> <tr>
<label>eth{{ forloop.counter0 }}({{ network.target|default:"no target" }})</label> <th>{% trans 'Name' %}</th>
<button class="btn btn-sm pull-right btn-danger" value="{{ network.mac }}" name="delete_network" title="{% trans "Delete Device" %}" onclick="return confirm('{% trans "Are you sure?" %}')">{% trans "Delete" %}</button> <th>{% trans 'MAC' %}</th>
<th>{% trans 'NIC' %}</th>
<th>{% trans 'Filter' %}</th>
<th>{% trans 'Actions' %}</th>
</tr>
</thead>
<tbody>
{% for network in networks %}
<tr>
<td class="col-sm-2"><label>eth{{ forloop.counter0 }}({{ network.target|default:"no target" }})</label></td>
<td><input class="form-control" type="text" value="{{ network.mac }}" readonly/></td>
<td><input class="form-control" type="text" value="{{ network.nic }}" readonly/></td>
<td><input class="form-control" type="text" value="{{ network.filterref }}" readonly/></td>
<td class="col-sm-2">
<form class="form-horizontal" method="post" name="set_qos{{ forloop.counter0 }}" role="form">{% csrf_token %}
<button data-target="#editInstanceNetwork{{ forloop.counter0 }}" type="button" class="btn btn-sm btn-primary"
title="Edit NIC" data-toggle="modal">
<span class="glyphicon glyphicon-edit" aria-hidden="true"></span>
</button>
<div class="modal fade" id="editInstanceNetwork{{ forloop.counter0 }}" role="dialog" aria-labelledby="editInstanceNetworkLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{% trans "Edit Instance Network" %}</h4>
</div> </div>
<div class="panel-body"> <div class="modal-body">
<div class="form-group form-inline"> <div class="form-group form-inline">
<label class="col-sm-2 col-sm-offset-1 control-label">{% trans "MAC" %} </label> <label class="col-sm-2 control-label">{% trans "MAC" %} </label>
<input class="form-control" type="text" value="{{ network.mac }}" readonly/> <input class="form-control" type="text" value="{{ network.mac }}" readonly/>
<label class="control-label"><em>to</em></label> <label class="control-label"><em>to</em></label>
<input class="form-control" type="text" name="net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/> <input class="form-control" type="text" name="net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
</div> </div>
<div class="form-group form-inline"> <div class="form-group form-inline">
<label class="col-sm-2 col-sm-offset-1 control-label">{% trans "NIC" %} </label> <label class="col-sm-2 control-label">{% trans "NIC" %} </label>
<input class="form-control" type="text" value="{{ network.nic }}" readonly/> <input class="form-control" type="text" value="{{ network.nic }}" readonly/>
<label class="control-label"><em>to</em></label> <label class="control-label"><em>to</em></label>
<select class="form-control" name="net-source-{{ forloop.counter0 }}"> <select class="form-control" name="net-source-{{ forloop.counter0 }}">
@ -882,7 +900,7 @@
</select> </select>
</div> </div>
<div class="form-group form-inline"> <div class="form-group form-inline">
<label class="col-sm-2 col-sm-offset-1">{% trans "Filter" %} </label> <label class="col-sm-2 control-label">{% trans "Filter" %} </label>
<input class="form-control" type="text" value="{{ network.filterref }}" readonly/> <input class="form-control" type="text" value="{{ network.filterref }}" readonly/>
<label class="control-label"><em>to</em></label> <label class="control-label"><em>to</em></label>
<select class="form-control" name="net-nwfilter-{{ forloop.counter0 }}"> <select class="form-control" name="net-nwfilter-{{ forloop.counter0 }}">
@ -895,9 +913,72 @@
<button class="btn btn-sm btn-primary btn-block" name="change_network" title="{% trans "Apply Network Changes" %}">{% trans "Apply" %}</button> <button class="btn btn-sm btn-primary btn-block" name="change_network" title="{% trans "Apply Network Changes" %}">{% trans "Apply" %}</button>
</div> </div>
</div> </div>
{% endfor %}
</form>
</div> </div>
</div>
<button class="btn btn-sm btn-danger" value="{{ network.mac }}" name="delete_network" title="{% trans "Delete Device" %}"
onclick="return confirm('{% trans "Are you sure?" %}')">
<i class="glyphicon glyphicon-trash"></i>
</button>
{% include 'add_network_qos.html' with id=forloop.counter0 %}
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if qos %}
<div class="col-xs-10 col-sm-10">
<p><strong>{% trans "Qos Configuration" %}</strong></p>
</div>
<div class="col-xs-12 col-sm-12">
<table class="table table-hover">
<thead>
<tr>
<th>{% trans "Direction" %}</th>
<th>{% trans "Average" %}</th>
<th>{% trans "Peak" %}</th>
<th>{% trans "Burst" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for q, attrs in qos.items %}
{% for att in attrs %}
<form method="post" role="form">{% csrf_token %}
<tr>
<td><label class="control-label">{{ q }} {{ att.direction | capfirst }}</label></td>
<td><input id="qos_average" class="form-control" name="qos_average"
value="{{ att.average|default:'' }}"/></td>
<td><input id="qos_peak" class="form-control" name="qos_peak"
value="{{ att.peak|default:'' }}"/></td>
<td><input id="qos_burst" class="form-control" name="qos_burst"
value="{{ att.burst|default:'' }}"/></td>
<td class="col-sm-2">
<input name="qos_direction" value="{{ att.direction }}" hidden/>
<input name="net-mac" value="{{ q }}" hidden/>
<button type="submit" class="btn btn-sm btn-primary"
name="set_qos" data-toggle="modal"
title="Edit Qos" onclick="return confirm('{% trans "Are you sure?" %}')">
<i class="glyphicon glyphicon-save"></i>
</button>
<button type="submit" class="btn btn-sm btn-danger"
name="unset_qos"
title="Delete Qos" onclick="return confirm('{% trans "Are you sure?" %}')">
<i class="glyphicon glyphicon-trash"></i>
</button>
</td>
</tr>
</form>
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="migrate"> <div role="tabpanel" class="tab-pane tab-pane-bordered" id="migrate">
@ -1209,6 +1290,8 @@
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="options"> <div role="tabpanel" class="tab-pane tab-pane-bordered" id="options">
<div class="well">
<p>{% trans "To set instance template name description, shutdown the instance." %}</p>
<form class="form-horizontal" action="" method="post" role="form">{% csrf_token %} <form class="form-horizontal" action="" method="post" role="form">{% csrf_token %}
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Title" %}</label> <label class="col-sm-3 control-label">{% trans "Title" %}</label>
@ -1225,17 +1308,52 @@
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Is template" %}</label> <label class="col-sm-3 control-label">{% trans "Is template" %}</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="checkbox" name="is_template" value="True" id="is_template" {% if instance.is_template %}checked{% endif %} {% if not request.user.is_superuser %}disabled{% endif %}> <input type="checkbox"
name="is_template"
value="True"
id="is_template"
{% if instance.is_template %}checked{% endif %}
{% if not request.user.is_superuser and not request.user.is_staff %}disabled{% endif %}>
</div> </div>
</div> </div>
<div class="form-group ">
<div class="col-sm-offset-3 col-sm-6">
{% ifequal status 5 %} {% ifequal status 5 %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="change_options">{% trans "Change" %}</button> <button type="submit" class="btn btn-block btn-success" name="change_options">{% trans "Change" %}</button>
{% else %} {% else %}
<button class="btn btn-lg btn-success pull-right disabled" name="change_options">{% trans "Change" %}</button> <button class="btn btn-block btn-success disabled" name="change_options">{% trans "Change" %}</button>
{% endifequal %} {% endifequal %}
</div>
</div>
</form>
</div>
<div class="well">
<p>{% trans "To set instance video model, shutdown the instance." %}</p>
<form class="form-horizontal" method="post" role="form">{% csrf_token %}
<div class="form-group">
<label for="video_model_select" class="col-sm-3 control-label">{% trans "Primary Video Model" %}</label>
<div class="col-sm-6">
<div class="input-group">
<select id="video_model_select" class="form-control" name="video_model">
<option value="" style="font-weight: bold">{% trans "please choose" %}</option>
{% for vmodel in videos_host %}
<option value="{{ vmodel }}">{{ vmodel }}</option>
{% endfor %}
</select>
<span class="input-group-btn">
{% ifequal status 5 %}
<button type="submit" class="btn btn-success" name="set_video_model">{% trans "Set" %}</button>
{% else %}
<button class="btn btn-success disabled" name="set_video_model">{% trans "Set" %}</button>
{% endifequal %}
</span>
</div>
</div>
</div>
</form> </form>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -1516,6 +1634,13 @@
$("#console_select_listen_address option[value='" + console_listen_address + "']").prop('selected', true); $("#console_select_listen_address option[value='" + console_listen_address + "']").prop('selected', true);
} }
}); });
$(document).ready(function () {
// get video model or fall back to default
let video_model = "{{ video_model }}"
if (video_model != '') {
$("#video_model_select option[value='" + video_model + "']").prop('selected', true);
}
});
$(document).ready(function () { $(document).ready(function () {
// set vdi url // set vdi url
$.get("{% url 'vdi_url' vname %}", function(data) { $.get("{% url 'vdi_url' vname %}", function(data) {

View file

@ -272,6 +272,7 @@ def instance(request, compute_id, vname):
title = conn.get_title() title = conn.get_title()
description = conn.get_description() description = conn.get_description()
networks = conn.get_net_device() networks = conn.get_net_device()
qos = conn.get_all_qos()
disks = conn.get_disk_devices() disks = conn.get_disk_devices()
media = conn.get_media_devices() media = conn.get_media_devices()
if len(media) != 0: if len(media) != 0:
@ -290,6 +291,7 @@ def instance(request, compute_id, vname):
console_port = conn.get_console_port() console_port = conn.get_console_port()
console_keymap = conn.get_console_keymap() console_keymap = conn.get_console_keymap()
console_listen_address = conn.get_console_listen_addr() console_listen_address = conn.get_console_listen_addr()
video_model = conn.get_video_model()
snapshots = sorted(conn.get_snapshot(), reverse=True, key=lambda k: k['date']) snapshots = sorted(conn.get_snapshot(), reverse=True, key=lambda k: k['date'])
inst_xml = conn._XMLDesc(VIR_DOMAIN_XML_SECURE) inst_xml = conn._XMLDesc(VIR_DOMAIN_XML_SECURE)
has_managed_save_image = conn.get_managed_save_image() has_managed_save_image = conn.get_managed_save_image()
@ -327,6 +329,7 @@ def instance(request, compute_id, vname):
vcpu_host = len(vcpu_range) vcpu_host = len(vcpu_range)
memory_host = conn.get_max_memory() memory_host = conn.get_max_memory()
bus_host = conn.get_disk_bus_types() bus_host = conn.get_disk_bus_types()
videos_host = conn.get_video_models()
networks_host = sorted(conn.get_networks()) networks_host = sorted(conn.get_networks())
interfaces_host = sorted(conn.get_ifaces()) interfaces_host = sorted(conn.get_ifaces())
nwfilters_host = conn.get_nwfilters() nwfilters_host = conn.get_nwfilters()
@ -482,8 +485,9 @@ def instance(request, compute_id, vname):
addlogmsg(request.user.username, instance.name, msg) addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#resize') return HttpResponseRedirect(request.get_full_path() + '#resize')
if 'resizevm_mem' in request.POST and ( if 'resizevm_mem' in request.POST and (request.user.is_superuser or
request.user.is_superuser or request.user.is_staff or userinstance.is_change): request.user.is_staff or
userinstance.is_change):
new_memory = request.POST.get('memory', '') new_memory = request.POST.get('memory', '')
new_memory_custom = request.POST.get('memory_custom', '') new_memory_custom = request.POST.get('memory_custom', '')
if new_memory_custom: if new_memory_custom:
@ -699,7 +703,8 @@ def instance(request, compute_id, vname):
msg = _("Set boot order") msg = _("Set boot order")
if not conn.get_status() == 5: if not conn.get_status() == 5:
messages.success(request, _("Boot menu changes applied. But it will be activated after shutdown")) messages.success(request, _("Boot menu changes applied. " +
"But it will be activated after shutdown"))
else: else:
messages.success(request, _("Boot order changed successfully.")) messages.success(request, _("Boot order changed successfully."))
addlogmsg(request.user.username, instance.name, msg) addlogmsg(request.user.username, instance.name, msg)
@ -727,7 +732,8 @@ def instance(request, compute_id, vname):
error_messages.append(msg) error_messages.append(msg)
if not error_messages: if not error_messages:
if not conn.set_console_passwd(passwd): if not conn.set_console_passwd(passwd):
msg = _("Error setting console password. You should check that your instance have an graphic device.") msg = _("Error setting console password. " +
"You should check that your instance have an graphic device.")
error_messages.append(msg) error_messages.append(msg)
else: else:
msg = _("Set VNC password") msg = _("Set VNC password")
@ -760,6 +766,13 @@ def instance(request, compute_id, vname):
return HttpResponseRedirect(request.get_full_path() + '#vncsettings') return HttpResponseRedirect(request.get_full_path() + '#vncsettings')
if request.user.is_superuser: if request.user.is_superuser:
if 'set_video_model' in request.POST:
video_model = request.POST.get('video_model', 'vga')
conn.set_video_model(video_model)
msg = _("Set Video Model")
addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#options')
if 'migrate' in request.POST: if 'migrate' in request.POST:
compute_id = request.POST.get('compute_id', '') compute_id = request.POST.get('compute_id', '')
live = request.POST.get('live_migrate', False) live = request.POST.get('live_migrate', False)
@ -808,6 +821,40 @@ def instance(request, compute_id, vname):
addlogmsg(request.user.username, instance.name, msg) addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#network') return HttpResponseRedirect(request.get_full_path() + '#network')
if 'set_qos' in request.POST:
qos_dir = request.POST.get('qos_direction', '')
average = request.POST.get('qos_average') or 0
peak = request.POST.get('qos_peak') or 0
burst = request.POST.get('qos_burst') or 0
keys = request.POST.keys()
mac_key = [key for key in keys if 'mac' in key]
if mac_key: mac = request.POST.get(mac_key[0])
try:
conn.set_qos(mac, qos_dir, average, peak, burst)
if conn.get_status() == 5:
messages.success(request, "{} Qos is set".format(qos_dir.capitalize()))
else:
messages.success(request,
"{} Qos is set. Network XML is changed.".format(qos_dir.capitalize()) +
"Stop and start network to activate new config")
except libvirtError as le:
messages.error(request, le.message)
return HttpResponseRedirect(request.get_full_path() + '#network')
if 'unset_qos' in request.POST:
qos_dir = request.POST.get('qos_direction', '')
mac = request.POST.get('net-mac')
conn.unset_qos(mac, qos_dir)
if conn.get_status() == 5:
messages.success(request, "{} Qos is deleted".format(qos_dir.capitalize()))
else:
messages.success(request,
"{} Qos is deleted. Network XML is changed. ".format(qos_dir.capitalize()) +
"Stop and start network to activate new config.")
return HttpResponseRedirect(request.get_full_path() + '#network')
if 'add_owner' in request.POST: if 'add_owner' in request.POST:
user_id = int(request.POST.get('user_id', '')) user_id = int(request.POST.get('user_id', ''))
@ -1014,7 +1061,7 @@ def get_host_instances(request, comp):
conn.close() conn.close()
else: else:
raise libvirtError("Problem occured with {} - {}".format(comp.name, status)) raise libvirtError("Problem occurred with {} - {}".format(comp.name, status))
return all_host_vms return all_host_vms

View file

@ -1,11 +1,11 @@
{% load i18n %} {% load i18n %}
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
<a href="#AddInboundQos" type="button" class="btn btn-success pull-right" data-toggle="modal" title="add inbound qos"> <a href="#AddQos{{ id }}" type="button" class="btn btn-sm btn-success pull-right" data-toggle="modal" title="add qos">
<span class="glyphicon glyphicon-arrow-down" aria-hidden="true"></span> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
</a> </a>
<!-- Modal pool --> <!-- Modal pool -->
<div class="modal fade" id="AddInboundQos" tabindex="-1" role="dialog" aria-labelledby="AddInboundQosLabel" <div class="modal fade" id="AddQos{{ id }}" tabindex="-1" role="dialog" aria-labelledby="AddQosLabel"
aria-hidden="true"> aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@ -13,36 +13,43 @@
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{% trans "Add Inbound Qos for Network" %}</h4> <h4 class="modal-title">{% trans "Add Inbound Qos for Network" %}</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form class="form-horizontal" method="post" name="set_qos" role="form">{% csrf_token %} <div class="form-group">
<label class="col-sm-4 control-label">{% trans "Direction" %}:</label>
<div class="col-sm-6">
<select class="form-control" name="qos_direction">
<option value="inbound">{% trans 'Inbound' %}</option>
<option value="outbound">{% trans 'Outbound' %}</option>
</select>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-4 control-label">{% trans "Average" %}:</label> <label class="col-sm-4 control-label">{% trans "Average" %}:</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input class="form-control" name="qos_inbound_average" required pattern="[0-9]+"/> <input class="form-control" name="qos_average" placeholder="kilobytes"
required pattern="[0-9]+"/>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-4 control-label">{% trans "Peak" %}:</label> <label class="col-sm-4 control-label">{% trans "Peak" %}:</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input class="form-control" name="qos_inbound_peak" <input class="form-control" name="qos_peak" placeholder="kilobytes"
required pattern="[0-9]+"/> pattern="[0-9]+"/>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-4 control-label">{% trans "Burst" %}:</label> <label class="col-sm-4 control-label">{% trans "Burst" %}:</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input class="form-control" name="qos_inbound_burst" required pattern="[0-9]+"/> <input class="form-control" name="qos_burst" placeholder="kilobytes"
pattern="[0-9]+"/>
</div> </div>
</div> </div>
<input name="qos_direction" value="inbound" hidden/>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans 'Close' %}</button> <button type="button" class="btn btn-default" data-dismiss="modal">{% trans 'Close' %}</button>
<button type="submit" class="btn btn-primary" name="set_qos">{% trans 'Save' %}</button> <button type="submit" class="btn btn-primary" name="set_qos">{% trans 'Save' %}</button>
</div> </div>
</form>
</div> </div>
</div> <!-- /.modal-dialog --> </div> <!-- /.modal-dialog -->
</div> <!-- /.modal --> </div> <!-- /.modal -->

View file

@ -1,49 +0,0 @@
{% load i18n %}
{% if request.user.is_superuser %}
<a href="#AddOutboundQos" type="button" class="btn btn-success pull-right" data-toggle="modal" title="add outbound qos">
<span class="glyphicon glyphicon-arrow-up" aria-hidden="true"></span>
</a>
<!-- Modal pool -->
<div class="modal fade" id="AddOutboundQos" tabindex="-1" role="dialog" aria-labelledby="AddOutboundQosLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{% trans "Add Outbound Qos for Network" %}</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" method="post" name="set_qos" role="form">{% csrf_token %}
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Average" %}:</label>
<div class="col-sm-6">
<input class="form-control" name="qos_outbound_average" required pattern="[0-9]+"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Peak" %}:</label>
<div class="col-sm-6">
<input class="form-control" name="qos_outbound_peak"
required pattern="[0-9]+"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Burst" %}:</label>
<div class="col-sm-6">
<input class="form-control" name="qos_outbound_burst" required pattern="[0-9]+"/>
</div>
</div>
<input name="qos_direction" value="outbound" hidden/>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans 'Close' %}</button>
<button type="submit" class="btn btn-primary" name="set_qos">{% trans 'Save' %}</button>
</div>
</form>
</div>
</div> <!-- /.modal-dialog -->
</div> <!-- /.modal -->
{% endif %}

View file

@ -339,14 +339,15 @@
</div> </div>
{% endif %} {% endif %}
{% ifequal state 0 %} {% if net_forward.0 == 'route' or net_forward.0 == 'nat' or net_forward.0 == 'isolated' %}
{% include 'add_outbound_qos.html' %} {% if state == 0 and qos.items|length != 2%}
{% include 'add_inbound_qos.html' %} <form class="form-horizontal" method="post" name="set_qos" role="form">{% csrf_token %}
{% endifequal %} {% include 'add_network_qos.html' %}
</form>
{% endif %}
<div class="row"> <div class="row">
<h3 class="page-header">{% trans "Qos Configuration" %} <h3 class="page-header">{% trans "Qos Configuration" %}</h3>
</h3>
</div> </div>
<div class="row"> <div class="row">
@ -366,16 +367,16 @@
<form method="post" role="form">{% csrf_token %} <form method="post" role="form">{% csrf_token %}
<tr> <tr>
<td><label class="control-label">{{ q | capfirst }}</label></td> <td><label class="control-label">{{ q | capfirst }}</label></td>
<td><input id="qos_{{ q }}_av" class="form-control" name="qos_{{ q }}_average" <td><input id="qos_average" class="form-control" name="qos_average"
value="{{ att.average }}"/></td> value="{{ att.average|default:'' }}"/></td>
<td><input id="qos_{{ q }}_peak" class="form-control" name="qos_{{ q }}_peak" <td><input id="qos_peak" class="form-control" name="qos_peak"
value="{{ att.peak }}"/></td> value="{{ att.peak|default:'' }}"/></td>
<td><input id="qos_{{ q }}_burst" class="form-control" name="qos_{{ q }}_burst" <td><input id="qos_burst" class="form-control" name="qos_burst"
value="{{ att.burst }}"/></td> value="{{ att.burst|default:'' }}"/></td>
<td> <td>
<input name="qos_direction" value="{{ q }}" hidden/> <input name="qos_direction" value="{{ q }}" hidden/>
<button type="submit" class="btn btn-sm btn-primary" <button type="submit" class="btn btn-sm btn-primary"
name="set_qos" name="set_qos" data-toggle="modal"
title="Edit Qos" onclick="return confirm('{% trans "Are you sure?" %}')"> title="Edit Qos" onclick="return confirm('{% trans "Are you sure?" %}')">
<i class="glyphicon glyphicon-save"></i> <i class="glyphicon glyphicon-save"></i>
</button> </button>
@ -392,6 +393,7 @@
</table> </table>
</div> </div>
</div> </div>
{% endif %}
{% endblock %} {% endblock %}
{% block script %} {% block script %}
<script> <script>

View file

@ -195,19 +195,21 @@ def network(request, compute_id, pool):
else: else:
messages.success(request, _("Network XML is changed.")) messages.success(request, _("Network XML is changed."))
return HttpResponseRedirect(request.get_full_path()) return HttpResponseRedirect(request.get_full_path())
if 'set_qos' in request.POST: if 'set_qos' in request.POST:
qos_dir = request.POST.get('qos_direction', '') qos_dir = request.POST.get('qos_direction', '')
average = request.POST.get('qos_{}_average'.format(qos_dir), '') average = request.POST.get('qos_average') or 0
peak = request.POST.get('qos_{}_peak'.format(qos_dir), '') peak = request.POST.get('qos_peak') or 0
burst = request.POST.get('qos_{}_burst'.format(qos_dir), '') burst = request.POST.get('qos_burst') or 0
try:
conn.set_qos(qos_dir, average, peak, burst) conn.set_qos(qos_dir, average, peak, burst)
if conn.is_active(): if conn.is_active():
messages.success(request, "{} Qos is set. Network XML is changed.".format(qos_dir.capitalize()) + messages.success(request, "{} Qos is set. Network XML is changed.".format(qos_dir.capitalize()) +
"Stop and start network to activate new config") "Stop and start network to activate new config")
else: else:
messages.success(request, "{} Qos is set".format(qos_dir.capitalize())) messages.success(request, "{} Qos is set".format(qos_dir.capitalize()))
except libvirtError as le:
messages.error(request, le.message)
return HttpResponseRedirect(request.get_full_path()) return HttpResponseRedirect(request.get_full_path())
if 'unset_qos' in request.POST: if 'unset_qos' in request.POST:
qos_dir = request.POST.get('qos_direction', '') qos_dir = request.POST.get('qos_direction', '')

View file

@ -21,9 +21,9 @@ html {
} }
#noVNC_status_bar { #noVNC_status_bar {
margin-top: 52px;
width: 100%; width: 100%;
display:flex; display: flex;
justify-content: space-between;
} }
#noVNC_status { #noVNC_status {

View file

@ -81,7 +81,7 @@ body
{ {
min-height: 600px; min-height: 600px;
height: 100%; height: 100%;
margin: 10px; margin: 62px 10px 10px 10px;
padding: 0; padding: 0;
background-color: #333333; background-color: #333333;
} }

View file

@ -480,7 +480,7 @@ class wvmConnect(object):
"""Get available image filename extensions""" """Get available image filename extensions"""
return ['img', 'qcow', 'qcow2'] return ['img', 'qcow', 'qcow2']
def get_video(self): def get_video_models(self):
""" Get available graphics video types """ """ Get available graphics video types """
def get_video_list(ctx): def get_video_list(ctx):
result = [] result = []
@ -508,6 +508,15 @@ class wvmConnect(object):
def get_network(self, net): def get_network(self, net):
return self.wvm.networkLookupByName(net) return self.wvm.networkLookupByName(net)
def get_network_forward(self, net_name):
def get_forward(doc):
forward_mode = util.get_xpath(doc, '/network/forward/@mode')
return forward_mode or 'isolated'
net = self.get_network(net_name)
xml = net.XMLDesc(0)
return util.get_xml_path(xml, func=get_forward)
def get_nwfilter(self, name): def get_nwfilter(self, name):
return self.wvm.nwfilterLookupByName(name) return self.wvm.nwfilterLookupByName(name)

View file

@ -7,6 +7,7 @@ except:
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE
from vrtManager import util from vrtManager import util
from xml.etree import ElementTree from xml.etree import ElementTree
from lxml import etree
from datetime import datetime from datetime import datetime
from vrtManager.connection import wvmConnect from vrtManager.connection import wvmConnect
from vrtManager.storage import wvmStorage from vrtManager.storage import wvmStorage
@ -157,6 +158,12 @@ class wvmInstance(wvmConnect):
return self.wvm.defineXML(xml) return self.wvm.defineXML(xml)
def get_status(self): def get_status(self):
"""
VIR_DOMAIN_NOSTATE = 0
VIR_DOMAIN_RUNNING = 1
VIR_DOMAIN_PAUSED = 3
VIR_DOMAIN_SHUTOFF = 5
"""
return self.instance.info()[0] return self.instance.info()[0]
def get_autostart(self): def get_autostart(self):
@ -229,17 +236,38 @@ class wvmInstance(wvmConnect):
def networks(ctx): def networks(ctx):
result = [] result = []
inbound = outbound = []
for net in ctx.xpath('/domain/devices/interface'): for net in ctx.xpath('/domain/devices/interface'):
mac_host = net.xpath('mac/@address')[0] mac_inst = net.xpath('mac/@address')[0]
network_host = net.xpath('source/@network|source/@bridge|source/@dev')[0] nic_inst = net.xpath('source/@network|source/@bridge|source/@dev')[0]
target_host = '' if not net.xpath('target/@dev') else net.xpath('target/@dev')[0] target_inst = '' if not net.xpath('target/@dev') else net.xpath('target/@dev')[0]
filterref_host = '' if not net.xpath('filterref/@filter') else net.xpath('filterref/@filter')[0] filterref_inst = '' if not net.xpath('filterref/@filter') else net.xpath('filterref/@filter')[0]
if net.xpath('bandwidth/inbound'):
in_attr = net.xpath('bandwidth/inbound')[0]
in_av = in_attr.get('average')
in_peak = in_attr.get('peak')
in_burst = in_attr.get('burst')
inbound = {'average': in_av, 'peak': in_peak, 'burst': in_burst}
if net.xpath('bandwidth/outbound'):
out_attr = net.xpath('bandwidth/outbound')[0]
out_av = out_attr.get('average')
out_peak = out_attr.get('peak')
out_burst = out_attr.get('burst')
outbound = {'average': out_av, 'peak': out_peak, 'burst': out_burst}
try: try:
net = self.get_network(network_host) net = self.get_network(nic_inst)
ip = get_mac_ipaddr(net, mac_host) ip = get_mac_ipaddr(net, mac_inst)
except libvirtError: except libvirtError:
ip = None ip = None
result.append({'mac': mac_host, 'nic': network_host, 'target': target_host, 'ip': ip, 'filterref': filterref_host}) result.append({'mac': mac_inst,
'nic': nic_inst,
'target': target_inst,
'ip': ip,
'filterref': filterref_inst,
'inbound': inbound,
'outbound': outbound,
})
return result return result
return util.get_xml_path(self._XMLDesc(0), func=networks) return util.get_xml_path(self._XMLDesc(0), func=networks)
@ -680,8 +708,7 @@ class wvmInstance(wvmConnect):
def get_console_port(self, console_type=None): def get_console_port(self, console_type=None):
if console_type is None: if console_type is None:
console_type = self.get_console_type() console_type = self.get_console_type()
port = util.get_xml_path(self._XMLDesc(0), port = util.get_xml_path(self._XMLDesc(0), "/domain/devices/graphics[@type='%s']/@port" % console_type)
"/domain/devices/graphics[@type='%s']/@port" % console_type)
return port return port
def get_console_websocket_port(self): def get_console_websocket_port(self):
@ -691,8 +718,7 @@ class wvmInstance(wvmConnect):
return websocket_port return websocket_port
def get_console_passwd(self): def get_console_passwd(self):
return util.get_xml_path(self._XMLDesc(VIR_DOMAIN_XML_SECURE), return util.get_xml_path(self._XMLDesc(VIR_DOMAIN_XML_SECURE), "/domain/devices/graphics/@passwd")
"/domain/devices/graphics/@passwd")
def set_console_passwd(self, passwd): def set_console_passwd(self, passwd):
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE) xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
@ -735,8 +761,29 @@ class wvmInstance(wvmConnect):
self._defineXML(newxml) self._defineXML(newxml)
def get_console_keymap(self): def get_console_keymap(self):
return util.get_xml_path(self._XMLDesc(VIR_DOMAIN_XML_SECURE), return util.get_xml_path(self._XMLDesc(VIR_DOMAIN_XML_SECURE), "/domain/devices/graphics/@keymap") or ''
"/domain/devices/graphics/@keymap") or ''
def get_video_model(self):
""" :return only primary video card"""
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
tree = etree.fromstring(xml)
video_models = tree.xpath("/domain/devices/video/model")
for model in video_models:
if model.get('primary') == 'yes' or len(video_models) == 1:
return model.get('type')
def set_video_model(self, model):
""" Changes only primary video card"""
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
tree = etree.fromstring(xml)
video_models = tree.xpath("/domain/devices/video/model")
video_xml = "<model type='{}'/>".format(model)
for model in video_models:
if model.get('primary') == 'yes' or len(video_models) == 1:
parent = model.getparent()
parent.remove(model)
parent.append(etree.fromstring(video_xml))
self._defineXML(etree.tostring(tree))
def resize(self, cur_memory, memory, cur_vcpu, vcpu, disks=[]): def resize(self, cur_memory, memory, cur_vcpu, vcpu, disks=[]):
""" """
@ -1007,25 +1054,34 @@ class wvmInstance(wvmConnect):
bridge_name = net.bridgeName() bridge_name = net.bridgeName()
return bridge_name return bridge_name
def add_network(self, mac_address, source, source_type='net', interface_type='bridge', model='virtio', nwfilter=None): def add_network(self, mac_address, source, source_type='net', model='virtio', nwfilter=None):
bridge_name = self.get_bridge_name(source, source_type) bridge_name = self.get_bridge_name(source, source_type)
xml_interface = """
forward_mode = self.get_network_forward(source)
if forward_mode in ['nat', 'isolated', 'routed']:
interface_type = 'network'
else:
interface_type = 'bridge'
xml_iface = """
<interface type='%s'> <interface type='%s'>
<mac address='%s'/> <mac address='%s'/>""" % (interface_type, mac_address)
<source bridge='%s'/> if interface_type == 'network':
<model type='%s'/> xml_iface += """<source network='%s'/>""" % source
""" % (interface_type, mac_address, bridge_name, model) else:
xml_iface += """<source bridge='%s'/>""" % bridge_name
xml_iface += """<model type='%s'/>""" % model
if nwfilter: if nwfilter:
xml_interface += """ xml_iface += """
<filterref filter='%s'/> <filterref filter='%s'/>
""" % nwfilter """ % nwfilter
xml_interface += """</interface>""" xml_iface += """</interface>"""
if self.get_status() == 1: if self.get_status() == 1:
self.instance.attachDeviceFlags(xml_interface, VIR_DOMAIN_AFFECT_LIVE) self.instance.attachDeviceFlags(xml_iface, VIR_DOMAIN_AFFECT_LIVE)
self.instance.attachDeviceFlags(xml_interface, VIR_DOMAIN_AFFECT_CONFIG) self.instance.attachDeviceFlags(xml_iface, VIR_DOMAIN_AFFECT_CONFIG)
if self.get_status() == 5: if self.get_status() == 5:
self.instance.attachDeviceFlags(xml_interface, VIR_DOMAIN_AFFECT_CONFIG) self.instance.attachDeviceFlags(xml_iface, VIR_DOMAIN_AFFECT_CONFIG)
def delete_network(self, mac_address): def delete_network(self, mac_address):
tree = ElementTree.fromstring(self._XMLDesc(0)) tree = ElementTree.fromstring(self._XMLDesc(0))
@ -1044,10 +1100,11 @@ class wvmInstance(wvmConnect):
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE) xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
tree = ElementTree.fromstring(xml) tree = ElementTree.fromstring(xml)
for num, interface in enumerate(tree.findall('devices/interface')): for num, interface in enumerate(tree.findall('devices/interface')):
net_source = network_data['net-source-' + str(num)] net_mac = network_data.get('net-mac-' + str(num))
net_source_type = network_data['net-source-' + str(num) + '-type'] if net_mac is None: continue
net_mac = network_data['net-mac-' + str(num)] net_source = network_data.get('net-source-' + str(num))
net_filter = network_data['net-nwfilter-' + str(num)] net_source_type = network_data.get('net-source-' + str(num) + '-type')
net_filter = network_data.get('net-nwfilter-' + str(num))
bridge_name = self.get_bridge_name(net_source, net_source_type) bridge_name = self.get_bridge_name(net_source, net_source_type)
if interface.get('type') == 'bridge': if interface.get('type') == 'bridge':
source = interface.find('mac') source = interface.find('mac')
@ -1103,10 +1160,78 @@ class wvmInstance(wvmConnect):
tree = ElementTree.fromstring(xml) tree = ElementTree.fromstring(xml)
self._set_options(tree, options) self._set_options(tree, options)
new_xml = ElementTree.tostring(tree) new_xml = ElementTree.tostring(tree)
self._defineXML(new_xml) self._defineXML(new_xml)
def set_memory(self, size, flags=0): def set_memory(self, size, flags=0):
self.instance.setMemoryFlags(size, flags) self.instance.setMemoryFlags(size, flags)
def get_all_qos(self):
qos_values = dict()
tree = etree.fromstring(self._XMLDesc(0))
qos = tree.xpath("/domain/devices/interface")
for q in qos:
bound_list = list()
mac = q.xpath('mac/@address')
band = q.find('bandwidth')
if band is not None:
in_qos = band.find('inbound')
if in_qos is not None:
in_av = in_qos.get('average')
in_peak = in_qos.get('peak')
in_burst = in_qos.get('burst')
in_floor = in_qos.get('floor')
bound_list.append({'direction': 'inbound', 'average': in_av, 'peak': in_peak, 'floor': in_floor, 'burst': in_burst})
out_qos = band.find('outbound')
if out_qos is not None:
out_av = out_qos.get('average')
out_peak = out_qos.get('peak')
out_burst = out_qos.get('burst')
bound_list.append({'direction': 'outbound', 'average': out_av, 'peak': out_peak, 'burst': out_burst})
qos_values[mac[0]] = bound_list
return qos_values
def set_qos(self, mac, direction, average, peak, burst):
if direction == "inbound":
xml = "<inbound average='{}' peak='{}' burst='{}'/>".format(average, peak, burst)
elif direction == "outbound":
xml = "<outbound average='{}' peak='{}' burst='{}'/>".format(average, peak, burst)
else:
raise Exception('Direction must be inbound or outbound')
tree = etree.fromstring(self._XMLDesc(0))
macs = tree.xpath("/domain/devices/interface/mac")
for cur_mac in macs:
if cur_mac.get("address") == mac:
interface = cur_mac.getparent()
band = interface.find('bandwidth')
if band is None:
xml = "<bandwidth>" + xml + "</bandwidth>"
interface.append(etree.fromstring(xml))
else:
direct = band.find(direction)
if direct is not None:
parent = direct.getparent()
parent.remove(direct)
parent.append(etree.fromstring(xml))
else:
band.append(etree.fromstring(xml))
new_xml = etree.tostring(tree)
self.wvm.defineXML(new_xml)
def unset_qos(self, mac, direction):
tree = etree.fromstring(self._XMLDesc(0))
for direct in tree.xpath("/domain/devices/interface/bandwidth/{}".format(direction)):
band_el = direct.getparent()
interface_el = band_el.getparent() # parent bandwidth,it parent is interface
parent_mac = interface_el.xpath('mac/@address')
if parent_mac[0] == mac:
band_el.remove(direct)
self.wvm.defineXML(etree.tostring(tree))