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

added new feature: create and attach new volume to existing instance

move wvmCreate.get_cache_modes() to wvmConnect

add wvmConnect.get_busses(), wvmConnect.get_image_formats(), used in forms

add corresponding default values to settings (INSTANCE_VOLUME_DEFAULT_FORMAT INSTANCE_VOLUME_DEFAULT_BUS INSTANCE_VOLUME_DEFAULT_CACHE)
This commit is contained in:
Ing. Jan KRCMAR 2017-07-19 15:34:03 +02:00
parent b095a77da5
commit 53f5518706
6 changed files with 170 additions and 15 deletions

View file

@ -309,6 +309,11 @@
{% trans "Resize Instance" %} {% trans "Resize Instance" %}
</a> </a>
</li> </li>
<li role="presentation">
<a href="#addvolume" aria-controls="addvolume" role="tab" data-toggle="tab">
{% trans "Add New Volume" %}
</a>
</li>
</ul> </ul>
<!-- Tab panes --> <!-- Tab panes -->
<div class="tab-content"> <div class="tab-content">
@ -390,6 +395,88 @@
{% endif %} {% endif %}
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="addvolume">
{% if request.user.is_superuser or userinstace.is_change %}
<form class="form-horizontal" method="post" role="form">{% csrf_token %}
<p style="font-weight:bold;">{% trans "Volume parameters" %}</p>
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Storage" %}</label>
<div class="col-sm-4">
<select name="storage" class="form-control image-format">
{% for storage in storages %}
<option value="{{ storage }}">{{ storage }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Name" %}</label>
<div class="col-sm-4">
<input type="text" class="form-control" name="name" placeholder="{% trans "Name" %}" required pattern="[a-zA-Z0-9\.\-_]+">
</div>
<div class="col-sm-2">
<select name="extension" class="form-control image-format">
{% for format in formats %}
<option value="{{ format }}" {% if format == default_format %}selected{% endif %}>{% trans format %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Format" %}</label>
<div class="col-sm-4">
<select name="format" class="form-control image-format">
{% for format in formats %}
<option value="{{ format }}" {% if format == default_format %}selected{% endif %}>{% trans format %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Size" %}</label>
<div class="col-sm-4">
<input type="text" class="form-control" name="size" value="10" maxlength="3" required pattern="[0-9]+">
</div>
<label class="col-sm-1 control-label">{% trans "GB" %}</label>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Bus" %}</label>
<div class="col-sm-4">
<select name="bus" class="form-control image-format">
{% for bus in busses %}
<option value="{{ bus }}" {% if bus == default_bus %}selected{% endif %}>{% trans bus %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Cache" %}</label>
<div class="col-sm-4">
<select name="cache" class="form-control image-format">
{% for mode, name in cache_modes %}
<option value="{{ mode }}" {% if mode == default_cache %}selected{% endif %}>{% trans name %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group meta-prealloc">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Metadata" %}</label>
<div class="col-sm-4">
<input type="checkbox" name="meta_prealloc" value="true">
</div>
</div>
{% ifequal status 5 %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="addvolume">{% trans "Add volume" %}</button>
{% else %}
<button class="btn btn-lg btn-success pull-right disabled">{% trans "Add volume" %}</button>
{% endifequal %}
</form>
{% else %}
{% trans "You don't have permission for resizing instance" %}
<button class="btn btn-lg btn-success pull-right disabled">{% trans "Add volume" %}</button>
{% endif %}
<div class="clearfix"></div>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -4,7 +4,7 @@ import json
import socket import socket
import crypt import crypt
import re import re
from string import letters, digits import string
from random import choice from random import choice
from bisect import insort from bisect import insort
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
@ -18,6 +18,7 @@ from accounts.models import UserInstance, UserSSHKey
from vrtManager.hostdetails import wvmHostDetails from vrtManager.hostdetails import wvmHostDetails
from vrtManager.instance import wvmInstance, wvmInstances from vrtManager.instance import wvmInstance, wvmInstances
from vrtManager.connection import connection_manager from vrtManager.connection import connection_manager
from vrtManager.create import wvmCreate
from vrtManager.util import randomPasswd from vrtManager.util import randomPasswd
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE
from webvirtcloud.settings import QEMU_KEYMAPS, QEMU_CONSOLE_TYPES from webvirtcloud.settings import QEMU_KEYMAPS, QEMU_CONSOLE_TYPES
@ -279,13 +280,25 @@ def instance(request, compute_id, vname):
msg += " (%s > %s)" % (disk_size, ua.max_disk_size) msg += " (%s > %s)" % (disk_size, ua.max_disk_size)
return msg return msg
def get_new_disk_dev(disks, bus):
if bus == "virtio":
dev_base = "vd"
else:
dev_base = "sd"
existing_devs = [ disk['dev'] for disk in disks ]
for l in string.lowercase:
dev = dev_base + l
if dev not in existing_devs:
return dev
raise Exception(_('None available device name'))
try: try:
conn = wvmInstance(compute.hostname, conn = wvmInstance(compute.hostname,
compute.login, compute.login,
compute.password, compute.password,
compute.type, compute.type,
vname) vname)
status = conn.get_status() status = conn.get_status()
autostart = conn.get_autostart() autostart = conn.get_autostart()
vcpu = conn.get_vcpu() vcpu = conn.get_vcpu()
@ -318,6 +331,13 @@ def instance(request, compute_id, vname):
console_passwd = conn.get_console_passwd() console_passwd = conn.get_console_passwd()
clone_free_names = get_clone_free_names() clone_free_names = get_clone_free_names()
user_quota_msg = check_user_quota(0, 0, 0, 0) user_quota_msg = check_user_quota(0, 0, 0, 0)
storages = sorted(conn.get_storages())
cache_modes = sorted(conn.get_cache_modes().items())
default_cache = settings.INSTANCE_VOLUME_DEFAULT_CACHE
default_format = settings.INSTANCE_VOLUME_DEFAULT_FORMAT
formats = conn.get_image_formats()
default_bus = settings.INSTANCE_VOLUME_DEFAULT_BUS
busses = conn.get_busses()
try: try:
instance = Instance.objects.get(compute_id=compute_id, name=vname) instance = Instance.objects.get(compute_id=compute_id, name=vname)
@ -457,6 +477,27 @@ 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 'addvolume' in request.POST and (request.user.is_superuser or userinstace.is_change):
connCreate = wvmCreate(compute.hostname,
compute.login,
compute.password,
compute.type)
storage = request.POST.get('storage', '')
name = request.POST.get('name', '')
extension = request.POST.get('extension', '')
format = request.POST.get('format', '')
size = request.POST.get('size', 0)
meta_prealloc = request.POST.get('meta_prealloc', False)
bus = request.POST.get('bus', '')
cache = request.POST.get('cache', '')
target = get_new_disk_dev(disks, bus)
path = connCreate.create_volume(storage, name, size, format, meta_prealloc, extension)
conn.attach_disk(path, target, subdriver=format, cache=cache, targetbus=bus)
msg = _('Attach new disk')
addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#resize')
if 'umount_iso' in request.POST: if 'umount_iso' in request.POST:
image = request.POST.get('path', '') image = request.POST.get('path', '')
dev = request.POST.get('umount_iso', '') dev = request.POST.get('umount_iso', '')

View file

@ -380,6 +380,25 @@ class wvmConnect(object):
interface.append(inface) interface.append(inface)
return interface return interface
def get_cache_modes(self):
"""Get cache available modes"""
return {
'default': 'Default',
'none': 'Disabled',
'writethrough': 'Write through',
'writeback': 'Write back',
'directsync': 'Direct sync', # since libvirt 0.9.5
'unsafe': 'Unsafe', # since libvirt 0.9.7
}
def get_busses(self):
"""Get available busses"""
return [ 'ide', 'scsi', 'usb', 'virtio' ]
def get_image_formats(self):
"""Get available image formats"""
return [ 'raw', 'qcow', 'qcow2' ]
def get_iface(self, name): def get_iface(self, name):
return self.wvm.interfaceLookupByName(name) return self.wvm.interfaceLookupByName(name)

View file

@ -48,23 +48,12 @@ class wvmCreate(wvmConnect):
"""Get guest capabilities""" """Get guest capabilities"""
return util.get_xml_path(self.get_cap_xml(), "/capabilities/host/cpu/arch") return util.get_xml_path(self.get_cap_xml(), "/capabilities/host/cpu/arch")
def get_cache_modes(self): def create_volume(self, storage, name, size, format='qcow2', metadata=False, image_extension='img'):
"""Get cache available modes"""
return {
'default': 'Default',
'none': 'Disabled',
'writethrough': 'Write through',
'writeback': 'Write back',
'directsync': 'Direct sync', # since libvirt 0.9.5
'unsafe': 'Unsafe', # since libvirt 0.9.7
}
def create_volume(self, storage, name, size, format='qcow2', metadata=False):
size = int(size) * 1073741824 size = int(size) * 1073741824
stg = self.get_storage(storage) stg = self.get_storage(storage)
storage_type = util.get_xml_path(stg.XMLDesc(0), "/pool/@type") storage_type = util.get_xml_path(stg.XMLDesc(0), "/pool/@type")
if storage_type == 'dir': if storage_type == 'dir':
name += '.img' name += '.' + image_extension
alloc = 0 alloc = 0
else: else:
alloc = size alloc = size

View file

@ -340,6 +340,22 @@ class wvmInstance(wvmConnect):
xmldom = ElementTree.tostring(tree) xmldom = ElementTree.tostring(tree)
self._defineXML(xmldom) self._defineXML(xmldom)
def attach_disk(self, source, target, sourcetype='file', type='disk', driver='qemu', subdriver='raw', cache='none', targetbus='ide'):
tree = ElementTree.fromstring(self._XMLDesc(0))
xml_disk = """
<disk type='%s' device='%s'>
<driver name='%s' type='%s' cache='%s'/>
<source file='%s'/>
<target dev='%s' bus='%s'/>
</disk>
""" % (sourcetype, type, driver, subdriver, cache, source, target, targetbus)
if self.get_status() == 5:
devices = tree.find('devices')
elm_disk = ElementTree.fromstring(xml_disk)
devices.append(elm_disk)
xmldom = ElementTree.tostring(tree)
self._defineXML(xmldom)
def cpu_usage(self): def cpu_usage(self):
cpu_usage = {} cpu_usage = {}
if self.get_status() == 1: if self.get_status() == 1:

View file

@ -124,3 +124,6 @@ ALLOW_EMPTY_PASSWORD = True
SHOW_ACCESS_ROOT_PASSWORD = False SHOW_ACCESS_ROOT_PASSWORD = False
SHOW_ACCESS_SSH_KEYS = False SHOW_ACCESS_SSH_KEYS = False
SHOW_PROFILE_EDIT_PASSWORD = False SHOW_PROFILE_EDIT_PASSWORD = False
INSTANCE_VOLUME_DEFAULT_FORMAT = 'qcow2'
INSTANCE_VOLUME_DEFAULT_BUS = 'virtio'
INSTANCE_VOLUME_DEFAULT_CACHE = 'directsync'