diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..c419263 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/computes/templates/overview.html b/computes/templates/overview.html index 8bfad6e..34e68c5 100644 --- a/computes/templates/overview.html +++ b/computes/templates/overview.html @@ -34,7 +34,8 @@ <h3 class="page-header">{% trans "Basic details" %}</h3> <div class="col-xs-4 col-sm-3"> <p>{% trans "Hostname" %}</p> - <p>{% trans "Hypervisor" %}</p> + <p>{% trans "Hypervisors" %}</p> + <p>{% trans "Emulator" %}</p> <p>{% trans "Memory" %}</p> <p>{% trans "Architecture" %}</p> <p>{% trans "Logical CPUs" %}</p> @@ -44,7 +45,14 @@ </div> <div class="col-xs-8 col-sm-7"> <p>{{ hostname }}</p> - <p>{{ hypervisor }}</p> + <p>{% for arch, hpv in hypervisor.items %} + <span class="glyphicon glyphicon-chevron-right"></span> + <span class="label label-default">{{ arch }}</span> + {% for h in hpv %} + <span class="label label-primary">{{ h }}</span>{% endfor %} + {% endfor %} + </p> + <p>{{ emulator }}</p> <p>{{ host_memory|filesizeformat }}</p> <p>{{ host_arch }}</p> <p>{{ logical_cpu }}</p> diff --git a/computes/views.py b/computes/views.py index 8bea447..18ad680 100644 --- a/computes/views.py +++ b/computes/views.py @@ -156,6 +156,7 @@ def overview(request, compute_id): hostname, host_arch, host_memory, logical_cpu, model_cpu, uri_conn = conn.get_node_info() hypervisor = conn.hypervisor_type() mem_usage = conn.get_memory_usage() + emulator = conn.emulator() conn.close() except libvirtError as lib_err: error_messages.append(lib_err) diff --git a/create/forms.py b/create/forms.py index fccf944..0f208ce 100644 --- a/create/forms.py +++ b/create/forms.py @@ -2,6 +2,7 @@ import re from django import forms from django.utils.translation import ugettext_lazy as _ from create.models import Flavor +from webvirtcloud.settings import QEMU_CONSOLE_LISTEN_ADDRESSES class FlavorAddForm(forms.Form): @@ -45,6 +46,9 @@ class NewVMForm(forms.Form): meta_prealloc = forms.BooleanField(required=False) virtio = forms.BooleanField(required=False) mac = forms.CharField(required=False) + console_pass = forms.CharField(required=False,empty_value="", widget=forms.PasswordInput()) + video = forms.CharField(error_messages={'required': _('Please select a graphic display')}) + listener_addr = forms.ChoiceField(required=True, widget=forms.RadioSelect, choices=QEMU_CONSOLE_LISTEN_ADDRESSES) def clean_name(self): name = self.cleaned_data['name'] @@ -54,3 +58,4 @@ class NewVMForm(forms.Form): elif len(name) > 20: raise forms.ValidationError(_('The name of the virtual machine must not exceed 20 characters')) return name + diff --git a/create/templates/create_instance.html b/create/templates/create_instance.html index 42f18aa..88b5fbe 100644 --- a/create/templates/create_instance.html +++ b/create/templates/create_instance.html @@ -14,8 +14,8 @@ </div> </div> <!-- /.row --> - {% include 'errors_block.html' %} + {% include 'pleasewaitdialog.html' %} <div class="row"> <div class="col-lg-12"> @@ -108,6 +108,36 @@ </select> </div> </div> + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Video" %}</label> + <div class="col-sm-6"> + <select name="video" class="form-control"> + {% if not videos %} + <option value="vga">vga</option> + <option value="cirrus">cirrus</option> + {% endif %} + {% for video in videos %} + <option value="{{ video }}">{{ video }}</option> + {% endfor %} + </select> + </div> + </div> + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Console Password" %}</label> + <div class="col-sm-6"> + <input type="password" class="form-control" name="console_pass" placeholder="{% trans "Console Password" %}" maxlength="14"> + </div> + </div> + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Console Access" %}</label> + <div class="col-sm-6"> + <select name="listener_addr" class="form-control"> + {% for addr, label in listener_addr %} + <option value="{{ addr }}" {% if addr == "0.0.0.0" %} selected {% endif %}>{{ label }}</option> + {% endfor %} + </select> + </div> + </div> <div class="form-group"> <label class="col-sm-3 control-label">{% trans "VirtIO" %}</label> <div class="col-sm-6"> @@ -115,7 +145,7 @@ </div> </div> {% if storages %} - <button type="submit" class="btn btn-primary" name="create" value="1"> + <button type="submit" class="btn btn-primary" name="create" onclick="showPleaseWaitDialog()" value="1"> {% trans "Create" %} </button> {% else %} @@ -202,8 +232,39 @@ <input type="checkbox" name="virtio" value="true" checked> </div> </div> + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Video" %}</label> + <div class="col-sm-6"> + <select name="video" class="form-control"> + {% if not videos %} + <option value="vga">vga</option> + <option value="cirrus">cirrus</option> + {% endif %} + {% for video in videos %} + <option value="{{ video }}">{{ video }}</option> + {% endfor %} + </select> + </div> + </div> + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Console Password" %}</label> + <div class="col-sm-6"> + <input type="password" class="form-control" name="console_pass" placeholder="{% trans "Console Password" %}" maxlength="14"> + </div> + </div> + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Console Access" %}</label> + <div class="col-sm-6"> + <select name="listener_addr" class="form-control"> + {% for addr, label in listener_addr %} + <option value="{{ addr }}" {% if addr == "0.0.0.0" %} selected {% endif %}>{{ label }}</option> + {% endfor %} + </select> + </div> + </div> + {% if storages %} - <button type="submit" class="btn btn-primary" name="create" value="1"> + <button type="submit" class="btn btn-primary" name="create" value="1" onclick="showPleaseWaitDialog()"> {% trans "Create" %} </button> {% else %} @@ -218,9 +279,10 @@ <div class="well"> <form class="form-horizontal" method="post" role="form">{% csrf_token %} <div class="col-sm-12" id="xmlheight"> - <textarea id="editor" name="from_xml"></textarea> + <input type="hidden" name="dom_xml"/> + <textarea id="editor"></textarea> </div> - <button type="submit" class="btn btn-primary" name="create_xml"> + <button type="submit" class="btn btn-primary" name="create_xml" onclick="showPleaseWaitDialog()"> {% trans "Create" %} </button> </form> @@ -297,6 +359,16 @@ </select> </div> </div> + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "HDD cache mode" %}</label> + <div class="col-sm-6"> + <select id="cache_mode" name="cache_mode" class="form-control"> + {% for mode, name in cache_modes %} + <option value="{{ mode }}">{% trans name %}</option> + {% endfor %} + </select> + </div> + </div> <div class="form-group"> <label class="col-sm-3 control-label">{% trans "Network" %}</label> <div class="col-sm-6"> @@ -313,6 +385,36 @@ <input type="text" class="form-control" name="mac" maxlength="17" value="{{ mac_auto }}" required pattern="[a-zA-Z0-9:]+"> </div> </div> + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Video" %}</label> + <div class="col-sm-6"> + <select name="video" class="form-control"> + {% if not videos %} + <option value="vga">vga</option> + <option value="cirrus">cirrus</option> + {% endif %} + {% for video in videos %} + <option value="{{ video }}">{{ video }}</option> + {% endfor %} + </select> + </div> + </div> + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Console Password" %}</label> + <div class="col-sm-6"> + <input type="password" class="form-control" name="console_pass" placeholder="{% trans "Console Password" %}" maxlength="14"> + </div> + </div> + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Console Access" %}</label> + <div class="col-sm-6"> + <select name="listener_addr" class="form-control"> + {% for addr, label in listener_addr %} + <option value="{{ addr }}" {% if addr == "0.0.0.0" %} selected {% endif %}>{{ label }}</option> + {% endfor %} + </select> + </div> + </div> <div class="form-group"> <label class="col-sm-3 control-label">{% trans "Host-Model" %}</label> <div class="col-sm-6"> @@ -435,5 +537,10 @@ <script> var editor = ace.edit("editor"); editor.getSession().setMode("ace/mode/xml"); + + var input = $('input[name="dom_xml"]'); + editor.getSession().on("change",function () { + input.val(editor.getSession().getValue()); + }) </script> {% endblock %} diff --git a/create/views.py b/create/views.py index cd8e133..0c39a1d 100644 --- a/create/views.py +++ b/create/views.py @@ -10,7 +10,8 @@ from instances.models import Instance from vrtManager.create import wvmCreate from vrtManager import util from libvirt import libvirtError - +from webvirtcloud.settings import QEMU_CONSOLE_LISTEN_ADDRESSES +from django.contrib import messages @login_required def create_instance(request, compute_id): @@ -27,7 +28,7 @@ def create_instance(request, compute_id): storages = [] networks = [] meta_prealloc = False - computes = Compute.objects.all() + #computes = Compute.objects.all() compute = get_object_or_404(Compute, pk=compute_id) flavors = Flavor.objects.filter().order_by('id') @@ -40,7 +41,9 @@ def create_instance(request, compute_id): storages = sorted(conn.get_storages(only_actives=True)) networks = sorted(conn.get_networks()) instances = conn.get_instances() + videos = conn.get_video() cache_modes = sorted(conn.get_cache_modes().items()) + listener_addr = QEMU_CONSOLE_LISTEN_ADDRESSES mac_auto = util.randomMAC() get_images = sorted(conn.get_storages_images()) except libvirtError as lib_err: @@ -71,10 +74,10 @@ def create_instance(request, compute_id): delete_flavor.delete() return HttpResponseRedirect(request.get_full_path()) if 'create_xml' in request.POST: - xml = request.POST.get('from_xml', '') + xml = request.POST.get('dom_xml', '') try: name = util.get_xml_path(xml, '/domain/name') - except util.etree.ParserError: + except util.etree.Error as err: name = None if name in instances: error_msg = _("A virtual machine with this name already exists") @@ -110,8 +113,13 @@ def create_instance(request, compute_id): error_messages.append(lib_err.message) elif data['template']: templ_path = conn.get_volume_path(data['template']) - clone_path = conn.clone_from_template(data['name'], templ_path, metadata=meta_prealloc) - volumes[clone_path] = conn.get_volume_type(clone_path) + dest_vol = conn.get_volume_path(data["name"] + ".img") + if dest_vol: + error_msg = _("Image has already exist. Please check volumes or change instance name") + error_messages.append(error_msg) + else: + clone_path = conn.clone_from_template(data['name'], templ_path, metadata=meta_prealloc) + volumes[clone_path] = conn.get_volume_type(clone_path) else: if not data['images']: error_msg = _("First you need to create or select an image") @@ -131,12 +139,14 @@ def create_instance(request, compute_id): try: conn.create_instance(data['name'], data['memory'], data['vcpu'], data['host_model'], uuid, volumes, data['cache_mode'], data['networks'], data['virtio'], + data["listener_addr"], None, data["video"], data["console_pass"], data['mac']) create_instance = Instance(compute_id=compute_id, name=data['name'], uuid=uuid) create_instance.save() + messages.success(request,"Instance is created.") return HttpResponseRedirect(reverse('instance', args=[compute_id, data['name']])) except libvirtError as lib_err: - if data['hdd_size']: + if data['hdd_size'] or volumes[clone_path]: conn.delete_volume(volumes.keys()[0]) error_messages.append(lib_err) conn.close() diff --git a/datasource/views.py b/datasource/views.py index d23d99a..163ecb5 100644 --- a/datasource/views.py +++ b/datasource/views.py @@ -7,13 +7,15 @@ from libvirt import libvirtError import json import socket -OS_VERSIONS = [ 'latest', '' ] +OS_VERSIONS = ['latest', ''] OS_UUID = "iid-dswebvirtcloud" + def os_index(request): response = '\n'.join(OS_VERSIONS) return HttpResponse(response) + def os_metadata_json(request, version): """ :param request: @@ -27,9 +29,10 @@ def os_metadata_json(request, version): response = { 'uuid': OS_UUID, 'hostname': hostname } return HttpResponse(json.dumps(response)) else: - err = 'Invalid version: %s' % version + err = 'Invalid version: {}'.format(version) raise Http404(err) + def os_userdata(request, version): """ :param request: @@ -51,9 +54,10 @@ def os_userdata(request, version): return render(request, 'user_data', locals()) else: - err = 'Invalid version: %s' % version + err = 'Invalid version: {}'.format(version) raise Http404(err) + def get_client_ip(request): x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: @@ -62,10 +66,15 @@ def get_client_ip(request): ip = request.META.get('REMOTE_ADDR') return ip + def get_hostname_by_ip(ip): - addrs = socket.gethostbyaddr(ip) + try: + addrs = socket.gethostbyaddr(ip) + except Exception: + addrs = [ip,] return addrs[0] + def get_vdi_url(request, vname): instance = Instance.objects.get(name=vname) compute = instance.compute diff --git a/instances/templates/instance.html b/instances/templates/instance.html index 4aadf3e..9b34878 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -321,7 +321,12 @@ {% ifequal status 1 %} <div role="tabpanel" class="tab-pane tab-pane-bordered" id="vdiconsole"> <p>{% trans "This action opens a remote viewer with a connection to the console of the instance." %}</p> - <a href="#" class="btn btn-lg btn-success pull-right" id="vdi_url" >{% trans "VDI" %}</a> + <div class="input-group"> + <input type="text" class="input-lg disabled form-control" disabled id="vdi_url_input"/> + <span class="input-group-btn"> + <a href="#" class="btn btn-lg btn-success" id="vdi_url" >{% trans "VDI" %}</a> + </span> + </div> <div class="clearfix"></div> </div> {% endifequal %} @@ -511,8 +516,8 @@ </a> </li> <li role="presentation"> - <a href="#restoresnapshot" aria-controls="restoresnapshot" role="tab" data-toggle="tab"> - {% trans "Restore From Snapshot" %} + <a href="#managesnapshot" aria-controls="managesnapshot" role="tab" data-toggle="tab"> + {% trans "Manage Snapshots" %} </a> </li> </ul> @@ -538,10 +543,10 @@ <p>{% trans "To take a snapshot please Power Off the instance." %}</p> {% endifequal %} </div> - <div role="tabpanel" class="tab-pane tab-pane-bordered" id="restoresnapshot"> + <div role="tabpanel" class="tab-pane tab-pane-bordered" id="managesnapshot"> {% ifequal status 5 %} {% if snapshots %} - <p>{% trans "Choose a snapshot for restore" %}</p> + <p>{% trans "Choose a snapshot for restore/delete" %}</p> <div class="table-responsive"> <table class="table"> <thead> @@ -560,7 +565,7 @@ <form action="" method="post" style="height:10px" role="form">{% csrf_token %} <input type="hidden" name="name" value="{{ snap.name }}"> {% ifequal status 5 %} - <button type="submit" class="btn btn-sm btn-default" name="revert_snapshot" onclick="return confirm('Are you sure?')"> + <button type="submit" class="btn btn-sm btn-default" name="revert_snapshot" title="Revert to this Snapshot" onclick="return confirm('Are you sure?')"> <span class="glyphicon glyphicon-save"></span> </button> {% else %} @@ -573,7 +578,7 @@ <td style="width:30px;"> <form action="" method="post" role="form">{% csrf_token %} <input type="hidden" name="name" value="{{ snap.name }}"> - <button type="submit" class="btn btn-sm btn-default" name="delete_snapshot" onclick="return confirm('{% trans "Are you sure?" %}')"> + <button type="submit" class="btn btn-sm btn-default" name="delete_snapshot" title="Delete Snapshot" onclick="return confirm('{% trans "Are you sure?" %}')"> <span class="glyphicon glyphicon-trash"></span> </button> </form> @@ -1335,6 +1340,7 @@ $(document).ready(function () { // set vdi url $.get("/datasource/vdi/{{ vname }}/", function(data) { + $("#vdi_url_input").attr("value", data); $("#vdi_url").attr("href", data); }); }); @@ -1550,7 +1556,7 @@ } }); } - if (~$.inArray(hash, ['#takesnapshot', '#restoresnapshot'])) { + if (~$.inArray(hash, ['#takesnapshot', '#managesnapshot'])) { var btnsect = $('#navbtn>li>a'); $(btnsect).each(function () { if ($(this).attr('href') === '#snapshots') { diff --git a/instances/templates/instances_grouped.html b/instances/templates/instances_grouped.html index 7358c28..2e129c2 100644 --- a/instances/templates/instances_grouped.html +++ b/instances/templates/instances_grouped.html @@ -14,7 +14,9 @@ <tbody class="searchable"> {% for host, inst in all_host_vms.items %} <tr class="active" style="font-weight: bold;border-bottom: 2px solid darkgray;border-top: 2px solid darkgray;"> - <td><span class="fa fa-server"></span> </td> + <td> + <span id="collapse_host_instances_{{ host.1 }}" class="glyphicon glyphicon-chevron-up" onclick="hide_host_instances('{{ host.1 }}');"></span> + </td> <td><a href="{% url 'overview' host.0 %}">{{ host.1 }}</a></td> <td></td> <td> @@ -32,7 +34,7 @@ </tr> {% for vm, info in inst.items %} - <tr> + <tr host="{{ host.1 }}"> <td style="text-align: right">{{ forloop.counter }} </td> <td>  <a href="{% url 'instance' host.0 vm %}">{{ vm }}</a><br> <small><em>{{ info.title }}</em></small> @@ -119,4 +121,15 @@ {% endfor %} {% endfor %} </tbody> -</table> \ No newline at end of file +</table> +{% block script %} +<script> +function hide_host_instances(host) { + var rows = $('table tr'); + + host_rows = rows.filter('[host='+host+']'); + host_rows.toggle(); + $('span#collapse_host_instances_'+host).toggleClass("glyphicon-chevron-down").toggleClass("glyphicon-chevron-up"); +} +</script> +{% endblock %} diff --git a/instances/views.py b/instances/views.py index 6efbedf..4c6c4d6 100644 --- a/instances/views.py +++ b/instances/views.py @@ -451,7 +451,7 @@ def instance(request, compute_id, vname): else: error_messages.append(msg) else: - msg = _("Please shutdow down your instance and then try again") + msg = _("Please shutdown down your instance and then try again") error_messages.append(msg) if 'addpublickey' in request.POST: @@ -473,7 +473,7 @@ def instance(request, compute_id, vname): else: error_messages.append(msg) else: - msg = _("Please shutdow down your instance and then try again") + msg = _("Please shutdown down your instance and then try again") error_messages.append(msg) if 'resize' in request.POST and (request.user.is_superuser or request.user.is_staff or userinstance.is_change): @@ -550,14 +550,14 @@ def instance(request, compute_id, vname): conn.create_snapshot(name) msg = _("New snapshot") addlogmsg(request.user.username, instance.name, msg) - return HttpResponseRedirect(request.get_full_path() + '#restoresnapshot') + return HttpResponseRedirect(request.get_full_path() + '#managesnapshot') if 'delete_snapshot' in request.POST: snap_name = request.POST.get('name', '') conn.snapshot_delete(snap_name) msg = _("Delete snapshot") addlogmsg(request.user.username, instance.name, msg) - return HttpResponseRedirect(request.get_full_path() + '#restoresnapshot') + return HttpResponseRedirect(request.get_full_path() + '#managesnapshot') if 'revert_snapshot' in request.POST: snap_name = request.POST.get('name', '') diff --git a/static/js/sortable.min.js b/static/js/sortable.min.js index 242074c..8278f50 100755 --- a/static/js/sortable.min.js +++ b/static/js/sortable.min.js @@ -1,2 +1,2 @@ -/*! sortable.js 0.5.0 */ -(function(){var a,b,c,d,e,f;a="table[data-sortable]",c=/^-?[£$¤]?[\d,.]+%?$/,f=/^\s+|\s+$/g,e="ontouchstart"in document.documentElement,b=e?"touchstart":"click",d={init:function(){var b,c,e,f,g;for(c=document.querySelectorAll(a),g=[],e=0,f=c.length;f>e;e++)b=c[e],g.push(d.initTable(b));return g},initTable:function(a){var b,c,e,f,g;if(1===a.tHead.rows.length&&"true"!==a.getAttribute("data-sortable-initialized")){for(a.setAttribute("data-sortable-initialized","true"),e=a.querySelectorAll("th"),b=f=0,g=e.length;g>f;b=++f)c=e[b],"false"!==c.getAttribute("data-sortable")&&d.setupClickableTH(a,c,b);return a}},setupClickableTH:function(a,c,e){var f;return f=d.getColumnType(a,e),c.addEventListener(b,function(){var b,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u;for(j="true"===this.getAttribute("data-sorted"),k=this.getAttribute("data-sorted-direction"),b=j?"ascending"===k?"descending":"ascending":f.defaultSortDirection,m=this.parentNode.querySelectorAll("th"),n=0,q=m.length;q>n;n++)c=m[n],c.setAttribute("data-sorted","false"),c.removeAttribute("data-sorted-direction");for(this.setAttribute("data-sorted","true"),this.setAttribute("data-sorted-direction",b),l=a.tBodies[0],h=[],t=l.rows,o=0,r=t.length;r>o;o++)g=t[o],h.push([d.getNodeValue(g.cells[e]),g]);for(j?h.reverse():h.sort(f.compare),u=[],p=0,s=h.length;s>p;p++)i=h[p],u.push(l.appendChild(i[1]));return u})},getColumnType:function(a,b){var e,f,g,h,i;for(i=a.tBodies[0].rows,g=0,h=i.length;h>g;g++)if(e=i[g],f=d.getNodeValue(e.cells[b]),""!==f&&f.match(c))return d.types.numeric;return d.types.alpha},getNodeValue:function(a){return a?null!==a.getAttribute("data-value")?a.getAttribute("data-value"):"undefined"!=typeof a.innerText?a.innerText.replace(f,""):a.textContent.replace(f,""):""},types:{numeric:{defaultSortDirection:"descending",compare:function(a,b){var c,d;return c=parseFloat(a[0].replace(/[^0-9.-]/g,"")),d=parseFloat(b[0].replace(/[^0-9.-]/g,"")),isNaN(c)&&(c=0),isNaN(d)&&(d=0),d-c}},alpha:{defaultSortDirection:"ascending",compare:function(a,b){var c,d;return c=a[0].toLowerCase(),d=b[0].toLowerCase(),c===d?0:d>c?-1:1}}}},setTimeout(d.init,0),window.Sortable=d}).call(this); \ No newline at end of file +/*! sortable.js 0.8.0 */ +(function(){var a,b,c,d,e,f,g;a="table[data-sortable]",d=/^-?[£$¤]?[\d,.]+%?$/,g=/^\s+|\s+$/g,c=["click"],f="ontouchstart"in document.documentElement,f&&c.push("touchstart"),b=function(a,b,c){return null!=a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},e={init:function(b){var c,d,f,g,h;for(null==b&&(b={}),null==b.selector&&(b.selector=a),d=document.querySelectorAll(b.selector),h=[],f=0,g=d.length;g>f;f++)c=d[f],h.push(e.initTable(c));return h},initTable:function(a){var b,c,d,f,g,h;if(1===(null!=(h=a.tHead)?h.rows.length:void 0)&&"true"!==a.getAttribute("data-sortable-initialized")){for(a.setAttribute("data-sortable-initialized","true"),d=a.querySelectorAll("th"),b=f=0,g=d.length;g>f;b=++f)c=d[b],"false"!==c.getAttribute("data-sortable")&&e.setupClickableTH(a,c,b);return a}},setupClickableTH:function(a,d,f){var g,h,i,j,k,l;for(i=e.getColumnType(a,f),h=function(b){var c,g,h,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D;if(b.handled===!0)return!1;for(b.handled=!0,m="true"===this.getAttribute("data-sorted"),n=this.getAttribute("data-sorted-direction"),h=m?"ascending"===n?"descending":"ascending":i.defaultSortDirection,p=this.parentNode.querySelectorAll("th"),s=0,w=p.length;w>s;s++)d=p[s],d.setAttribute("data-sorted","false"),d.removeAttribute("data-sorted-direction");if(this.setAttribute("data-sorted","true"),this.setAttribute("data-sorted-direction",h),o=a.tBodies[0],l=[],m){for(D=o.rows,v=0,z=D.length;z>v;v++)g=D[v],l.push(g);for(l.reverse(),B=0,A=l.length;A>B;B++)k=l[B],o.appendChild(k)}else{for(r=null!=i.compare?i.compare:function(a,b){return b-a},c=function(a,b){return a[0]===b[0]?a[2]-b[2]:i.reverse?r(b[0],a[0]):r(a[0],b[0])},C=o.rows,j=t=0,x=C.length;x>t;j=++t)k=C[j],q=e.getNodeValue(k.cells[f]),null!=i.comparator&&(q=i.comparator(q)),l.push([q,k,j]);for(l.sort(c),u=0,y=l.length;y>u;u++)k=l[u],o.appendChild(k[1])}return"function"==typeof window.CustomEvent&&"function"==typeof a.dispatchEvent?a.dispatchEvent(new CustomEvent("Sortable.sorted",{bubbles:!0})):void 0},l=[],j=0,k=c.length;k>j;j++)g=c[j],l.push(b(d,g,h));return l},getColumnType:function(a,b){var c,d,f,g,h,i,j,k,l,m,n;if(d=null!=(l=a.querySelectorAll("th")[b])?l.getAttribute("data-sortable-type"):void 0,null!=d)return e.typesObject[d];for(m=a.tBodies[0].rows,h=0,j=m.length;j>h;h++)for(c=m[h],f=e.getNodeValue(c.cells[b]),n=e.types,i=0,k=n.length;k>i;i++)if(g=n[i],g.match(f))return g;return e.typesObject.alpha},getNodeValue:function(a){var b;return a?(b=a.getAttribute("data-value"),null!==b?b:"undefined"!=typeof a.innerText?a.innerText.replace(g,""):a.textContent.replace(g,"")):""},setupTypes:function(a){var b,c,d,f;for(e.types=a,e.typesObject={},f=[],c=0,d=a.length;d>c;c++)b=a[c],f.push(e.typesObject[b.name]=b);return f}},e.setupTypes([{name:"numeric",defaultSortDirection:"descending",match:function(a){return a.match(d)},comparator:function(a){return parseFloat(a.replace(/[^0-9.-]/g,""),10)||0}},{name:"date",defaultSortDirection:"ascending",reverse:!0,match:function(a){return!isNaN(Date.parse(a))},comparator:function(a){return Date.parse(a)||0}},{name:"alpha",defaultSortDirection:"ascending",match:function(){return!0},compare:function(a,b){return a.localeCompare(b)}}]),setTimeout(e.init,0),"function"==typeof define&&define.amd?define(function(){return e}):"undefined"!=typeof exports?module.exports=e:window.Sortable=e}).call(this); \ No newline at end of file diff --git a/vrtManager/connection.py b/vrtManager/connection.py index c3fd27d..65c2915 100644 --- a/vrtManager/connection.py +++ b/vrtManager/connection.py @@ -352,6 +352,14 @@ class wvmConnect(object): """Return xml capabilities""" return self.wvm.getCapabilities() + def get_dom_cap_xml(self): + """ Return domcapabilities xml""" + emulatorbin = self.emulator() + machine = self.machine() + arch = self.wvm.getInfo()[0] + virttype = self.hypervisor_type()[arch][0] + return self.wvm.getDomainCapabilities(emulatorbin, arch, machine, virttype) + def is_kvm_supported(self): """Return KVM capabilities.""" return util.is_kvm_available(self.get_cap_xml()) @@ -391,10 +399,39 @@ class wvmConnect(object): 'directsync': 'Direct sync', # since libvirt 0.9.5 'unsafe': 'Unsafe', # since libvirt 0.9.7 } - + + def hypervisor_type(self): + """Return hypervisor type""" + def hypervisors(ctx): + result = {} + for arch in ctx.xpath('/capabilities/guest/arch'): + domain_types = arch.xpath('domain/@type') + arch_name = arch.xpath('@name')[0] + result[arch_name]= domain_types + return result + return util.get_xml_path(self.get_cap_xml(), func=hypervisors) + + def emulator(self): + """Return emulator """ + return util.get_xml_path(self.get_cap_xml(), "/capabilities/guest/arch/emulator") + + def machine(self): + """ Return machine type of emulation""" + return util.get_xml_path(self.get_cap_xml(), "/capabilities/guest/arch/machine") + def get_busses(self): """Get available busses""" - return [ 'ide', 'scsi', 'usb', 'virtio' ] + + def get_bus_list(ctx): + result = [] + for disk_enum in ctx.xpath('/domainCapabilities/devices/disk/enum'): + if disk_enum.xpath("@name")[0] == "bus": + for values in disk_enum: result.append(values.text) + return result + + # return [ 'ide', 'scsi', 'usb', 'virtio' ] + return util.get_xml_path(self.get_dom_cap_xml(), func=get_bus_list) + def get_image_formats(self): """Get available image formats""" @@ -404,6 +441,17 @@ class wvmConnect(object): """Get available image filename extensions""" return [ 'img', 'qcow', 'qcow2' ] + def get_video(self): + """ Get available graphics video types """ + + def get_video_list(ctx): + result = [] + for video_enum in ctx.xpath('/domainCapabilities/devices/video/enum'): + if video_enum.xpath("@name")[0] == "modelType": + for values in video_enum: result.append(values.text) + return result + return util.get_xml_path(self.get_dom_cap_xml(),func=get_video_list) + def get_iface(self, name): return self.wvm.interfaceLookupByName(name) diff --git a/vrtManager/create.py b/vrtManager/create.py index 2c2b936..1770c8a 100644 --- a/vrtManager/create.py +++ b/vrtManager/create.py @@ -2,6 +2,7 @@ import string from vrtManager import util from vrtManager.connection import wvmConnect from webvirtcloud.settings import QEMU_CONSOLE_DEFAULT_TYPE +from webvirtcloud.settings import QEMU_CONSOLE_LISTEN_ADDRESSES from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_FORMAT @@ -148,7 +149,7 @@ class wvmCreate(wvmConnect): vol = self.get_volume_by_path(path) vol.delete() - def create_instance(self, name, memory, vcpu, host_model, uuid, images, cache_mode, networks, virtio, mac=None): + def create_instance(self, name, memory, vcpu, host_model, uuid, images, cache_mode, networks, virtio, listen_addr, nwfilter=None, video="cirrus", console_pass="random", mac=None): """ Create VM function """ @@ -228,20 +229,27 @@ class wvmCreate(wvmConnect): xml += """<interface type='network'>""" if mac: xml += """<mac address='%s'/>""" % mac - xml += """<source network='%s'/> - <filterref filter='clean-traffic'/>""" % net + xml += """<source network='%s'/>""" % net + if nwfilter: + xml += """<filterref filter='%s'/>""" % nwfilter if virtio: xml += """<model type='virtio'/>""" xml += """</interface>""" + if console_pass == "random": + console_pass = "passwd='" + util.randomPasswd() + "'" + else: + if not console_pass == "": + console_pass = "passwd='" + console_pass + "'" + xml += """ <input type='mouse' bus='ps2'/> <input type='tablet' bus='usb'/> - <graphics type='%s' port='-1' autoport='yes' passwd='%s' listen='127.0.0.1'/> + <graphics type='%s' port='-1' autoport='yes' %s listen='%s'/> <console type='pty'/> <video> - <model type='cirrus'/> + <model type='%s'/> </video> <memballoon model='virtio'/> </devices> - </domain>""" % (QEMU_CONSOLE_DEFAULT_TYPE, util.randomPasswd()) + </domain>""" % (QEMU_CONSOLE_DEFAULT_TYPE, console_pass, listen_addr, video) self._defineXML(xml) diff --git a/vrtManager/hostdetails.py b/vrtManager/hostdetails.py index a45d24f..7dda889 100644 --- a/vrtManager/hostdetails.py +++ b/vrtManager/hostdetails.py @@ -67,6 +67,6 @@ class wvmHostDetails(wvmConnect): info.append(self.wvm.getURI()) #uri return info - def hypervisor_type(self): - """Return hypervisor type""" - return get_xml_path(self.get_cap_xml(), "/capabilities/guest/arch/domain/@type") + + +