mirror of
https://github.com/retspen/webvirtcloud
synced 2025-01-12 08:25:18 +00:00
build new tree
This commit is contained in:
parent
4d48e79341
commit
dd5f98cbe8
22 changed files with 745 additions and 60 deletions
|
@ -14,7 +14,7 @@ def computes(request):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not request.user.is_authenticated():
|
if not request.user.is_authenticated():
|
||||||
return HttpResponseRedirect(reverse('login'))
|
return HttpResponseRedirect(reverse('index'))
|
||||||
|
|
||||||
def get_hosts_status(computes):
|
def get_hosts_status(computes):
|
||||||
"""
|
"""
|
||||||
|
@ -23,13 +23,13 @@ def computes(request):
|
||||||
compute_data = []
|
compute_data = []
|
||||||
for compute in computes:
|
for compute in computes:
|
||||||
compute_data.append({'id': compute.id,
|
compute_data.append({'id': compute.id,
|
||||||
'name': compute.name,
|
'name': compute.name,
|
||||||
'hostname': compute.hostname,
|
'hostname': compute.hostname,
|
||||||
'status': connection_manager.host_is_up(compute.type, compute.hostname),
|
'status': connection_manager.host_is_up(compute.type, compute.hostname),
|
||||||
'type': compute.type,
|
'type': compute.type,
|
||||||
'login': compute.login,
|
'login': compute.login,
|
||||||
'password': compute.password
|
'password': compute.password
|
||||||
})
|
})
|
||||||
return compute_data
|
return compute_data
|
||||||
|
|
||||||
computes = Compute.objects.filter()
|
computes = Compute.objects.filter()
|
||||||
|
@ -44,7 +44,7 @@ def compute(request, compute_id):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not request.user.is_authenticated():
|
if not request.user.is_authenticated():
|
||||||
return HttpResponseRedirect(reverse('login'))
|
return HttpResponseRedirect(reverse('index'))
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ def instances(request):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not request.user.is_authenticated():
|
if not request.user.is_authenticated():
|
||||||
return HttpResponseRedirect(reverse('login'))
|
return HttpResponseRedirect(reverse('index'))
|
||||||
|
|
||||||
computes = Compute.objects.filter()
|
computes = Compute.objects.filter()
|
||||||
all_host_vms = {}
|
all_host_vms = {}
|
||||||
|
@ -50,6 +50,6 @@ def instance(request, comptes_id, vname):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not request.user.is_authenticated():
|
if not request.user.is_authenticated():
|
||||||
return HttpResponseRedirect(reverse('login'))
|
return HttpResponseRedirect(reverse('index'))
|
||||||
|
|
||||||
return render(request, 'instances.html', locals())
|
return render(request, 'instances.html', locals())
|
73
interfaces/forms.py
Normal file
73
interfaces/forms.py
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import re
|
||||||
|
from django import forms
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class AddInterface(forms.Form):
|
||||||
|
name = forms.CharField(max_length=10, required=True)
|
||||||
|
itype = forms.ChoiceField(required=True, choices=(('bridge', 'bridge'), ('ethernet', 'ethernet')))
|
||||||
|
start_mode = forms.ChoiceField(required=True,
|
||||||
|
choices=(('none', 'none'), ('onboot', 'onboot'), ('hotplug', 'hotplug')))
|
||||||
|
netdev = forms.CharField(max_length=15, required=True)
|
||||||
|
ipv4_type = forms.ChoiceField(required=True, choices=(('dhcp', 'dhcp'), ('static', 'static'), ('none', 'none')))
|
||||||
|
ipv4_addr = forms.CharField(max_length=18, required=False)
|
||||||
|
ipv4_gw = forms.CharField(max_length=15, required=False)
|
||||||
|
ipv6_type = forms.ChoiceField(required=True, choices=(('dhcp', 'dhcp'), ('static', 'static'), ('none', 'none')))
|
||||||
|
ipv6_addr = forms.CharField(max_length=100, required=False)
|
||||||
|
ipv6_gw = forms.CharField(max_length=100, required=False)
|
||||||
|
stp = forms.ChoiceField(required=False, choices=(('on', 'on'), ('off', 'off')))
|
||||||
|
delay = forms.IntegerField(required=False)
|
||||||
|
|
||||||
|
def clean_ipv4_addr(self):
|
||||||
|
ipv4_addr = self.cleaned_data['ipv4_addr']
|
||||||
|
have_symbol = re.match('^[0-9./]+$', ipv4_addr)
|
||||||
|
if not have_symbol:
|
||||||
|
raise forms.ValidationError(_('The ipv4 must not contain any special characters'))
|
||||||
|
elif len(ipv4_addr) > 20:
|
||||||
|
raise forms.ValidationError(_('The ipv4 must not exceed 20 characters'))
|
||||||
|
return ipv4_addr
|
||||||
|
|
||||||
|
def clean_ipv4_gw(self):
|
||||||
|
ipv4_gw = self.cleaned_data['ipv4_gw']
|
||||||
|
have_symbol = re.match('^[0-9.]+$', ipv4_gw)
|
||||||
|
if not have_symbol:
|
||||||
|
raise forms.ValidationError(_('The ipv4 gateway must not contain any special characters'))
|
||||||
|
elif len(ipv4_gw) > 20:
|
||||||
|
raise forms.ValidationError(_('The ipv4 gateway must not exceed 20 characters'))
|
||||||
|
return ipv4_gw
|
||||||
|
|
||||||
|
def clean_ipv6_addr(self):
|
||||||
|
ipv6_addr = self.cleaned_data['ipv6_addr']
|
||||||
|
have_symbol = re.match('^[0-9a-f./:]+$', ipv6_addr)
|
||||||
|
if not have_symbol:
|
||||||
|
raise forms.ValidationError(_('The ipv6 must not contain any special characters'))
|
||||||
|
elif len(ipv6_addr) > 100:
|
||||||
|
raise forms.ValidationError(_('The ipv6 must not exceed 100 characters'))
|
||||||
|
return ipv6_addr
|
||||||
|
|
||||||
|
def clean_ipv6_gw(self):
|
||||||
|
ipv6_gw = self.cleaned_data['ipv6_gw']
|
||||||
|
have_symbol = re.match('^[0-9.]+$', ipv6_gw)
|
||||||
|
if not have_symbol:
|
||||||
|
raise forms.ValidationError(_('The ipv6 gateway must not contain any special characters'))
|
||||||
|
elif len(ipv6_gw) > 100:
|
||||||
|
raise forms.ValidationError(_('The ipv6 gateway must not exceed 100 characters'))
|
||||||
|
return ipv6_gw
|
||||||
|
|
||||||
|
def clean_name(self):
|
||||||
|
name = self.cleaned_data['name']
|
||||||
|
have_symbol = re.match('^[a-z0-9.]+$', name)
|
||||||
|
if not have_symbol:
|
||||||
|
raise forms.ValidationError(_('The interface must not contain any special characters'))
|
||||||
|
elif len(name) > 10:
|
||||||
|
raise forms.ValidationError(_('The interface must not exceed 10 characters'))
|
||||||
|
return name
|
||||||
|
|
||||||
|
def clean_netdev(self):
|
||||||
|
netdev = self.cleaned_data['netdev']
|
||||||
|
have_symbol = re.match('^[a-z0-9.]+$', netdev)
|
||||||
|
if not have_symbol:
|
||||||
|
raise forms.ValidationError(_('The interface must not contain any special characters'))
|
||||||
|
elif len(netdev) > 10:
|
||||||
|
raise forms.ValidationError(_('The interface must not exceed 10 characters'))
|
||||||
|
return netdev
|
|
@ -1,3 +1,97 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from computes.models import Compute
|
||||||
|
from interfaces.forms import AddInterface
|
||||||
|
from vrtManager.interface import wvmInterface, wvmInterfaces
|
||||||
|
from libvirt import libvirtError
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
|
def interfaces(request, compute_id):
|
||||||
|
"""
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not request.user.is_authenticated():
|
||||||
|
return HttpResponseRedirect(reverse('index'))
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
ifaces_all = []
|
||||||
|
compute = Compute.objects.get(id=compute_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = wvmInterfaces(compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type)
|
||||||
|
ifaces = conn.get_ifaces()
|
||||||
|
try:
|
||||||
|
netdevs = conn.get_net_device()
|
||||||
|
except:
|
||||||
|
netdevs = ['eth0', 'eth1']
|
||||||
|
|
||||||
|
for iface in ifaces:
|
||||||
|
ifaces_all.append(conn.get_iface_info(iface))
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
if 'create' in request.POST:
|
||||||
|
form = AddInterface(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
data = form.cleaned_data
|
||||||
|
conn.create_iface(data['name'], data['itype'], data['start_mode'], data['netdev'],
|
||||||
|
data['ipv4_type'], data['ipv4_addr'], data['ipv4_gw'],
|
||||||
|
data['ipv6_type'], data['ipv6_addr'], data['ipv6_gw'],
|
||||||
|
data['stp'], data['delay'])
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
conn.close()
|
||||||
|
except libvirtError as err:
|
||||||
|
errors.append(err)
|
||||||
|
|
||||||
|
return render(request, 'interfaces.html', locals())
|
||||||
|
|
||||||
|
|
||||||
|
def interface(request, compute_id, iface):
|
||||||
|
"""
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not request.user.is_authenticated():
|
||||||
|
return HttpResponseRedirect(reverse('index'))
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
ifaces_all = []
|
||||||
|
compute = Compute.objects.get(id=compute_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = wvmInterface(compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type,
|
||||||
|
iface)
|
||||||
|
start_mode = conn.get_start_mode()
|
||||||
|
state = conn.is_active()
|
||||||
|
mac = conn.get_mac()
|
||||||
|
itype = conn.get_type()
|
||||||
|
ipv4 = conn.get_ipv4()
|
||||||
|
ipv4_type = conn.get_ipv4_type()
|
||||||
|
ipv6 = conn.get_ipv6()
|
||||||
|
ipv6_type = conn.get_ipv6_type()
|
||||||
|
bridge = conn.get_bridge()
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
if 'stop' in request.POST:
|
||||||
|
conn.stop_iface()
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
if 'start' in request.POST:
|
||||||
|
conn.start_iface()
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
if 'delete' in request.POST:
|
||||||
|
conn.delete_iface()
|
||||||
|
return HttpResponseRedirect(reverse('interfaces', args=[host_id]))
|
||||||
|
conn.close()
|
||||||
|
except libvirtError as err:
|
||||||
|
errors.append(err)
|
||||||
|
|
||||||
|
return render(request, 'interface.html', locals())
|
||||||
|
|
43
networks/forms.py
Normal file
43
networks/forms.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import re
|
||||||
|
from django import forms
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class AddNetPool(forms.Form):
|
||||||
|
name = forms.CharField(error_messages={'required': _('No pool name has been entered')},
|
||||||
|
max_length=20)
|
||||||
|
subnet = forms.CharField(error_messages={'required': _('No subnet has been entered')},
|
||||||
|
max_length=20)
|
||||||
|
forward = forms.CharField(max_length=100)
|
||||||
|
dhcp = forms.BooleanField(required=False)
|
||||||
|
fixed = forms.BooleanField(required=False)
|
||||||
|
bridge_name = forms.CharField(max_length=20, required=False)
|
||||||
|
openvswitch = forms.BooleanField(required=False)
|
||||||
|
|
||||||
|
def clean_name(self):
|
||||||
|
name = self.cleaned_data['name']
|
||||||
|
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
|
||||||
|
if not have_symbol:
|
||||||
|
raise forms.ValidationError(_('The pool name must not contain any special characters'))
|
||||||
|
elif len(name) > 20:
|
||||||
|
raise forms.ValidationError(_('The pool name must not exceed 20 characters'))
|
||||||
|
return name
|
||||||
|
|
||||||
|
def clean_subnet(self):
|
||||||
|
subnet = self.cleaned_data['subnet']
|
||||||
|
have_symbol = re.match('^[0-9./]+$', subnet)
|
||||||
|
if not have_symbol:
|
||||||
|
raise forms.ValidationError(_('The pool subnet must not contain any special characters'))
|
||||||
|
elif len(subnet) > 20:
|
||||||
|
raise forms.ValidationError(_('The pool subnet must not exceed 20 characters'))
|
||||||
|
return subnet
|
||||||
|
|
||||||
|
def clean_bridge_name(self):
|
||||||
|
bridge_name = self.cleaned_data['bridge_name']
|
||||||
|
if self.cleaned_data['forward'] == 'bridge':
|
||||||
|
have_symbol = re.match('^[a-zA-Z0-9._-]+$', bridge_name)
|
||||||
|
if not have_symbol:
|
||||||
|
raise forms.ValidationError(_('The pool bridge name must not contain any special characters'))
|
||||||
|
elif len(bridge_name) > 20:
|
||||||
|
raise forms.ValidationError(_('The pool bridge name must not exceed 20 characters'))
|
||||||
|
return bridge_name
|
|
@ -1,3 +1,121 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from computes.models import Compute
|
||||||
|
from networks.forms import AddNetPool
|
||||||
|
from vrtManager.network import wvmNetwork, wvmNetworks
|
||||||
|
from vrtManager.network import network_size
|
||||||
|
from libvirt import libvirtError
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
|
def networks(request, compute_id):
|
||||||
|
"""
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not request.user.is_authenticated():
|
||||||
|
return HttpResponseRedirect(reverse('index'))
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
compute = Compute.objects.get(id=compute_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = wvmNetworks(compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type)
|
||||||
|
networks = conn.get_networks_info()
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
if 'create' in request.POST:
|
||||||
|
form = AddNetPool(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
data = form.cleaned_data
|
||||||
|
if data['name'] in networks:
|
||||||
|
msg = _("Pool name already in use")
|
||||||
|
errors.append(msg)
|
||||||
|
if data['forward'] == 'bridge' and data['bridge_name'] == '':
|
||||||
|
errors.append('Please enter bridge name')
|
||||||
|
try:
|
||||||
|
gateway, netmask, dhcp = network_size(data['subnet'], data['dhcp'])
|
||||||
|
except:
|
||||||
|
msg = _("Input subnet pool error")
|
||||||
|
errors.append(msg)
|
||||||
|
if not errors:
|
||||||
|
conn.create_network(data['name'], data['forward'], gateway, netmask,
|
||||||
|
dhcp, data['bridge_name'], data['openvswitch'], data['fixed'])
|
||||||
|
return HttpResponseRedirect(reverse('network', args=[host_id, data['name']]))
|
||||||
|
conn.close()
|
||||||
|
except libvirtError as err:
|
||||||
|
errors.append(err)
|
||||||
|
|
||||||
|
return render(request, 'network.html', locals())
|
||||||
|
|
||||||
|
|
||||||
|
def network(request, compute_id, pool):
|
||||||
|
"""
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not request.user.is_authenticated():
|
||||||
|
return HttpResponseRedirect(reverse('index'))
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
compute = Compute.objects.get(id=compute_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = wvmNetwork(compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type,
|
||||||
|
pool)
|
||||||
|
networks = conn.get_networks()
|
||||||
|
state = conn.is_active()
|
||||||
|
device = conn.get_bridge_device()
|
||||||
|
autostart = conn.get_autostart()
|
||||||
|
ipv4_forward = conn.get_ipv4_forward()
|
||||||
|
ipv4_dhcp_range_start = conn.get_ipv4_dhcp_range_start()
|
||||||
|
ipv4_dhcp_range_end = conn.get_ipv4_dhcp_range_end()
|
||||||
|
ipv4_network = conn.get_ipv4_network()
|
||||||
|
fixed_address = conn.get_mac_ipaddr()
|
||||||
|
except libvirtError as err:
|
||||||
|
errors.append(err)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
if 'start' in request.POST:
|
||||||
|
try:
|
||||||
|
conn.start()
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
except libvirtError as error_msg:
|
||||||
|
errors.append(error_msg.message)
|
||||||
|
if 'stop' in request.POST:
|
||||||
|
try:
|
||||||
|
conn.stop()
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
except libvirtError as error_msg:
|
||||||
|
errors.append(error_msg.message)
|
||||||
|
if 'delete' in request.POST:
|
||||||
|
try:
|
||||||
|
conn.delete()
|
||||||
|
return HttpResponseRedirect(reverse('networks', args=[host_id]))
|
||||||
|
except libvirtError as error_msg:
|
||||||
|
errors.append(error_msg.message)
|
||||||
|
if 'set_autostart' in request.POST:
|
||||||
|
try:
|
||||||
|
conn.set_autostart(1)
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
except libvirtError as error_msg:
|
||||||
|
errors.append(error_msg.message)
|
||||||
|
if 'unset_autostart' in request.POST:
|
||||||
|
try:
|
||||||
|
conn.set_autostart(0)
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
except libvirtError as error_msg:
|
||||||
|
errors.append(error_msg.message)
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return render(request, 'network.html', locals())
|
||||||
|
|
8
secrets/forms.py
Normal file
8
secrets/forms.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
|
||||||
|
class AddSecret(forms.Form):
|
||||||
|
ephemeral = forms.ChoiceField(required=True, choices=(('no', 'no'), ('yes', 'yes')))
|
||||||
|
private = forms.ChoiceField(required=True, choices=(('no', 'no'), ('yes', 'yes')))
|
||||||
|
usage_type = forms.ChoiceField(required=True, choices=(('ceph', 'ceph'), ('volume', 'volume'), ('iscsi', 'iscsi')))
|
||||||
|
data = forms.CharField(max_length=100, required=True)
|
|
@ -1,3 +1,59 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from computes.models import Compute
|
||||||
|
from secrets.forms import AddSecret
|
||||||
|
from vrtManager.secrets import wvmSecrets
|
||||||
|
from libvirt import libvirtError
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
|
def secrets(request, compute_id):
|
||||||
|
"""
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not request.user.is_authenticated():
|
||||||
|
return HttpResponseRedirect(reverse('index'))
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
secrets_all = []
|
||||||
|
compute = Compute.objects.get(id=compute_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = wvmSecrets(compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type)
|
||||||
|
secrets = conn.get_secrets()
|
||||||
|
for uuid in secrets:
|
||||||
|
secrt = conn.get_secret(uuid)
|
||||||
|
try:
|
||||||
|
secret_value = conn.get_secret_value(uuid)
|
||||||
|
except:
|
||||||
|
secret_value = ''
|
||||||
|
secrets_all.append({'usage': secrt.usageID(),
|
||||||
|
'uuid': secrt.UUIDString(),
|
||||||
|
'usageType': secrt.usageType(),
|
||||||
|
'value': secret_value
|
||||||
|
})
|
||||||
|
if request.method == 'POST':
|
||||||
|
if 'create' in request.POST:
|
||||||
|
form = AddSecret(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
data = form.cleaned_data
|
||||||
|
conn.create_secret(data['ephemeral'], data['private'], data['usage_type'], data['data'])
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
if 'delete' in request.POST:
|
||||||
|
uuid = request.POST.get('uuid', '')
|
||||||
|
conn.delete_secret(uuid)
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
if 'set_value' in request.POST:
|
||||||
|
uuid = request.POST.get('uuid', '')
|
||||||
|
value = request.POST.get('value', '')
|
||||||
|
conn.set_secret_value(uuid, value)
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
except libvirtError as err:
|
||||||
|
errors.append(err)
|
||||||
|
|
||||||
|
return render(request, 'secrets.html', locals())
|
||||||
|
|
87
storages/forms.py
Normal file
87
storages/forms.py
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import re
|
||||||
|
from django import forms
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class AddStgPool(forms.Form):
|
||||||
|
name = forms.CharField(error_messages={'required': _('No pool name has been entered')},
|
||||||
|
max_length=20)
|
||||||
|
stg_type = forms.CharField(max_length=10)
|
||||||
|
target = forms.CharField(error_messages={'required': _('No path has been entered')},
|
||||||
|
max_length=100,
|
||||||
|
required=False)
|
||||||
|
source = forms.CharField(max_length=100, required=False)
|
||||||
|
ceph_user = forms.CharField(required=False)
|
||||||
|
ceph_host = forms.CharField(required=False)
|
||||||
|
ceph_pool = forms.CharField(required=False)
|
||||||
|
secret = forms.CharField(required=False)
|
||||||
|
netfs_host = forms.CharField(required=False)
|
||||||
|
source_format = forms.CharField(required=False)
|
||||||
|
|
||||||
|
def clean_name(self):
|
||||||
|
name = self.cleaned_data['name']
|
||||||
|
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
|
||||||
|
if not have_symbol:
|
||||||
|
raise forms.ValidationError(_('The pool name must not contain any special characters'))
|
||||||
|
elif len(name) > 20:
|
||||||
|
raise forms.ValidationError(_('The pool name must not exceed 20 characters'))
|
||||||
|
return name
|
||||||
|
|
||||||
|
def clean_target(self):
|
||||||
|
storage_type = self.cleaned_data['stg_type']
|
||||||
|
target = self.cleaned_data['target']
|
||||||
|
have_symbol = re.match('^[a-zA-Z0-9/]+$', target)
|
||||||
|
if not have_symbol:
|
||||||
|
raise forms.ValidationError(_('The target must not contain any special characters'))
|
||||||
|
if storage_type == 'dir':
|
||||||
|
if not target:
|
||||||
|
raise forms.ValidationError(_('No path has been entered'))
|
||||||
|
return target
|
||||||
|
|
||||||
|
def clean_source(self):
|
||||||
|
storage_type = self.cleaned_data['stg_type']
|
||||||
|
source = self.cleaned_data['source']
|
||||||
|
have_symbol = re.match('^[a-zA-Z0-9\/]+$', source)
|
||||||
|
if storage_type == 'logical' or storage_type == 'netfs':
|
||||||
|
if not source:
|
||||||
|
raise forms.ValidationError(_('No device has been entered'))
|
||||||
|
if not have_symbol:
|
||||||
|
raise forms.ValidationError(_('The source must not contain any special characters'))
|
||||||
|
return source
|
||||||
|
|
||||||
|
|
||||||
|
class AddImage(forms.Form):
|
||||||
|
name = forms.CharField(max_length=20)
|
||||||
|
format = forms.ChoiceField(required=True, choices=(('qcow2', 'qcow2 (recommended)'),
|
||||||
|
('qcow', 'qcow'),
|
||||||
|
('raw', 'raw')))
|
||||||
|
size = forms.IntegerField()
|
||||||
|
meta_prealloc = forms.BooleanField(required=False)
|
||||||
|
|
||||||
|
def clean_name(self):
|
||||||
|
name = self.cleaned_data['name']
|
||||||
|
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
|
||||||
|
if not have_symbol:
|
||||||
|
raise forms.ValidationError(_('The image name must not contain any special characters'))
|
||||||
|
elif len(name) > 20:
|
||||||
|
raise forms.ValidationError(_('The image name must not exceed 20 characters'))
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
class CloneImage(forms.Form):
|
||||||
|
name = forms.CharField(max_length=20)
|
||||||
|
image = forms.CharField(max_length=20)
|
||||||
|
convert = forms.BooleanField(required=False)
|
||||||
|
format = forms.ChoiceField(required=False, choices=(('qcow2', 'qcow2 (recommended)'),
|
||||||
|
('qcow', 'qcow'),
|
||||||
|
('raw', 'raw')))
|
||||||
|
meta_prealloc = forms.BooleanField(required=False)
|
||||||
|
|
||||||
|
def clean_name(self):
|
||||||
|
name = self.cleaned_data['name']
|
||||||
|
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
|
||||||
|
if not have_symbol:
|
||||||
|
raise forms.ValidationError(_('The image name must not contain any special characters'))
|
||||||
|
elif len(name) > 20:
|
||||||
|
raise forms.ValidationError(_('The image name must not exceed 20 characters'))
|
||||||
|
return name
|
193
storages/views.py
Normal file
193
storages/views.py
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from computes.models import Compute
|
||||||
|
from storages.forms import AddStgPool, AddImage, CloneImage
|
||||||
|
from vrtManager.storage import wvmStorage, wvmStorages
|
||||||
|
from libvirt import libvirtError
|
||||||
|
|
||||||
|
|
||||||
|
def storages(request, compute_id):
|
||||||
|
"""
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not request.user.is_authenticated():
|
||||||
|
return HttpResponseRedirect(reverse('index'))
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
compute = Compute.objects.get(id=compute_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = wvmStorages(compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type)
|
||||||
|
storages = conn.get_storages_info()
|
||||||
|
secrets = conn.get_secrets()
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
if 'create' in request.POST:
|
||||||
|
form = AddStgPool(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
data = form.cleaned_data
|
||||||
|
if data['name'] in storages:
|
||||||
|
msg = _("Pool name already use")
|
||||||
|
errors.append(msg)
|
||||||
|
if data['stg_type'] == 'rbd':
|
||||||
|
if not data['secret']:
|
||||||
|
msg = _("You need create secret for pool")
|
||||||
|
errors.append(msg)
|
||||||
|
if not data['ceph_pool'] and not data['ceph_host'] and not data['ceph_user']:
|
||||||
|
msg = _("You need input all fields for creating ceph pool")
|
||||||
|
errors.append(msg)
|
||||||
|
if not errors:
|
||||||
|
if data['stg_type'] == 'rbd':
|
||||||
|
conn.create_storage_ceph(data['stg_type'], data['name'],
|
||||||
|
data['ceph_pool'], data['ceph_host'],
|
||||||
|
data['ceph_user'], data['secret'])
|
||||||
|
elif data['stg_type'] == 'netfs':
|
||||||
|
conn.create_storage_netfs(data['stg_type'], data['name'],
|
||||||
|
data['netfs_host'], data['source'],
|
||||||
|
data['source_format'], data['target'])
|
||||||
|
else:
|
||||||
|
conn.create_storage(data['stg_type'], data['name'], data['source'], data['target'])
|
||||||
|
return HttpResponseRedirect(reverse('storage', args=[host_id, data['name']]))
|
||||||
|
conn.close()
|
||||||
|
except libvirtError as err:
|
||||||
|
errors.append(err)
|
||||||
|
|
||||||
|
return render(request, 'storages.html', locals())
|
||||||
|
|
||||||
|
|
||||||
|
def storage(request, compute_id, pool):
|
||||||
|
"""
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not request.user.is_authenticated():
|
||||||
|
return HttpResponseRedirect(reverse('index'))
|
||||||
|
|
||||||
|
def handle_uploaded_file(path, f_name):
|
||||||
|
target = path + '/' + str(f_name)
|
||||||
|
destination = open(target, 'wb+')
|
||||||
|
for chunk in f_name.chunks():
|
||||||
|
destination.write(chunk)
|
||||||
|
destination.close()
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
compute = Compute.objects.get(id=compute_id)
|
||||||
|
meta_prealloc = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = wvmStorage(compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type,
|
||||||
|
pool)
|
||||||
|
|
||||||
|
storages = conn.get_storages()
|
||||||
|
state = conn.is_active()
|
||||||
|
size, free = conn.get_size()
|
||||||
|
used = (size - free)
|
||||||
|
if state:
|
||||||
|
percent = (used * 100) / size
|
||||||
|
else:
|
||||||
|
percent = 0
|
||||||
|
status = conn.get_status()
|
||||||
|
path = conn.get_target_path()
|
||||||
|
type = conn.get_type()
|
||||||
|
autostart = conn.get_autostart()
|
||||||
|
|
||||||
|
if state:
|
||||||
|
conn.refresh()
|
||||||
|
volumes = conn.update_volumes()
|
||||||
|
else:
|
||||||
|
volumes = None
|
||||||
|
except libvirtError as err:
|
||||||
|
errors.append(err)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
if 'start' in request.POST:
|
||||||
|
try:
|
||||||
|
conn.start()
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
except libvirtError as error_msg:
|
||||||
|
errors.append(error_msg.message)
|
||||||
|
if 'stop' in request.POST:
|
||||||
|
try:
|
||||||
|
conn.stop()
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
except libvirtError as error_msg:
|
||||||
|
errors.append(error_msg.message)
|
||||||
|
if 'delete' in request.POST:
|
||||||
|
try:
|
||||||
|
conn.delete()
|
||||||
|
return HttpResponseRedirect(reverse('storages', args=[host_id]))
|
||||||
|
except libvirtError as error_msg:
|
||||||
|
errors.append(error_msg.message)
|
||||||
|
if 'set_autostart' in request.POST:
|
||||||
|
try:
|
||||||
|
conn.set_autostart(1)
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
except libvirtError as error_msg:
|
||||||
|
errors.append(error_msg.message)
|
||||||
|
if 'unset_autostart' in request.POST:
|
||||||
|
try:
|
||||||
|
conn.set_autostart(0)
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
except libvirtError as error_msg:
|
||||||
|
errors.append(error_msg.message)
|
||||||
|
if 'add_volume' in request.POST:
|
||||||
|
form = AddImage(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
data = form.cleaned_data
|
||||||
|
if data['meta_prealloc'] and data['format'] == 'qcow2':
|
||||||
|
meta_prealloc = True
|
||||||
|
try:
|
||||||
|
conn.create_volume(data['name'], data['size'], data['format'], meta_prealloc)
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
except libvirtError as err:
|
||||||
|
errors.append(err)
|
||||||
|
if 'del_volume' in request.POST:
|
||||||
|
volname = request.POST.get('volname', '')
|
||||||
|
try:
|
||||||
|
vol = conn.get_volume(volname)
|
||||||
|
vol.delete(0)
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
except libvirtError as error_msg:
|
||||||
|
errors.append(error_msg.message)
|
||||||
|
if 'iso_upload' in request.POST:
|
||||||
|
if str(request.FILES['file']) in conn.update_volumes():
|
||||||
|
msg = _("ISO image already exist")
|
||||||
|
errors.append(msg)
|
||||||
|
else:
|
||||||
|
handle_uploaded_file(path, request.FILES['file'])
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
if 'cln_volume' in request.POST:
|
||||||
|
form = CloneImage(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
data = form.cleaned_data
|
||||||
|
img_name = data['name'] + '.img'
|
||||||
|
meta_prealloc = 0
|
||||||
|
if img_name in conn.update_volumes():
|
||||||
|
msg = _("Name of volume name already use")
|
||||||
|
errors.append(msg)
|
||||||
|
if not errors:
|
||||||
|
if data['convert']:
|
||||||
|
format = data['format']
|
||||||
|
if data['meta_prealloc'] and data['format'] == 'qcow2':
|
||||||
|
meta_prealloc = True
|
||||||
|
else:
|
||||||
|
format = None
|
||||||
|
try:
|
||||||
|
conn.clone_volume(data['image'], data['name'], format, meta_prealloc)
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
except libvirtError as err:
|
||||||
|
errors.append(err)
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return render(request, 'storage.html', locals())
|
|
@ -1,3 +0,0 @@
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
|
@ -9,15 +9,47 @@
|
||||||
|
|
||||||
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
|
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
|
||||||
<button type="button" class="btn btn-success pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></button>
|
<button type="button" class="btn btn-success pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></button>
|
||||||
<h1 class="page-header">Computes</h1>
|
<h1 class="page-header">{{ compute.name }}</h1>
|
||||||
|
|
||||||
<div class="alert alert-danger alert-dismissible" role="danger">
|
{% include 'errors.html' %}
|
||||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
||||||
<strong>Error:</strong> Better check yourself, you're not looking too good.
|
<h4>{% trans "Basic details" %}</h4>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans "Connection" %}</td>
|
||||||
|
<td>{{ uri_conn }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans "Hostname" %}</td>
|
||||||
|
<td>{{ hostname }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans "Hypervisor" %}</td>
|
||||||
|
<td>{{ hypervisor }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans "Memory" %}</td>
|
||||||
|
<td>{{ host_memory|filesizeformat }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans "Logical CPUs" %}</td>
|
||||||
|
<td>{{ logical_cpu }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans "Processor" %}</td>
|
||||||
|
<td>{{ model_cpu }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans "Architecture" %}</td>
|
||||||
|
<td>{{ host_arch }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,10 +11,7 @@
|
||||||
<button type="button" class="btn btn-success pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></button>
|
<button type="button" class="btn btn-success pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></button>
|
||||||
<h1 class="page-header">Computes</h1>
|
<h1 class="page-header">Computes</h1>
|
||||||
|
|
||||||
<div class="alert alert-danger alert-dismissible" role="danger">
|
{% include 'errors.html' %}
|
||||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
||||||
<strong>Error:</strong> Better check yourself, you're not looking too good.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% if computes_info %}
|
{% if computes_info %}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
{% load static %}
|
|
||||||
{% include 'header.html' %}
|
|
||||||
<body>
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
{% include 'sidebar.html' %}
|
|
||||||
|
|
||||||
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
|
|
||||||
<h1 class="page-header">Dashboard</h1>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include 'footer.html' %}
|
|
8
templates/errors.html
Normal file
8
templates/errors.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{% if errors %}
|
||||||
|
{% for error in errors %}
|
||||||
|
<div class="alert alert-danger alert-dismissible" role="danger">
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<strong>Error:</strong> Better check yourself, you're not looking too good.
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
|
@ -11,10 +11,7 @@
|
||||||
<button type="button" class="btn btn-success pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></button>
|
<button type="button" class="btn btn-success pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></button>
|
||||||
<h1 class="page-header">Instances</h1>
|
<h1 class="page-header">Instances</h1>
|
||||||
|
|
||||||
<div class="alert alert-danger alert-dismissible" role="danger">
|
{% include 'errors.html' %}
|
||||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
||||||
<strong>Error:</strong> Better check yourself, you're not looking too good.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
|
|
|
@ -15,18 +15,17 @@ urlpatterns = patterns('',
|
||||||
url(r'^computes/$', 'computes.views.computes', name='computes'),
|
url(r'^computes/$', 'computes.views.computes', name='computes'),
|
||||||
url(r'^compute/(\d+)/$', 'computes.views.compute', name='compute'),
|
url(r'^compute/(\d+)/$', 'computes.views.compute', name='compute'),
|
||||||
|
|
||||||
# url(r'^storages/$', 'storages.views.storages', name='storages'),
|
url(r'^storages/(\d+)/$', 'storages.views.storages', name='storages'),
|
||||||
# url(r'^storage/(\d+)/([\w\-\.]+)/$', 'storages.views.storage', name='storage'),
|
url(r'^storage/(\d+)/([\w\-\.]+)/$', 'storages.views.storage', name='storage'),
|
||||||
#
|
|
||||||
# url(r'^networks/$', 'networks.views.networks', name='networks'),
|
url(r'^networks/(\d+)/$', 'networks.views.networks', name='networks'),
|
||||||
# url(r'^network/(\d+)/([\w\-\.]+)/$', 'networks.views.network', name='network'),
|
url(r'^network/(\d+)/([\w\-\.]+)/$', 'networks.views.network', name='network'),
|
||||||
#
|
|
||||||
# url(r'^interfaces/$', 'interfaces.views.interfaces', name='interfaces'),
|
url(r'^interfaces/(\d+)/$', 'interfaces.views.interfaces', name='interfaces'),
|
||||||
# url(r'^interface/(\d+)/([\w\.]+)$', 'interfaces.views.interface', name='interface'),
|
url(r'^interface/(\d+)/([\w\.]+)$', 'interfaces.views.interface', name='interface'),
|
||||||
#
|
|
||||||
# url(r'^secrets/$', 'secrets.views.secrets', name='secrets'),
|
url(r'^secret/(\d+)/$', 'secrets.views.secrets', name='secrets'),
|
||||||
# url(r'^secret/(\d+)/([\w\.]+)$', 'secrets.views.secret', name='secret'),
|
|
||||||
#
|
|
||||||
# url(r'^accounts/$', 'accounts.views.accounts', name='accounts'),
|
# url(r'^accounts/$', 'accounts.views.accounts', name='accounts'),
|
||||||
# url(r'^account/(\d+)/$', 'accounts.views.account', name='account'),
|
# url(r'^account/(\d+)/$', 'accounts.views.account', name='account'),
|
||||||
#
|
#
|
||||||
|
|
Loading…
Reference in a new issue