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 @@

{% trans "Hostname" %}

-

{% trans "Hypervisor" %}

+

{% trans "Hypervisors" %}

+

{% trans "Emulator" %}

{% trans "Memory" %}

{% trans "Architecture" %}

{% trans "Logical CPUs" %}

@@ -44,7 +45,14 @@

{{ hostname }}

-

{{ hypervisor }}

+

{% for arch, hpv in hypervisor.items %} + + {{ arch }} + {% for h in hpv %} + {{ h }}{% endfor %} + {% endfor %} +

+

{{ emulator }}

{{ host_memory|filesizeformat }}

{{ host_arch }}

{{ logical_cpu }}

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 @@
- {% include 'errors_block.html' %} + {% include 'pleasewaitdialog.html' %}
@@ -108,6 +108,36 @@
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
@@ -115,7 +145,7 @@
{% if storages %} - {% else %} @@ -202,8 +232,39 @@ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ {% if storages %} - {% else %} @@ -218,9 +279,10 @@
{% csrf_token %}
- + +
-
@@ -297,6 +359,16 @@
+
+ +
+ +
+
@@ -313,6 +385,36 @@
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
@@ -435,5 +537,10 @@ {% 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 %}

{% trans "This action opens a remote viewer with a connection to the console of the instance." %}

- {% trans "VDI" %} +
+ + + {% trans "VDI" %} + +
{% endifequal %} @@ -511,8 +516,8 @@
  • - - {% trans "Restore From Snapshot" %} + + {% trans "Manage Snapshots" %}
  • @@ -538,10 +543,10 @@

    {% trans "To take a snapshot please Power Off the instance." %}

    {% endifequal %}
    -
    +
    {% ifequal status 5 %} {% if snapshots %} -

    {% trans "Choose a snapshot for restore" %}

    +

    {% trans "Choose a snapshot for restore/delete" %}

    @@ -560,7 +565,7 @@ {% csrf_token %} {% ifequal status 5 %} - {% else %} @@ -573,7 +578,7 @@ {% for host, inst in all_host_vms.items %} - + {% for vm, info in inst.items %} - + -
    {% csrf_token %} - @@ -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 @@
    + + {{ host.1 }} @@ -32,7 +34,7 @@
    {{ forloop.counter }} {{ vm }}
    {{ info.title }} @@ -119,4 +121,15 @@ {% endfor %} {% endfor %}
    \ No newline at end of file + +{% block 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 += """""" if mac: xml += """""" % mac - xml += """ - """ % net + xml += """""" % net + if nwfilter: + xml += """""" % nwfilter if virtio: xml += """""" xml += """""" + if console_pass == "random": + console_pass = "passwd='" + util.randomPasswd() + "'" + else: + if not console_pass == "": + console_pass = "passwd='" + console_pass + "'" + xml += """ - + - """ % (QEMU_CONSOLE_DEFAULT_TYPE, util.randomPasswd()) + """ % (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") + + +