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:
parent
b095a77da5
commit
53f5518706
6 changed files with 170 additions and 15 deletions
|
@ -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>
|
||||||
|
|
|
@ -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', '')
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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'
|
||||||
|
|
Loading…
Reference in a new issue