mirror of
https://github.com/retspen/webvirtcloud
synced 2024-10-31 19:44:16 +00:00
format python code with black
This commit is contained in:
parent
ea409ca863
commit
217e106c8b
55 changed files with 2510 additions and 1454 deletions
|
@ -3,48 +3,59 @@ from django.db.models.signals import post_migrate
|
||||||
|
|
||||||
|
|
||||||
def apply_change_password(sender, **kwargs):
|
def apply_change_password(sender, **kwargs):
|
||||||
'''
|
"""
|
||||||
Apply new change_password permission for all users
|
Apply new change_password permission for all users
|
||||||
Depending on settings SHOW_PROFILE_EDIT_PASSWORD
|
Depending on settings SHOW_PROFILE_EDIT_PASSWORD
|
||||||
'''
|
"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import Permission, User
|
from django.contrib.auth.models import Permission, User
|
||||||
if hasattr(settings, 'SHOW_PROFILE_EDIT_PASSWORD'):
|
|
||||||
print('\033[1m! \033[92mSHOW_PROFILE_EDIT_PASSWORD is found inside settings.py\033[0m')
|
if hasattr(settings, "SHOW_PROFILE_EDIT_PASSWORD"):
|
||||||
print('\033[1m* \033[92mApplying permission can_change_password for all users\033[0m')
|
print("\033[1m! \033[92mSHOW_PROFILE_EDIT_PASSWORD is found inside settings.py\033[0m")
|
||||||
|
print("\033[1m* \033[92mApplying permission can_change_password for all users\033[0m")
|
||||||
users = User.objects.all()
|
users = User.objects.all()
|
||||||
permission = Permission.objects.get(codename='change_password')
|
permission = Permission.objects.get(codename="change_password")
|
||||||
if settings.SHOW_PROFILE_EDIT_PASSWORD:
|
if settings.SHOW_PROFILE_EDIT_PASSWORD:
|
||||||
print('\033[1m! \033[91mWarning!!! Setting to True for all users\033[0m')
|
print("\033[1m! \033[91mWarning!!! Setting to True for all users\033[0m")
|
||||||
for user in users:
|
for user in users:
|
||||||
user.user_permissions.add(permission)
|
user.user_permissions.add(permission)
|
||||||
else:
|
else:
|
||||||
print('\033[1m* \033[91mWarning!!! Setting to False for all users\033[0m')
|
print("\033[1m* \033[91mWarning!!! Setting to False for all users\033[0m")
|
||||||
for user in users:
|
for user in users:
|
||||||
user.user_permissions.remove(permission)
|
user.user_permissions.remove(permission)
|
||||||
print('\033[1m! Don`t forget to remove the option from settings.py\033[0m')
|
print("\033[1m! Don`t forget to remove the option from settings.py\033[0m")
|
||||||
|
|
||||||
|
|
||||||
def create_admin(sender, **kwargs):
|
def create_admin(sender, **kwargs):
|
||||||
'''
|
"""
|
||||||
Create initial admin user
|
Create initial admin user
|
||||||
'''
|
"""
|
||||||
from accounts.models import UserAttributes
|
from accounts.models import UserAttributes
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
plan = kwargs.get('plan', [])
|
plan = kwargs.get("plan", [])
|
||||||
for migration, rolled_back in plan:
|
for migration, rolled_back in plan:
|
||||||
if migration.app_label == 'accounts' and migration.name == '0001_initial' and not rolled_back:
|
if (
|
||||||
|
migration.app_label == "accounts"
|
||||||
|
and migration.name == "0001_initial"
|
||||||
|
and not rolled_back
|
||||||
|
):
|
||||||
if User.objects.count() == 0:
|
if User.objects.count() == 0:
|
||||||
print('\033[1m* \033[92mCreating default admin user\033[0m')
|
print("\033[1m* \033[92mCreating default admin user\033[0m")
|
||||||
admin = User.objects.create_superuser('admin', None, 'admin')
|
admin = User.objects.create_superuser("admin", None, "admin")
|
||||||
UserAttributes(user=admin, max_instances=-1, max_cpus=-1, max_memory=-1, max_disk_size=-1).save()
|
UserAttributes(
|
||||||
|
user=admin,
|
||||||
|
max_instances=-1,
|
||||||
|
max_cpus=-1,
|
||||||
|
max_memory=-1,
|
||||||
|
max_disk_size=-1,
|
||||||
|
).save()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
class AccountsConfig(AppConfig):
|
class AccountsConfig(AppConfig):
|
||||||
name = 'accounts'
|
name = "accounts"
|
||||||
verbose_name = 'Accounts'
|
verbose_name = "Accounts"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
post_migrate.connect(create_admin, sender=self)
|
post_migrate.connect(create_admin, sender=self)
|
||||||
|
|
|
@ -12,52 +12,52 @@ class UserInstanceForm(ModelForm):
|
||||||
super(UserInstanceForm, self).__init__(*args, **kwargs)
|
super(UserInstanceForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
# Make user and instance fields not editable after creation
|
# Make user and instance fields not editable after creation
|
||||||
instance = getattr(self, 'instance', None)
|
instance = getattr(self, "instance", None)
|
||||||
if instance and instance.id is not None:
|
if instance and instance.id is not None:
|
||||||
self.fields['user'].disabled = True
|
self.fields["user"].disabled = True
|
||||||
self.fields['instance'].disabled = True
|
self.fields["instance"].disabled = True
|
||||||
|
|
||||||
def clean_instance(self):
|
def clean_instance(self):
|
||||||
instance = self.cleaned_data['instance']
|
instance = self.cleaned_data["instance"]
|
||||||
if app_settings.ALLOW_INSTANCE_MULTIPLE_OWNER == 'False':
|
if app_settings.ALLOW_INSTANCE_MULTIPLE_OWNER == "False":
|
||||||
exists = UserInstance.objects.filter(instance=instance)
|
exists = UserInstance.objects.filter(instance=instance)
|
||||||
if exists:
|
if exists:
|
||||||
raise ValidationError(_('Instance owned by another user'))
|
raise ValidationError(_("Instance owned by another user"))
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserInstance
|
model = UserInstance
|
||||||
fields = '__all__'
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class ProfileForm(ModelForm):
|
class ProfileForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = get_user_model()
|
model = get_user_model()
|
||||||
fields = ('first_name', 'last_name', 'email')
|
fields = ("first_name", "last_name", "email")
|
||||||
|
|
||||||
|
|
||||||
class UserSSHKeyForm(ModelForm):
|
class UserSSHKeyForm(ModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.user = kwargs.pop('user', None)
|
self.user = kwargs.pop("user", None)
|
||||||
self.publickeys = UserSSHKey.objects.filter(user=self.user)
|
self.publickeys = UserSSHKey.objects.filter(user=self.user)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def clean_keyname(self):
|
def clean_keyname(self):
|
||||||
for key in self.publickeys:
|
for key in self.publickeys:
|
||||||
if self.cleaned_data['keyname'] == key.keyname:
|
if self.cleaned_data["keyname"] == key.keyname:
|
||||||
raise ValidationError(_("Key name already exist"))
|
raise ValidationError(_("Key name already exist"))
|
||||||
|
|
||||||
return self.cleaned_data['keyname']
|
return self.cleaned_data["keyname"]
|
||||||
|
|
||||||
def clean_keypublic(self):
|
def clean_keypublic(self):
|
||||||
for key in self.publickeys:
|
for key in self.publickeys:
|
||||||
if self.cleaned_data['keypublic'] == key.keypublic:
|
if self.cleaned_data["keypublic"] == key.keypublic:
|
||||||
raise ValidationError(_("Public key already exist"))
|
raise ValidationError(_("Public key already exist"))
|
||||||
|
|
||||||
if not validate_ssh_key(self.cleaned_data['keypublic']):
|
if not validate_ssh_key(self.cleaned_data["keypublic"]):
|
||||||
raise ValidationError(_('Invalid key'))
|
raise ValidationError(_("Invalid key"))
|
||||||
return self.cleaned_data['keypublic']
|
return self.cleaned_data["keypublic"]
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
ssh_key = super().save(commit=False)
|
ssh_key = super().save(commit=False)
|
||||||
|
@ -68,8 +68,8 @@ class UserSSHKeyForm(ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserSSHKey
|
model = UserSSHKey
|
||||||
fields = ('keyname', 'keypublic')
|
fields = ("keyname", "keypublic")
|
||||||
|
|
||||||
|
|
||||||
class EmailOTPForm(Form):
|
class EmailOTPForm(Form):
|
||||||
email = EmailField(label=_('Email'))
|
email = EmailField(label=_("Email"))
|
||||||
|
|
|
@ -7,7 +7,7 @@ from instances.models import Instance
|
||||||
|
|
||||||
class UserInstanceManager(models.Manager):
|
class UserInstanceManager(models.Manager):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().select_related('instance', 'user')
|
return super().get_queryset().select_related("instance", "user")
|
||||||
|
|
||||||
|
|
||||||
class UserInstance(models.Model):
|
class UserInstance(models.Model):
|
||||||
|
@ -20,16 +20,19 @@ class UserInstance(models.Model):
|
||||||
objects = UserInstanceManager()
|
objects = UserInstanceManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _('Instance "%(inst)s" of user %(user)s') % {"inst": self.instance, "user": self.user}
|
return _('Instance "%(inst)s" of user %(user)s') % {
|
||||||
|
"inst": self.instance,
|
||||||
|
"user": self.user,
|
||||||
|
}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ['user', 'instance']
|
unique_together = ["user", "instance"]
|
||||||
|
|
||||||
|
|
||||||
class UserSSHKey(models.Model):
|
class UserSSHKey(models.Model):
|
||||||
user = models.ForeignKey(User, on_delete=models.DO_NOTHING)
|
user = models.ForeignKey(User, on_delete=models.DO_NOTHING)
|
||||||
keyname = models.CharField(_('key name'), max_length=25)
|
keyname = models.CharField(_("key name"), max_length=25)
|
||||||
keypublic = models.CharField(_('public key'), max_length=500)
|
keypublic = models.CharField(_("public key"), max_length=500)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.keyname
|
return self.keyname
|
||||||
|
@ -38,26 +41,26 @@ class UserSSHKey(models.Model):
|
||||||
class UserAttributes(models.Model):
|
class UserAttributes(models.Model):
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||||
can_clone_instances = models.BooleanField(default=True)
|
can_clone_instances = models.BooleanField(default=True)
|
||||||
max_instances = models.IntegerField(_('max instances'),
|
max_instances = models.IntegerField(
|
||||||
default=2,
|
_("max instances"),
|
||||||
help_text=_("-1 for unlimited. Any integer value"),
|
default=2,
|
||||||
validators=[
|
help_text=_("-1 for unlimited. Any integer value"),
|
||||||
MinValueValidator(-1),
|
validators=[MinValueValidator(-1)],
|
||||||
])
|
)
|
||||||
max_cpus = models.IntegerField(
|
max_cpus = models.IntegerField(
|
||||||
_('max CPUs'),
|
_("max CPUs"),
|
||||||
default=2,
|
default=2,
|
||||||
help_text=_("-1 for unlimited. Any integer value"),
|
help_text=_("-1 for unlimited. Any integer value"),
|
||||||
validators=[MinValueValidator(-1)],
|
validators=[MinValueValidator(-1)],
|
||||||
)
|
)
|
||||||
max_memory = models.IntegerField(
|
max_memory = models.IntegerField(
|
||||||
_('max memory'),
|
_("max memory"),
|
||||||
default=2048,
|
default=2048,
|
||||||
help_text=_("-1 for unlimited. Any integer value"),
|
help_text=_("-1 for unlimited. Any integer value"),
|
||||||
validators=[MinValueValidator(-1)],
|
validators=[MinValueValidator(-1)],
|
||||||
)
|
)
|
||||||
max_disk_size = models.IntegerField(
|
max_disk_size = models.IntegerField(
|
||||||
_('max disk size'),
|
_("max disk size"),
|
||||||
default=20,
|
default=20,
|
||||||
help_text=_("-1 for unlimited. Any integer value"),
|
help_text=_("-1 for unlimited. Any integer value"),
|
||||||
validators=[MinValueValidator(-1)],
|
validators=[MinValueValidator(-1)],
|
||||||
|
@ -71,8 +74,9 @@ class PermissionSet(models.Model):
|
||||||
"""
|
"""
|
||||||
Dummy model for holding set of permissions we need to be automatically added by Django
|
Dummy model for holding set of permissions we need to be automatically added by Django
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
default_permissions = ()
|
default_permissions = ()
|
||||||
permissions = (('change_password', _('Can change password')), )
|
permissions = (("change_password", _("Can change password")),)
|
||||||
|
|
||||||
managed = False
|
managed = False
|
||||||
|
|
|
@ -23,15 +23,15 @@ class AccountsTestCase(TestCase):
|
||||||
# Add users for testing purposes
|
# Add users for testing purposes
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
cls.admin_user = User.objects.get(pk=1)
|
cls.admin_user = User.objects.get(pk=1)
|
||||||
cls.test_user = User.objects.create_user(username='test', password='test')
|
cls.test_user = User.objects.create_user(username="test", password="test")
|
||||||
|
|
||||||
# Add localhost compute
|
# Add localhost compute
|
||||||
cls.compute = Compute(
|
cls.compute = Compute(
|
||||||
name='test-compute',
|
name="test-compute",
|
||||||
hostname='localhost',
|
hostname="localhost",
|
||||||
login='',
|
login="",
|
||||||
password='',
|
password="",
|
||||||
details='local',
|
details="local",
|
||||||
type=4,
|
type=4,
|
||||||
)
|
)
|
||||||
cls.compute.save()
|
cls.compute.save()
|
||||||
|
@ -45,17 +45,17 @@ class AccountsTestCase(TestCase):
|
||||||
|
|
||||||
# Add disks for testing
|
# Add disks for testing
|
||||||
cls.connection.create_volume(
|
cls.connection.create_volume(
|
||||||
'default',
|
"default",
|
||||||
'test-volume',
|
"test-volume",
|
||||||
1,
|
1,
|
||||||
'qcow2',
|
"qcow2",
|
||||||
False,
|
False,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
|
|
||||||
# XML for testing vm
|
# XML for testing vm
|
||||||
with open('conf/test-vm.xml', 'r') as f:
|
with open("conf/test-vm.xml", "r") as f:
|
||||||
cls.xml = f.read()
|
cls.xml = f.read()
|
||||||
|
|
||||||
# Create testing vm from XML
|
# Create testing vm from XML
|
||||||
|
@ -71,80 +71,90 @@ class AccountsTestCase(TestCase):
|
||||||
super().tearDownClass()
|
super().tearDownClass()
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client.login(username='admin', password='admin')
|
self.client.login(username="admin", password="admin")
|
||||||
permission = Permission.objects.get(codename='change_password')
|
permission = Permission.objects.get(codename="change_password")
|
||||||
self.test_user.user_permissions.add(permission)
|
self.test_user.user_permissions.add(permission)
|
||||||
self.rsa_key = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6OOdbfv27QVnSC6sKxGaHb6YFc+3gxCkyVR3cTSXE/n5BEGf8aOgBpepULWa1RZfxYHY14PlKULDygdXSdrrR2kNSwoKz/Oo4d+3EE92L7ocl1+djZbptzgWgtw1OseLwbFik+iKlIdqPsH+IUQvX7yV545ZQtAP8Qj1R+uCqkw== test@test'
|
self.rsa_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6OOdbfv27QVnSC6sKxGaHb6YFc+3gxCkyVR3cTSXE/n5BEGf8aOgBpepULWa1RZfxYHY14PlKULDygdXSdrrR2kNSwoKz/Oo4d+3EE92L7ocl1+djZbptzgWgtw1OseLwbFik+iKlIdqPsH+IUQvX7yV545ZQtAP8Qj1R+uCqkw== test@test"
|
||||||
self.ecdsa_key = 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJc5xpT3R0iFJYNZbmWgAiDlHquX/BcV1kVTsnBfiMsZgU3lGaqz2eb7IBcir/dxGnsVENTTmPQ6sNcxLxT9kkQ= realgecko@archlinux'
|
self.ecdsa_key = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJc5xpT3R0iFJYNZbmWgAiDlHquX/BcV1kVTsnBfiMsZgU3lGaqz2eb7IBcir/dxGnsVENTTmPQ6sNcxLxT9kkQ= realgecko@archlinux"
|
||||||
|
|
||||||
def test_profile(self):
|
def test_profile(self):
|
||||||
response = self.client.get(reverse('accounts:profile'))
|
response = self.client.get(reverse("accounts:profile"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.get(reverse('accounts:account', args=[self.test_user.id]))
|
response = self.client.get(
|
||||||
|
reverse("accounts:account", args=[self.test_user.id])
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_account_with_otp(self):
|
def test_account_with_otp(self):
|
||||||
settings.OTP_ENABLED = True
|
settings.OTP_ENABLED = True
|
||||||
response = self.client.get(reverse('accounts:account', args=[self.test_user.id]))
|
response = self.client.get(
|
||||||
|
reverse("accounts:account", args=[self.test_user.id])
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_login_logout(self):
|
def test_login_logout(self):
|
||||||
client = Client()
|
client = Client()
|
||||||
|
|
||||||
response = client.post(reverse("accounts:login"), {"username": "test", "password": "test"})
|
response = client.post(
|
||||||
self.assertRedirects(response, reverse('accounts:profile'))
|
reverse("accounts:login"), {"username": "test", "password": "test"}
|
||||||
|
)
|
||||||
|
self.assertRedirects(response, reverse("accounts:profile"))
|
||||||
|
|
||||||
response = client.get(reverse('accounts:logout'))
|
response = client.get(reverse("accounts:logout"))
|
||||||
self.assertRedirects(response, reverse('accounts:login'))
|
self.assertRedirects(response, reverse("accounts:login"))
|
||||||
|
|
||||||
def test_change_password(self):
|
def test_change_password(self):
|
||||||
self.client.force_login(self.test_user)
|
self.client.force_login(self.test_user)
|
||||||
|
|
||||||
response = self.client.get(reverse('accounts:change_password'))
|
response = self.client.get(reverse("accounts:change_password"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse('accounts:change_password'),
|
reverse("accounts:change_password"),
|
||||||
{
|
{
|
||||||
'old_password': 'wrongpass',
|
"old_password": "wrongpass",
|
||||||
'new_password1': 'newpw',
|
"new_password1": "newpw",
|
||||||
'new_password2': 'newpw',
|
"new_password2": "newpw",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse('accounts:change_password'),
|
reverse("accounts:change_password"),
|
||||||
{
|
{
|
||||||
'old_password': 'test',
|
"old_password": "test",
|
||||||
'new_password1': 'newpw',
|
"new_password1": "newpw",
|
||||||
'new_password2': 'newpw',
|
"new_password2": "newpw",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertRedirects(response, reverse('accounts:profile'))
|
self.assertRedirects(response, reverse("accounts:profile"))
|
||||||
|
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
|
||||||
logged_in = self.client.login(username='test', password='newpw')
|
logged_in = self.client.login(username="test", password="newpw")
|
||||||
self.assertTrue(logged_in)
|
self.assertTrue(logged_in)
|
||||||
|
|
||||||
def test_user_instance_create_update_delete(self):
|
def test_user_instance_create_update_delete(self):
|
||||||
# create
|
# create
|
||||||
response = self.client.get(reverse('accounts:user_instance_create', args=[self.test_user.id]))
|
response = self.client.get(
|
||||||
|
reverse("accounts:user_instance_create", args=[self.test_user.id])
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse('accounts:user_instance_create', args=[self.test_user.id]),
|
reverse("accounts:user_instance_create", args=[self.test_user.id]),
|
||||||
{
|
{
|
||||||
'user': self.test_user.id,
|
"user": self.test_user.id,
|
||||||
'instance': self.instance.id,
|
"instance": self.instance.id,
|
||||||
'is_change': False,
|
"is_change": False,
|
||||||
'is_delete': False,
|
"is_delete": False,
|
||||||
'is_vnc': False,
|
"is_vnc": False,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertRedirects(response, reverse('accounts:account', args=[self.test_user.id]))
|
self.assertRedirects(
|
||||||
|
response, reverse("accounts:account", args=[self.test_user.id])
|
||||||
|
)
|
||||||
|
|
||||||
user_instance: UserInstance = UserInstance.objects.get(pk=1)
|
user_instance: UserInstance = UserInstance.objects.get(pk=1)
|
||||||
self.assertEqual(user_instance.user, self.test_user)
|
self.assertEqual(user_instance.user, self.test_user)
|
||||||
|
@ -154,20 +164,24 @@ class AccountsTestCase(TestCase):
|
||||||
self.assertEqual(user_instance.is_vnc, False)
|
self.assertEqual(user_instance.is_vnc, False)
|
||||||
|
|
||||||
# update
|
# update
|
||||||
response = self.client.get(reverse('accounts:user_instance_update', args=[user_instance.id]))
|
response = self.client.get(
|
||||||
|
reverse("accounts:user_instance_update", args=[user_instance.id])
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse('accounts:user_instance_update', args=[user_instance.id]),
|
reverse("accounts:user_instance_update", args=[user_instance.id]),
|
||||||
{
|
{
|
||||||
'user': self.test_user.id,
|
"user": self.test_user.id,
|
||||||
'instance': self.instance.id,
|
"instance": self.instance.id,
|
||||||
'is_change': True,
|
"is_change": True,
|
||||||
'is_delete': True,
|
"is_delete": True,
|
||||||
'is_vnc': True,
|
"is_vnc": True,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertRedirects(response, reverse('accounts:account', args=[self.test_user.id]))
|
self.assertRedirects(
|
||||||
|
response, reverse("accounts:account", args=[self.test_user.id])
|
||||||
|
)
|
||||||
|
|
||||||
user_instance: UserInstance = UserInstance.objects.get(pk=1)
|
user_instance: UserInstance = UserInstance.objects.get(pk=1)
|
||||||
self.assertEqual(user_instance.user, self.test_user)
|
self.assertEqual(user_instance.user, self.test_user)
|
||||||
|
@ -177,91 +191,118 @@ class AccountsTestCase(TestCase):
|
||||||
self.assertEqual(user_instance.is_vnc, True)
|
self.assertEqual(user_instance.is_vnc, True)
|
||||||
|
|
||||||
# delete
|
# delete
|
||||||
response = self.client.get(reverse('accounts:user_instance_delete', args=[user_instance.id]))
|
response = self.client.get(
|
||||||
|
reverse("accounts:user_instance_delete", args=[user_instance.id])
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(reverse('accounts:user_instance_delete', args=[user_instance.id]))
|
response = self.client.post(
|
||||||
self.assertRedirects(response, reverse('accounts:account', args=[self.test_user.id]))
|
reverse("accounts:user_instance_delete", args=[user_instance.id])
|
||||||
|
)
|
||||||
|
self.assertRedirects(
|
||||||
|
response, reverse("accounts:account", args=[self.test_user.id])
|
||||||
|
)
|
||||||
|
|
||||||
# test 'next' redirect during deletion
|
# test 'next' redirect during deletion
|
||||||
user_instance = UserInstance.objects.create(user=self.test_user, instance=self.instance)
|
user_instance = UserInstance.objects.create(
|
||||||
|
user=self.test_user, instance=self.instance
|
||||||
|
)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse('accounts:user_instance_delete', args=[user_instance.id]) + '?next=' + reverse('index'))
|
reverse("accounts:user_instance_delete", args=[user_instance.id])
|
||||||
self.assertRedirects(response, reverse('index'))
|
+ "?next="
|
||||||
|
+ reverse("index")
|
||||||
|
)
|
||||||
|
self.assertRedirects(response, reverse("index"))
|
||||||
|
|
||||||
def test_update_user_profile(self):
|
def test_update_user_profile(self):
|
||||||
self.client.force_login(self.test_user)
|
self.client.force_login(self.test_user)
|
||||||
|
|
||||||
user = get_user_model().objects.get(username='test')
|
user = get_user_model().objects.get(username="test")
|
||||||
self.assertEqual(user.first_name, '')
|
self.assertEqual(user.first_name, "")
|
||||||
self.assertEqual(user.last_name, '')
|
self.assertEqual(user.last_name, "")
|
||||||
self.assertEqual(user.email, '')
|
self.assertEqual(user.email, "")
|
||||||
|
|
||||||
response = self.client.post(reverse('accounts:profile'), {
|
response = self.client.post(
|
||||||
'first_name': 'first name',
|
reverse("accounts:profile"),
|
||||||
'last_name': 'last name',
|
{
|
||||||
'email': 'email@mail.mail',
|
"first_name": "first name",
|
||||||
})
|
"last_name": "last name",
|
||||||
self.assertRedirects(response, reverse('accounts:profile'))
|
"email": "email@mail.mail",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertRedirects(response, reverse("accounts:profile"))
|
||||||
|
|
||||||
user = get_user_model().objects.get(username='test')
|
user = get_user_model().objects.get(username="test")
|
||||||
self.assertEqual(user.first_name, 'first name')
|
self.assertEqual(user.first_name, "first name")
|
||||||
self.assertEqual(user.last_name, 'last name')
|
self.assertEqual(user.last_name, "last name")
|
||||||
self.assertEqual(user.email, 'email@mail.mail')
|
self.assertEqual(user.email, "email@mail.mail")
|
||||||
|
|
||||||
def test_create_delete_ssh_key(self):
|
def test_create_delete_ssh_key(self):
|
||||||
response = self.client.get(reverse('accounts:ssh_key_create'))
|
response = self.client.get(reverse("accounts:ssh_key_create"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(reverse('accounts:ssh_key_create'), {
|
response = self.client.post(
|
||||||
'keyname': 'keyname',
|
reverse("accounts:ssh_key_create"),
|
||||||
'keypublic': self.rsa_key,
|
{
|
||||||
})
|
"keyname": "keyname",
|
||||||
self.assertRedirects(response, reverse('accounts:profile'))
|
"keypublic": self.rsa_key,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertRedirects(response, reverse("accounts:profile"))
|
||||||
|
|
||||||
key = UserSSHKey.objects.get(pk=1)
|
key = UserSSHKey.objects.get(pk=1)
|
||||||
self.assertEqual(key.keyname, 'keyname')
|
self.assertEqual(key.keyname, "keyname")
|
||||||
self.assertEqual(key.keypublic, self.rsa_key)
|
self.assertEqual(key.keypublic, self.rsa_key)
|
||||||
|
|
||||||
response = self.client.get(reverse('accounts:ssh_key_delete', args=[1]))
|
response = self.client.get(reverse("accounts:ssh_key_delete", args=[1]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(reverse('accounts:ssh_key_delete', args=[1]))
|
response = self.client.post(reverse("accounts:ssh_key_delete", args=[1]))
|
||||||
self.assertRedirects(response, reverse('accounts:profile'))
|
self.assertRedirects(response, reverse("accounts:profile"))
|
||||||
|
|
||||||
def test_validate_ssh_key(self):
|
def test_validate_ssh_key(self):
|
||||||
self.assertFalse(validate_ssh_key(''))
|
self.assertFalse(validate_ssh_key(""))
|
||||||
self.assertFalse(validate_ssh_key('ssh-rsa ABBA test@test'))
|
self.assertFalse(validate_ssh_key("ssh-rsa ABBA test@test"))
|
||||||
self.assertFalse(validate_ssh_key('ssh-rsa AAAABwdzZGY= test@test'))
|
self.assertFalse(validate_ssh_key("ssh-rsa AAAABwdzZGY= test@test"))
|
||||||
self.assertFalse(validate_ssh_key('ssh-rsa AAA test@test'))
|
self.assertFalse(validate_ssh_key("ssh-rsa AAA test@test"))
|
||||||
# validate ecdsa key
|
# validate ecdsa key
|
||||||
self.assertTrue(validate_ssh_key(self.ecdsa_key))
|
self.assertTrue(validate_ssh_key(self.ecdsa_key))
|
||||||
|
|
||||||
def test_forms(self):
|
def test_forms(self):
|
||||||
# raise available validation errors for maximum coverage
|
# raise available validation errors for maximum coverage
|
||||||
form = UserSSHKeyForm({'keyname': 'keyname', 'keypublic': self.rsa_key}, user=self.test_user)
|
form = UserSSHKeyForm(
|
||||||
|
{"keyname": "keyname", "keypublic": self.rsa_key}, user=self.test_user
|
||||||
|
)
|
||||||
form.save()
|
form.save()
|
||||||
|
|
||||||
form = UserSSHKeyForm({'keyname': 'keyname', 'keypublic': self.rsa_key}, user=self.test_user)
|
form = UserSSHKeyForm(
|
||||||
|
{"keyname": "keyname", "keypublic": self.rsa_key}, user=self.test_user
|
||||||
|
)
|
||||||
self.assertFalse(form.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
|
|
||||||
form = UserSSHKeyForm({'keyname': 'keyname', 'keypublic': 'invalid key'}, user=self.test_user)
|
form = UserSSHKeyForm(
|
||||||
|
{"keyname": "keyname", "keypublic": "invalid key"}, user=self.test_user
|
||||||
|
)
|
||||||
self.assertFalse(form.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
|
|
||||||
app_settings.ALLOW_INSTANCE_MULTIPLE_OWNER = 'False'
|
app_settings.ALLOW_INSTANCE_MULTIPLE_OWNER = "False"
|
||||||
form = UserInstanceForm({
|
form = UserInstanceForm(
|
||||||
'user': self.admin_user.id,
|
{
|
||||||
'instance': self.instance.id,
|
"user": self.admin_user.id,
|
||||||
'is_change': False,
|
"instance": self.instance.id,
|
||||||
'is_delete': False,
|
"is_change": False,
|
||||||
'is_vnc': False,
|
"is_delete": False,
|
||||||
})
|
"is_vnc": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
form.save()
|
form.save()
|
||||||
form = UserInstanceForm({
|
form = UserInstanceForm(
|
||||||
'user': self.test_user.id,
|
{
|
||||||
'instance': self.instance.id,
|
"user": self.test_user.id,
|
||||||
'is_change': False,
|
"instance": self.instance.id,
|
||||||
'is_delete': False,
|
"is_change": False,
|
||||||
'is_vnc': False,
|
"is_delete": False,
|
||||||
})
|
"is_vnc": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
self.assertFalse(form.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
|
|
|
@ -5,29 +5,50 @@ from django_otp.forms import OTPAuthenticationForm
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
app_name = 'accounts'
|
app_name = "accounts"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('logout/', LogoutView.as_view(template_name='logout.html'), name='logout'),
|
path("logout/", LogoutView.as_view(template_name="logout.html"), name="logout"),
|
||||||
path('profile/', views.profile, name='profile'),
|
path("profile/", views.profile, name="profile"),
|
||||||
path('profile/<int:user_id>/', views.account, name='account'),
|
path("profile/<int:user_id>/", views.account, name="account"),
|
||||||
path('change_password/', views.change_password, name='change_password'),
|
path("change_password/", views.change_password, name="change_password"),
|
||||||
path('user_instance/create/<int:user_id>/', views.user_instance_create, name='user_instance_create'),
|
path(
|
||||||
path('user_instance/<int:pk>/update/', views.user_instance_update, name='user_instance_update'),
|
"user_instance/create/<int:user_id>/",
|
||||||
path('user_instance/<int:pk>/delete/', views.user_instance_delete, name='user_instance_delete'),
|
views.user_instance_create,
|
||||||
path('ssh_key/create/', views.ssh_key_create, name='ssh_key_create'),
|
name="user_instance_create",
|
||||||
path('ssh_key/<int:pk>/delete/', views.ssh_key_delete, name='ssh_key_delete'),
|
),
|
||||||
|
path(
|
||||||
|
"user_instance/<int:pk>/update/",
|
||||||
|
views.user_instance_update,
|
||||||
|
name="user_instance_update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"user_instance/<int:pk>/delete/",
|
||||||
|
views.user_instance_delete,
|
||||||
|
name="user_instance_delete",
|
||||||
|
),
|
||||||
|
path("ssh_key/create/", views.ssh_key_create, name="ssh_key_create"),
|
||||||
|
path("ssh_key/<int:pk>/delete/", views.ssh_key_delete, name="ssh_key_delete"),
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.OTP_ENABLED:
|
if settings.OTP_ENABLED:
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
path(
|
path(
|
||||||
'login/',
|
"login/",
|
||||||
LoginView.as_view(template_name='accounts/otp_login.html', authentication_form=OTPAuthenticationForm),
|
LoginView.as_view(
|
||||||
name='login',
|
template_name="accounts/otp_login.html",
|
||||||
|
authentication_form=OTPAuthenticationForm,
|
||||||
|
),
|
||||||
|
name="login",
|
||||||
|
),
|
||||||
|
path("email_otp/", views.email_otp, name="email_otp"),
|
||||||
|
path(
|
||||||
|
"admin_email_otp/<int:user_id>/",
|
||||||
|
views.admin_email_otp,
|
||||||
|
name="admin_email_otp",
|
||||||
),
|
),
|
||||||
path('email_otp/', views.email_otp, name='email_otp'),
|
|
||||||
path('admin_email_otp/<int:user_id>/', views.admin_email_otp, name='admin_email_otp'),
|
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
urlpatterns += path('login/', LoginView.as_view(template_name='login.html'), name='login'),
|
urlpatterns += (
|
||||||
|
path("login/", LoginView.as_view(template_name="login.html"), name="login"),
|
||||||
|
)
|
||||||
|
|
|
@ -172,7 +172,10 @@ def email_otp(request):
|
||||||
device = get_user_totp_device(user)
|
device = get_user_totp_device(user)
|
||||||
send_email_with_otp(user, device)
|
send_email_with_otp(user, device)
|
||||||
|
|
||||||
messages.success(request, _("OTP Sent to %(email)s") % {"email": form.cleaned_data["email"]})
|
messages.success(
|
||||||
|
request,
|
||||||
|
_("OTP Sent to %(email)s") % {"email": form.cleaned_data["email"]}
|
||||||
|
)
|
||||||
return redirect("accounts:login")
|
return redirect("accounts:login")
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
|
@ -191,7 +194,13 @@ def admin_email_otp(request, user_id):
|
||||||
device = get_user_totp_device(user)
|
device = get_user_totp_device(user)
|
||||||
if user.email != "":
|
if user.email != "":
|
||||||
send_email_with_otp(user, device)
|
send_email_with_otp(user, device)
|
||||||
messages.success(request, _("OTP QR code was emailed to user %(user)s") % {"user": user})
|
messages.success(
|
||||||
|
request,
|
||||||
|
_("OTP QR code was emailed to user %(user)s") % {"user": user}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
messages.error(request, _("User email not set, failed to send QR code"))
|
messages.error(
|
||||||
|
request,
|
||||||
|
_("User email not set, failed to send QR code")
|
||||||
|
)
|
||||||
return redirect("accounts:account", user.id)
|
return redirect("accounts:account", user.id)
|
||||||
|
|
|
@ -2,4 +2,4 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class AdminConfig(AppConfig):
|
class AdminConfig(AppConfig):
|
||||||
name = 'admin'
|
name = "admin"
|
||||||
|
|
|
@ -7,7 +7,7 @@ class Permission(P):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.content_type.app_label}: {self.name}'
|
return f"{self.content_type.app_label}: {self.name}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
|
@ -8,62 +8,66 @@ from accounts.models import UserAttributes
|
||||||
|
|
||||||
class AdminTestCase(TestCase):
|
class AdminTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client.login(username='admin', password='admin')
|
self.client.login(username="admin", password="admin")
|
||||||
|
|
||||||
def test_group_list(self):
|
def test_group_list(self):
|
||||||
response = self.client.get(reverse('admin:group_list'))
|
response = self.client.get(reverse("admin:group_list"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_groups(self):
|
def test_groups(self):
|
||||||
response = self.client.get(reverse('admin:group_create'))
|
response = self.client.get(reverse("admin:group_create"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(reverse('admin:group_create'), {'name': 'Test Group'})
|
response = self.client.post(
|
||||||
self.assertRedirects(response, reverse('admin:group_list'))
|
reverse("admin:group_create"), {"name": "Test Group"}
|
||||||
|
)
|
||||||
|
self.assertRedirects(response, reverse("admin:group_list"))
|
||||||
|
|
||||||
group = Group.objects.get(name='Test Group')
|
group = Group.objects.get(name="Test Group")
|
||||||
self.assertEqual(group.id, 1)
|
self.assertEqual(group.id, 1)
|
||||||
|
|
||||||
response = self.client.get(reverse('admin:group_update', args=[1]))
|
response = self.client.get(reverse("admin:group_update", args=[1]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(reverse('admin:group_update', args=[1]), {'name': 'Updated Group Test'})
|
response = self.client.post(
|
||||||
self.assertRedirects(response, reverse('admin:group_list'))
|
reverse("admin:group_update", args=[1]), {"name": "Updated Group Test"}
|
||||||
|
)
|
||||||
|
self.assertRedirects(response, reverse("admin:group_list"))
|
||||||
|
|
||||||
group = Group.objects.get(id=1)
|
group = Group.objects.get(id=1)
|
||||||
self.assertEqual(group.name, 'Updated Group Test')
|
self.assertEqual(group.name, "Updated Group Test")
|
||||||
|
|
||||||
response = self.client.get(reverse('admin:group_delete', args=[1]))
|
response = self.client.get(reverse("admin:group_delete", args=[1]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(reverse('admin:group_delete', args=[1]))
|
response = self.client.post(reverse("admin:group_delete", args=[1]))
|
||||||
self.assertRedirects(response, reverse('admin:group_list'))
|
self.assertRedirects(response, reverse("admin:group_list"))
|
||||||
|
|
||||||
with self.assertRaises(ObjectDoesNotExist):
|
with self.assertRaises(ObjectDoesNotExist):
|
||||||
Group.objects.get(id=1)
|
Group.objects.get(id=1)
|
||||||
|
|
||||||
def test_user_list(self):
|
def test_user_list(self):
|
||||||
response = self.client.get(reverse('admin:user_list'))
|
response = self.client.get(reverse("admin:user_list"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_users(self):
|
def test_users(self):
|
||||||
response = self.client.get(reverse('admin:user_create'))
|
response = self.client.get(reverse("admin:user_create"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse('admin:user_create'),
|
reverse("admin:user_create"),
|
||||||
{
|
{
|
||||||
'username': 'test',
|
"username": "test",
|
||||||
'password': 'test',
|
"password": "test",
|
||||||
'max_instances': 1,
|
"max_instances": 1,
|
||||||
'max_cpus': 1,
|
"max_cpus": 1,
|
||||||
'max_memory': 1024,
|
"max_memory": 1024,
|
||||||
'max_disk_size': 4,
|
"max_disk_size": 4,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertRedirects(response, reverse('admin:user_list'))
|
self.assertRedirects(response, reverse("admin:user_list"))
|
||||||
|
|
||||||
user = User.objects.get(username='test')
|
user = User.objects.get(username="test")
|
||||||
self.assertEqual(user.id, 2)
|
self.assertEqual(user.id, 2)
|
||||||
|
|
||||||
ua: UserAttributes = UserAttributes.objects.get(id=2)
|
ua: UserAttributes = UserAttributes.objects.get(id=2)
|
||||||
|
@ -73,23 +77,23 @@ class AdminTestCase(TestCase):
|
||||||
self.assertEqual(ua.max_memory, 1024)
|
self.assertEqual(ua.max_memory, 1024)
|
||||||
self.assertEqual(ua.max_disk_size, 4)
|
self.assertEqual(ua.max_disk_size, 4)
|
||||||
|
|
||||||
response = self.client.get(reverse('admin:user_update', args=[2]))
|
response = self.client.get(reverse("admin:user_update", args=[2]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse('admin:user_update', args=[2]),
|
reverse("admin:user_update", args=[2]),
|
||||||
{
|
{
|
||||||
'username': 'utest',
|
"username": "utest",
|
||||||
'max_instances': 2,
|
"max_instances": 2,
|
||||||
'max_cpus': 2,
|
"max_cpus": 2,
|
||||||
'max_memory': 2048,
|
"max_memory": 2048,
|
||||||
'max_disk_size': 8,
|
"max_disk_size": 8,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertRedirects(response, reverse('admin:user_list'))
|
self.assertRedirects(response, reverse("admin:user_list"))
|
||||||
|
|
||||||
user = User.objects.get(id=2)
|
user = User.objects.get(id=2)
|
||||||
self.assertEqual(user.username, 'utest')
|
self.assertEqual(user.username, "utest")
|
||||||
|
|
||||||
ua: UserAttributes = UserAttributes.objects.get(id=2)
|
ua: UserAttributes = UserAttributes.objects.get(id=2)
|
||||||
self.assertEqual(ua.user_id, 2)
|
self.assertEqual(ua.user_id, 2)
|
||||||
|
@ -98,23 +102,23 @@ class AdminTestCase(TestCase):
|
||||||
self.assertEqual(ua.max_memory, 2048)
|
self.assertEqual(ua.max_memory, 2048)
|
||||||
self.assertEqual(ua.max_disk_size, 8)
|
self.assertEqual(ua.max_disk_size, 8)
|
||||||
|
|
||||||
response = self.client.get(reverse('admin:user_block', args=[2]))
|
response = self.client.get(reverse("admin:user_block", args=[2]))
|
||||||
user = User.objects.get(id=2)
|
user = User.objects.get(id=2)
|
||||||
self.assertFalse(user.is_active)
|
self.assertFalse(user.is_active)
|
||||||
|
|
||||||
response = self.client.get(reverse('admin:user_unblock', args=[2]))
|
response = self.client.get(reverse("admin:user_unblock", args=[2]))
|
||||||
user = User.objects.get(id=2)
|
user = User.objects.get(id=2)
|
||||||
self.assertTrue(user.is_active)
|
self.assertTrue(user.is_active)
|
||||||
|
|
||||||
response = self.client.get(reverse('admin:user_delete', args=[2]))
|
response = self.client.get(reverse("admin:user_delete", args=[2]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(reverse('admin:user_delete', args=[2]))
|
response = self.client.post(reverse("admin:user_delete", args=[2]))
|
||||||
self.assertRedirects(response, reverse('admin:user_list'))
|
self.assertRedirects(response, reverse("admin:user_list"))
|
||||||
|
|
||||||
with self.assertRaises(ObjectDoesNotExist):
|
with self.assertRaises(ObjectDoesNotExist):
|
||||||
User.objects.get(id=2)
|
User.objects.get(id=2)
|
||||||
|
|
||||||
def test_logs(self):
|
def test_logs(self):
|
||||||
response = self.client.get(reverse('admin:logs'))
|
response = self.client.get(reverse("admin:logs"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
|
@ -3,16 +3,16 @@ from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('groups/', views.group_list, name='group_list'),
|
path("groups/", views.group_list, name="group_list"),
|
||||||
path('groups/create/', views.group_create, name='group_create'),
|
path("groups/create/", views.group_create, name="group_create"),
|
||||||
path('groups/<int:pk>/update/', views.group_update, name='group_update'),
|
path("groups/<int:pk>/update/", views.group_update, name="group_update"),
|
||||||
path('groups/<int:pk>/delete/', views.group_delete, name='group_delete'),
|
path("groups/<int:pk>/delete/", views.group_delete, name="group_delete"),
|
||||||
path('users/', views.user_list, name='user_list'),
|
path("users/", views.user_list, name="user_list"),
|
||||||
path('users/create/', views.user_create, name='user_create'),
|
path("users/create/", views.user_create, name="user_create"),
|
||||||
path('users/<int:pk>/update_password/', views.user_update_password, name='user_update_password'),
|
path("users/<int:pk>/update_password/", views.user_update_password, name="user_update_password"),
|
||||||
path('users/<int:pk>/update/', views.user_update, name='user_update'),
|
path("users/<int:pk>/update/", views.user_update, name="user_update"),
|
||||||
path('users/<int:pk>/delete/', views.user_delete, name='user_delete'),
|
path("users/<int:pk>/delete/", views.user_delete, name="user_delete"),
|
||||||
path('users/<int:pk>/block/', views.user_block, name='user_block'),
|
path("users/<int:pk>/block/", views.user_block, name="user_block"),
|
||||||
path('users/<int:pk>/unblock/', views.user_unblock, name='user_unblock'),
|
path("users/<int:pk>/unblock/", views.user_unblock, name="user_unblock"),
|
||||||
path('logs/', views.logs, name='logs'),
|
path("logs/", views.logs, name="logs"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -107,7 +107,11 @@ def user_create(request):
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"admin/user_form.html",
|
"admin/user_form.html",
|
||||||
{"user_form": user_form, "attributes_form": attributes_form, "title": _("Create User")},
|
{
|
||||||
|
"user_form": user_form,
|
||||||
|
"attributes_form": attributes_form,
|
||||||
|
"title": _("Create User"),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,7 +120,9 @@ def user_update(request, pk):
|
||||||
user = get_object_or_404(User, pk=pk)
|
user = get_object_or_404(User, pk=pk)
|
||||||
attributes = UserAttributes.objects.get(user=user)
|
attributes = UserAttributes.objects.get(user=user)
|
||||||
user_form = forms.UserForm(request.POST or None, instance=user)
|
user_form = forms.UserForm(request.POST or None, instance=user)
|
||||||
attributes_form = forms.UserAttributesForm(request.POST or None, instance=attributes)
|
attributes_form = forms.UserAttributesForm(
|
||||||
|
request.POST or None, instance=attributes
|
||||||
|
)
|
||||||
if user_form.is_valid() and attributes_form.is_valid():
|
if user_form.is_valid() and attributes_form.is_valid():
|
||||||
user_form.save()
|
user_form.save()
|
||||||
attributes_form.save()
|
attributes_form.save()
|
||||||
|
@ -126,7 +132,11 @@ def user_update(request, pk):
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"admin/user_form.html",
|
"admin/user_form.html",
|
||||||
{"user_form": user_form, "attributes_form": attributes_form, "title": _("Update User")},
|
{
|
||||||
|
"user_form": user_form,
|
||||||
|
"attributes_form": attributes_form,
|
||||||
|
"title": _("Update User"),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,7 +148,10 @@ def user_update_password(request, pk):
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
user = form.save()
|
user = form.save()
|
||||||
update_session_auth_hash(request, user) # Important!
|
update_session_auth_hash(request, user) # Important!
|
||||||
messages.success(request, _("Password changed for %(user)s") % {"user": user.username})
|
messages.success(
|
||||||
|
request,
|
||||||
|
_("Password changed for %(user)s") % {"user": user.username}
|
||||||
|
)
|
||||||
return redirect("admin:user_list")
|
return redirect("admin:user_list")
|
||||||
else:
|
else:
|
||||||
messages.error(request, _("Wrong Data Provided"))
|
messages.error(request, _("Wrong Data Provided"))
|
||||||
|
|
|
@ -2,4 +2,4 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class AppsettingsConfig(AppConfig):
|
class AppsettingsConfig(AppConfig):
|
||||||
name = 'appsettings'
|
name = "appsettings"
|
||||||
|
|
|
@ -3,12 +3,11 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class AppSettings(models.Model):
|
class AppSettings(models.Model):
|
||||||
|
|
||||||
def choices_as_list(self):
|
def choices_as_list(self):
|
||||||
return self.choices.split(',')
|
return self.choices.split(",")
|
||||||
|
|
||||||
name = models.CharField(_('name'), max_length=25, null=False)
|
name = models.CharField(_("name"), max_length=25, null=False)
|
||||||
key = models.CharField(_('key'), db_index=True, max_length=50, unique=True)
|
key = models.CharField(_("key"), db_index=True, max_length=50, unique=True)
|
||||||
value = models.CharField(_('value'), max_length=25)
|
value = models.CharField(_("value"), max_length=25)
|
||||||
choices = models.CharField(_('choices'), max_length=70)
|
choices = models.CharField(_("choices"), max_length=70)
|
||||||
description = models.CharField(_('description'), max_length=100, null=True)
|
description = models.CharField(_("description"), max_length=100, null=True)
|
||||||
|
|
|
@ -27,7 +27,9 @@ def appsettings(request):
|
||||||
addlogmsg(request.user.username, "-", "", err)
|
addlogmsg(request.user.username, "-", "", err)
|
||||||
|
|
||||||
# Bootstrap settings related with filesystems, because of that they are excluded from other settings
|
# Bootstrap settings related with filesystems, because of that they are excluded from other settings
|
||||||
appsettings = AppSettings.objects.exclude(description__startswith="Bootstrap").order_by("name")
|
appsettings = AppSettings.objects.exclude(
|
||||||
|
description__startswith="Bootstrap"
|
||||||
|
).order_by("name")
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if "SASS_DIR" in request.POST:
|
if "SASS_DIR" in request.POST:
|
||||||
|
@ -35,7 +37,9 @@ def appsettings(request):
|
||||||
sass_dir.value = request.POST.get("SASS_DIR", "")
|
sass_dir.value = request.POST.get("SASS_DIR", "")
|
||||||
sass_dir.save()
|
sass_dir.save()
|
||||||
|
|
||||||
msg = _("SASS directory path is changed. Now: %(dir)s") % {"dir": sass_dir.value}
|
msg = _("SASS directory path is changed. Now: %(dir)s") % {
|
||||||
|
"dir": sass_dir.value
|
||||||
|
}
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
msg = err
|
msg = err
|
||||||
|
@ -47,15 +51,17 @@ def appsettings(request):
|
||||||
if "BOOTSTRAP_THEME" in request.POST:
|
if "BOOTSTRAP_THEME" in request.POST:
|
||||||
theme = request.POST.get("BOOTSTRAP_THEME", "")
|
theme = request.POST.get("BOOTSTRAP_THEME", "")
|
||||||
scss_var = f"@import '{sass_dir.value}/wvc-theme/{theme}/variables';"
|
scss_var = f"@import '{sass_dir.value}/wvc-theme/{theme}/variables';"
|
||||||
#scss_boot = f"@import '{sass_dir.value}/bootstrap/bootstrap.scss';"
|
# scss_boot = f"@import '{sass_dir.value}/bootstrap/bootstrap.scss';"
|
||||||
scss_boot = f"@import '{sass_dir.value}/bootstrap-overrides.scss';"
|
scss_boot = f"@import '{sass_dir.value}/bootstrap-overrides.scss';"
|
||||||
scss_bootswatch = f"@import '{sass_dir.value}/wvc-theme/{theme}/bootswatch';"
|
scss_bootswatch = (
|
||||||
|
f"@import '{sass_dir.value}/wvc-theme/{theme}/bootswatch';"
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(sass_dir.value + "/wvc-main.scss", "w") as main:
|
with open(sass_dir.value + "/wvc-main.scss", "w") as main:
|
||||||
main.write(scss_var + "\n" + scss_boot + "\n" + scss_bootswatch + "\n")
|
main.write(
|
||||||
|
scss_var + "\n" + scss_boot + "\n" + scss_bootswatch + "\n"
|
||||||
|
)
|
||||||
|
|
||||||
css_compressed = sass.compile(
|
css_compressed = sass.compile(
|
||||||
string=scss_var + "\n" + scss_boot + "\n" + scss_bootswatch,
|
string=scss_var + "\n" + scss_boot + "\n" + scss_bootswatch,
|
||||||
|
@ -82,7 +88,10 @@ def appsettings(request):
|
||||||
setting.value = request.POST.get(setting.key, "")
|
setting.value = request.POST.get(setting.key, "")
|
||||||
setting.save()
|
setting.save()
|
||||||
|
|
||||||
msg = _("%(setting)s is changed. Now: %(value)s") % {"setting": setting.name, "value": setting.value}
|
msg = _("%(setting)s is changed. Now: %(value)s") % {
|
||||||
|
"setting": setting.name,
|
||||||
|
"value": setting.value,
|
||||||
|
}
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
msg = err
|
msg = err
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from computes.models import Compute
|
from computes.models import Compute
|
||||||
from vrtManager.connection import (
|
from vrtManager.connection import (
|
||||||
|
@ -10,18 +9,17 @@ from vrtManager.connection import (
|
||||||
|
|
||||||
|
|
||||||
class ComputeSerializer(serializers.ModelSerializer):
|
class ComputeSerializer(serializers.ModelSerializer):
|
||||||
# Use <input type="password"> for the input.
|
# Use <input type="password"> for the input.
|
||||||
password = serializers.CharField(style={'input_type': 'password'})
|
password = serializers.CharField(style={"input_type": "password"})
|
||||||
# Use a radio input instead of a select input.
|
# Use a radio input instead of a select input.
|
||||||
conn_types = (
|
conn_types = (
|
||||||
(CONN_SSH, 'SSH'),
|
(CONN_SSH, "SSH"),
|
||||||
(CONN_TCP, 'TCP'),
|
(CONN_TCP, "TCP"),
|
||||||
(CONN_TLS, 'TLS'),
|
(CONN_TLS, "TLS"),
|
||||||
(CONN_SOCKET, 'SOCK'),
|
(CONN_SOCKET, "SOCK"),
|
||||||
)
|
)
|
||||||
type = serializers.ChoiceField(choices=conn_types)
|
type = serializers.ChoiceField(choices=conn_types)
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Compute
|
model = Compute
|
||||||
fields = ['id', 'name', 'hostname', 'login', 'password', 'type', 'details']
|
fields = ["id", "name", "hostname", "login", "password", "type", "details"]
|
||||||
|
|
|
@ -11,13 +11,13 @@ class ComputeViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
API endpoint that allows computes to be viewed or edited.
|
API endpoint that allows computes to be viewed or edited.
|
||||||
"""
|
"""
|
||||||
queryset = Compute.objects.all().order_by('name')
|
|
||||||
|
queryset = Compute.objects.all().order_by("name")
|
||||||
serializer_class = ComputeSerializer
|
serializer_class = ComputeSerializer
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
class ComputeArchitecturesView(viewsets.ViewSet):
|
class ComputeArchitecturesView(viewsets.ViewSet):
|
||||||
|
|
||||||
def list(self, request, compute_pk=None):
|
def list(self, request, compute_pk=None):
|
||||||
"""
|
"""
|
||||||
Return a list of supported host architectures.
|
Return a list of supported host architectures.
|
||||||
|
@ -43,7 +43,6 @@ class ComputeArchitecturesView(viewsets.ViewSet):
|
||||||
|
|
||||||
|
|
||||||
class ComputeMachinesView(viewsets.ViewSet):
|
class ComputeMachinesView(viewsets.ViewSet):
|
||||||
|
|
||||||
def list(self, request, compute_pk=None, archs_pk=None):
|
def list(self, request, compute_pk=None, archs_pk=None):
|
||||||
"""
|
"""
|
||||||
Return a list of supported host architectures.
|
Return a list of supported host architectures.
|
||||||
|
|
|
@ -13,8 +13,8 @@ class TcpComputeForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Compute
|
model = Compute
|
||||||
widgets = {'password': forms.PasswordInput()}
|
widgets = {"password": forms.PasswordInput()}
|
||||||
fields = '__all__'
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class SshComputeForm(forms.ModelForm):
|
class SshComputeForm(forms.ModelForm):
|
||||||
|
@ -23,7 +23,7 @@ class SshComputeForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Compute
|
model = Compute
|
||||||
exclude = ['password']
|
exclude = ["password"]
|
||||||
|
|
||||||
|
|
||||||
class TlsComputeForm(forms.ModelForm):
|
class TlsComputeForm(forms.ModelForm):
|
||||||
|
@ -32,14 +32,14 @@ class TlsComputeForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Compute
|
model = Compute
|
||||||
widgets = {'password': forms.PasswordInput()}
|
widgets = {"password": forms.PasswordInput()}
|
||||||
fields = '__all__'
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class SocketComputeForm(forms.ModelForm):
|
class SocketComputeForm(forms.ModelForm):
|
||||||
hostname = forms.CharField(widget=forms.HiddenInput, initial='localhost')
|
hostname = forms.CharField(widget=forms.HiddenInput, initial="localhost")
|
||||||
type = forms.IntegerField(widget=forms.HiddenInput, initial=CONN_SOCKET)
|
type = forms.IntegerField(widget=forms.HiddenInput, initial=CONN_SOCKET)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Compute
|
model = Compute
|
||||||
fields = ['name', 'details', 'hostname', 'type']
|
fields = ["name", "details", "hostname", "type"]
|
||||||
|
|
|
@ -8,11 +8,11 @@ from vrtManager.hostdetails import wvmHostDetails
|
||||||
|
|
||||||
|
|
||||||
class Compute(Model):
|
class Compute(Model):
|
||||||
name = CharField(_('name'), max_length=64, unique=True)
|
name = CharField(_("name"), max_length=64, unique=True)
|
||||||
hostname = CharField(_('hostname'), max_length=64)
|
hostname = CharField(_("hostname"), max_length=64)
|
||||||
login = CharField(_('login'), max_length=20)
|
login = CharField(_("login"), max_length=20)
|
||||||
password = CharField(_('password'), max_length=14, blank=True, null=True)
|
password = CharField(_("password"), max_length=14, blank=True, null=True)
|
||||||
details = CharField(_('details'), max_length=64, null=True, blank=True)
|
details = CharField(_("details"), max_length=64, null=True, blank=True)
|
||||||
type = IntegerField()
|
type = IntegerField()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
@ -55,7 +55,7 @@ class Compute(Model):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def ram_usage(self):
|
def ram_usage(self):
|
||||||
return self.proxy.get_memory_usage()['percent']
|
return self.proxy.get_memory_usage()["percent"]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
|
@ -7,115 +7,121 @@ from .models import Compute
|
||||||
|
|
||||||
class ComputesTestCase(TestCase):
|
class ComputesTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client.login(username='admin', password='admin')
|
self.client.login(username="admin", password="admin")
|
||||||
Compute(
|
Compute(
|
||||||
name='local',
|
name="local",
|
||||||
hostname='localhost',
|
hostname="localhost",
|
||||||
login='',
|
login="",
|
||||||
password='',
|
password="",
|
||||||
details='local',
|
details="local",
|
||||||
type=4,
|
type=4,
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
def test_index(self):
|
def test_index(self):
|
||||||
response = self.client.get(reverse('computes'))
|
response = self.client.get(reverse("computes"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_create_update_delete(self):
|
def test_create_update_delete(self):
|
||||||
response = self.client.get(reverse('add_socket_host'))
|
response = self.client.get(reverse("add_socket_host"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse('add_socket_host'),
|
reverse("add_socket_host"),
|
||||||
{
|
{
|
||||||
'name': 'l1',
|
"name": "l1",
|
||||||
'details': 'Created',
|
"details": "Created",
|
||||||
'hostname': 'localhost',
|
"hostname": "localhost",
|
||||||
'type': 4,
|
"type": 4,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertRedirects(response, reverse('computes'))
|
self.assertRedirects(response, reverse("computes"))
|
||||||
|
|
||||||
compute = Compute.objects.get(pk=2)
|
compute = Compute.objects.get(pk=2)
|
||||||
self.assertEqual(compute.name, 'l1')
|
self.assertEqual(compute.name, "l1")
|
||||||
self.assertEqual(compute.details, 'Created')
|
self.assertEqual(compute.details, "Created")
|
||||||
|
|
||||||
response = self.client.get(reverse('compute_update', args=[2]))
|
response = self.client.get(reverse("compute_update", args=[2]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse('compute_update', args=[2]),
|
reverse("compute_update", args=[2]),
|
||||||
{
|
{
|
||||||
'name': 'l2',
|
"name": "l2",
|
||||||
'details': 'Updated',
|
"details": "Updated",
|
||||||
'hostname': 'localhost',
|
"hostname": "localhost",
|
||||||
'type': 4,
|
"type": 4,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertRedirects(response, reverse('computes'))
|
self.assertRedirects(response, reverse("computes"))
|
||||||
|
|
||||||
compute = Compute.objects.get(pk=2)
|
compute = Compute.objects.get(pk=2)
|
||||||
self.assertEqual(compute.name, 'l2')
|
self.assertEqual(compute.name, "l2")
|
||||||
self.assertEqual(compute.details, 'Updated')
|
self.assertEqual(compute.details, "Updated")
|
||||||
|
|
||||||
response = self.client.get(reverse('compute_delete', args=[2]))
|
response = self.client.get(reverse("compute_delete", args=[2]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.post(reverse('compute_delete', args=[2]))
|
response = self.client.post(reverse("compute_delete", args=[2]))
|
||||||
self.assertRedirects(response, reverse('computes'))
|
self.assertRedirects(response, reverse("computes"))
|
||||||
|
|
||||||
with self.assertRaises(ObjectDoesNotExist):
|
with self.assertRaises(ObjectDoesNotExist):
|
||||||
Compute.objects.get(id=2)
|
Compute.objects.get(id=2)
|
||||||
|
|
||||||
def test_overview(self):
|
def test_overview(self):
|
||||||
response = self.client.get(reverse('overview', args=[1]))
|
response = self.client.get(reverse("overview", args=[1]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_graph(self):
|
def test_graph(self):
|
||||||
response = self.client.get(reverse('compute_graph', args=[1]))
|
response = self.client.get(reverse("compute_graph", args=[1]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_instances(self):
|
def test_instances(self):
|
||||||
response = self.client.get(reverse('instances', args=[1]))
|
response = self.client.get(reverse("instances", args=[1]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_storages(self):
|
def test_storages(self):
|
||||||
response = self.client.get(reverse('storages', args=[1]))
|
response = self.client.get(reverse("storages", args=[1]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_storage(self):
|
def test_storage(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_default_storage_volumes(self):
|
def test_default_storage_volumes(self):
|
||||||
response = self.client.get(reverse('volumes', kwargs={'compute_id': 1, 'pool': 'default'}))
|
response = self.client.get(
|
||||||
|
reverse("volumes", kwargs={"compute_id": 1, "pool": "default"})
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_default_storage(self):
|
def test_default_storage(self):
|
||||||
response = self.client.get(reverse('storage', kwargs={'compute_id': 1, 'pool': 'default'}))
|
response = self.client.get(
|
||||||
|
reverse("storage", kwargs={"compute_id": 1, "pool": "default"})
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_networks(self):
|
def test_networks(self):
|
||||||
response = self.client.get(reverse('networks', args=[1]))
|
response = self.client.get(reverse("networks", args=[1]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_default_network(self):
|
def test_default_network(self):
|
||||||
response = self.client.get(reverse('network', kwargs={'compute_id': 1, 'pool': 'default'}))
|
response = self.client.get(
|
||||||
|
reverse("network", kwargs={"compute_id": 1, "pool": "default"})
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_interfaces(self):
|
def test_interfaces(self):
|
||||||
response = self.client.get(reverse('interfaces', args=[1]))
|
response = self.client.get(reverse("interfaces", args=[1]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
# TODO: add test for single interface
|
# TODO: add test for single interface
|
||||||
|
|
||||||
def test_nwfilters(self):
|
def test_nwfilters(self):
|
||||||
response = self.client.get(reverse('nwfilters', args=[1]))
|
response = self.client.get(reverse("nwfilters", args=[1]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
# TODO: add test for single nwfilter
|
# TODO: add test for single nwfilter
|
||||||
|
|
||||||
def test_secrets(self):
|
def test_secrets(self):
|
||||||
response = self.client.get(reverse('virtsecrets', args=[1]))
|
response = self.client.get(reverse("virtsecrets", args=[1]))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
# def test_create_instance_select_type(self):
|
# def test_create_instance_select_type(self):
|
||||||
|
@ -125,19 +131,29 @@ class ComputesTestCase(TestCase):
|
||||||
# TODO: create_instance
|
# TODO: create_instance
|
||||||
|
|
||||||
def test_machines(self):
|
def test_machines(self):
|
||||||
response = self.client.get(reverse('machines', kwargs={'compute_id': 1, 'arch': 'x86_64'}))
|
response = self.client.get(
|
||||||
|
reverse("machines", kwargs={"compute_id": 1, "arch": "x86_64"})
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_compute_disk_buses(self):
|
def test_compute_disk_buses(self):
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse('buses', kwargs={
|
reverse(
|
||||||
'compute_id': 1,
|
"buses",
|
||||||
'arch': 'x86_64',
|
kwargs={
|
||||||
'machine': 'pc',
|
"compute_id": 1,
|
||||||
'disk': 'disk',
|
"arch": "x86_64",
|
||||||
}))
|
"machine": "pc",
|
||||||
|
"disk": "disk",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_dom_capabilities(self):
|
def test_dom_capabilities(self):
|
||||||
response = self.client.get(reverse('domcaps', kwargs={'compute_id': 1, 'arch': 'x86_64', 'machine': 'pc'}))
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"domcaps", kwargs={"compute_id": 1, "arch": "x86_64", "machine": "pc"}
|
||||||
|
)
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
|
@ -9,36 +9,71 @@ from storages.views import create_volume, get_volumes, storage, storages
|
||||||
from . import forms, views
|
from . import forms, views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.computes, name='computes'),
|
path("", views.computes, name="computes"),
|
||||||
path('add_tcp_host/', views.compute_create, {'FormClass': forms.TcpComputeForm}, name='add_tcp_host'),
|
|
||||||
path('add_ssh_host/', views.compute_create, {'FormClass': forms.SshComputeForm}, name='add_ssh_host'),
|
|
||||||
path('add_tls_host/', views.compute_create, {'FormClass': forms.TlsComputeForm}, name='add_tls_host'),
|
|
||||||
path('add_socket_host/', views.compute_create, {'FormClass': forms.SocketComputeForm}, name='add_socket_host'),
|
|
||||||
path(
|
path(
|
||||||
'<int:compute_id>/',
|
"add_tcp_host/",
|
||||||
include([
|
views.compute_create,
|
||||||
path('', views.overview, name='overview'),
|
{"FormClass": forms.TcpComputeForm},
|
||||||
path('update/', views.compute_update, name='compute_update'),
|
name="add_tcp_host",
|
||||||
path('delete/', views.compute_delete, name='compute_delete'),
|
),
|
||||||
path('statistics', views.compute_graph, name='compute_graph'),
|
path(
|
||||||
path('instances/', views.instances, name='instances'),
|
"add_ssh_host/",
|
||||||
path('storages/', storages, name='storages'),
|
views.compute_create,
|
||||||
path('storage/<str:pool>/volumes/', get_volumes, name='volumes'),
|
{"FormClass": forms.SshComputeForm},
|
||||||
path('storage/<str:pool>/', storage, name='storage'),
|
name="add_ssh_host",
|
||||||
path('storage/<str:pool>/create_volume/', create_volume, name='create_volume'),
|
),
|
||||||
path('networks/', networks, name='networks'),
|
path(
|
||||||
path('network/<str:pool>/', network, name='network'),
|
"add_tls_host/",
|
||||||
path('interfaces/', interfaces, name='interfaces'),
|
views.compute_create,
|
||||||
path('interface/<str:iface>/', interface, name='interface'),
|
{"FormClass": forms.TlsComputeForm},
|
||||||
path('nwfilters/', nwfilters, name='nwfilters'),
|
name="add_tls_host",
|
||||||
path('nwfilter/<str:nwfltr>/', nwfilter, name='nwfilter'),
|
),
|
||||||
path('virtsecrets/', secrets, name='virtsecrets'),
|
path(
|
||||||
path('archs/<str:arch>/machines/', views.get_compute_machine_types, name='machines'),
|
"add_socket_host/",
|
||||||
path(
|
views.compute_create,
|
||||||
'archs/<str:arch>/machines/<str:machine>/disks/<str:disk>/buses/',
|
{"FormClass": forms.SocketComputeForm},
|
||||||
views.get_compute_disk_buses,
|
name="add_socket_host",
|
||||||
name='buses',
|
),
|
||||||
),
|
path(
|
||||||
path('archs/<str:arch>/machines/<str:machine>/capabilities/', views.get_dom_capabilities, name='domcaps'),
|
"<int:compute_id>/",
|
||||||
])),
|
include(
|
||||||
|
[
|
||||||
|
path("", views.overview, name="overview"),
|
||||||
|
path("update/", views.compute_update, name="compute_update"),
|
||||||
|
path("delete/", views.compute_delete, name="compute_delete"),
|
||||||
|
path("statistics", views.compute_graph, name="compute_graph"),
|
||||||
|
path("instances/", views.instances, name="instances"),
|
||||||
|
path("storages/", storages, name="storages"),
|
||||||
|
path("storage/<str:pool>/volumes/", get_volumes, name="volumes"),
|
||||||
|
path("storage/<str:pool>/", storage, name="storage"),
|
||||||
|
path(
|
||||||
|
"storage/<str:pool>/create_volume/",
|
||||||
|
create_volume,
|
||||||
|
name="create_volume",
|
||||||
|
),
|
||||||
|
path("networks/", networks, name="networks"),
|
||||||
|
path("network/<str:pool>/", network, name="network"),
|
||||||
|
path("interfaces/", interfaces, name="interfaces"),
|
||||||
|
path("interface/<str:iface>/", interface, name="interface"),
|
||||||
|
path("nwfilters/", nwfilters, name="nwfilters"),
|
||||||
|
path("nwfilter/<str:nwfltr>/", nwfilter, name="nwfilter"),
|
||||||
|
path("virtsecrets/", secrets, name="virtsecrets"),
|
||||||
|
path(
|
||||||
|
"archs/<str:arch>/machines/",
|
||||||
|
views.get_compute_machine_types,
|
||||||
|
name="machines",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"archs/<str:arch>/machines/<str:machine>/disks/<str:disk>/buses/",
|
||||||
|
views.get_compute_disk_buses,
|
||||||
|
name="buses",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"archs/<str:arch>/machines/<str:machine>/capabilities/",
|
||||||
|
views.get_dom_capabilities,
|
||||||
|
name="domcaps",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,7 +9,7 @@ def refresh_instance_database(compute):
|
||||||
Instance.objects.filter(compute=compute).exclude(name__in=domain_names).delete()
|
Instance.objects.filter(compute=compute).exclude(name__in=domain_names).delete()
|
||||||
Instance.objects.filter(compute=compute).exclude(uuid__in=domain_uuids).delete()
|
Instance.objects.filter(compute=compute).exclude(uuid__in=domain_uuids).delete()
|
||||||
# Create instances that're on host but not in DB
|
# Create instances that're on host but not in DB
|
||||||
names = Instance.objects.filter(compute=compute).values_list('name', flat=True)
|
names = Instance.objects.filter(compute=compute).values_list("name", flat=True)
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
if domain.name() not in names:
|
if domain.name() not in names:
|
||||||
Instance(compute=compute, name=domain.name(), uuid=domain.UUIDString()).save()
|
Instance( compute=compute, name=domain.name(), uuid=domain.UUIDString()).save()
|
||||||
|
|
|
@ -3,9 +3,9 @@ import re
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
have_symbol = re.compile('[^a-zA-Z0-9._-]+')
|
have_symbol = re.compile("[^a-zA-Z0-9._-]+")
|
||||||
wrong_ip = re.compile('^0.|^255.')
|
wrong_ip = re.compile("^0.|^255.")
|
||||||
wrong_name = re.compile('[^a-zA-Z0-9._-]+')
|
wrong_name = re.compile("[^a-zA-Z0-9._-]+")
|
||||||
|
|
||||||
|
|
||||||
def validate_hostname(value):
|
def validate_hostname(value):
|
||||||
|
@ -13,12 +13,14 @@ def validate_hostname(value):
|
||||||
wip = wrong_ip.match(value)
|
wip = wrong_ip.match(value)
|
||||||
|
|
||||||
if sym:
|
if sym:
|
||||||
raise ValidationError(_('Hostname must contain only numbers, or the domain name separated by "."'))
|
raise ValidationError(
|
||||||
|
_('Hostname must contain only numbers, or the domain name separated by "."')
|
||||||
|
)
|
||||||
elif wip:
|
elif wip:
|
||||||
raise ValidationError(_('Wrong IP address'))
|
raise ValidationError(_("Wrong IP address"))
|
||||||
|
|
||||||
|
|
||||||
def validate_name(value):
|
def validate_name(value):
|
||||||
have_symbol = wrong_name.match('[^a-zA-Z0-9._-]+')
|
have_symbol = wrong_name.match("[^a-zA-Z0-9._-]+")
|
||||||
if have_symbol:
|
if have_symbol:
|
||||||
raise ValidationError(_('The hostname must not contain any special characters'))
|
raise ValidationError(_("The hostname must not contain any special characters"))
|
||||||
|
|
|
@ -7,7 +7,12 @@ from django.utils import timezone
|
||||||
from libvirt import libvirtError
|
from libvirt import libvirtError
|
||||||
|
|
||||||
from admin.decorators import superuser_only
|
from admin.decorators import superuser_only
|
||||||
from computes.forms import SocketComputeForm, SshComputeForm, TcpComputeForm, TlsComputeForm
|
from computes.forms import (
|
||||||
|
SocketComputeForm,
|
||||||
|
SshComputeForm,
|
||||||
|
TcpComputeForm,
|
||||||
|
TlsComputeForm,
|
||||||
|
)
|
||||||
from computes.models import Compute
|
from computes.models import Compute
|
||||||
from instances.models import Instance
|
from instances.models import Instance
|
||||||
from vrtManager.connection import (
|
from vrtManager.connection import (
|
||||||
|
@ -39,7 +44,8 @@ def computes(request):
|
||||||
def overview(request, compute_id):
|
def overview(request, compute_id):
|
||||||
compute = get_object_or_404(Compute, pk=compute_id)
|
compute = get_object_or_404(Compute, pk=compute_id)
|
||||||
status = (
|
status = (
|
||||||
"true" if connection_manager.host_is_up(compute.type, compute.hostname) is True else "false"
|
"true"
|
||||||
|
if connection_manager.host_is_up(compute.type, compute.hostname) is True else "false"
|
||||||
)
|
)
|
||||||
|
|
||||||
conn = wvmHostDetails(
|
conn = wvmHostDetails(
|
||||||
|
@ -48,7 +54,14 @@ def overview(request, compute_id):
|
||||||
compute.password,
|
compute.password,
|
||||||
compute.type,
|
compute.type,
|
||||||
)
|
)
|
||||||
hostname, host_arch, host_memory, logical_cpu, model_cpu, uri_conn = conn.get_node_info()
|
(
|
||||||
|
hostname,
|
||||||
|
host_arch,
|
||||||
|
host_memory,
|
||||||
|
logical_cpu,
|
||||||
|
model_cpu,
|
||||||
|
uri_conn,
|
||||||
|
) = conn.get_node_info()
|
||||||
hypervisor = conn.get_hypervisors_domain_types()
|
hypervisor = conn.get_hypervisors_domain_types()
|
||||||
mem_usage = conn.get_memory_usage()
|
mem_usage = conn.get_memory_usage()
|
||||||
emulator = conn.get_emulator(host_arch)
|
emulator = conn.get_emulator(host_arch)
|
||||||
|
@ -64,9 +77,15 @@ def instances(request, compute_id):
|
||||||
compute = get_object_or_404(Compute, pk=compute_id)
|
compute = get_object_or_404(Compute, pk=compute_id)
|
||||||
|
|
||||||
utils.refresh_instance_database(compute)
|
utils.refresh_instance_database(compute)
|
||||||
instances = Instance.objects.filter(compute=compute).prefetch_related("userinstance_set")
|
instances = Instance.objects.filter(compute=compute).prefetch_related(
|
||||||
|
"userinstance_set"
|
||||||
|
)
|
||||||
|
|
||||||
return render(request, "computes/instances.html", {"compute": compute, "instances": instances})
|
return render(
|
||||||
|
request,
|
||||||
|
"computes/instances.html",
|
||||||
|
{"compute": compute, "instances": instances}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@superuser_only
|
@superuser_only
|
||||||
|
|
240
console/novncd
240
console/novncd
|
@ -6,9 +6,9 @@ import logging
|
||||||
import django
|
import django
|
||||||
|
|
||||||
DIR_PATH = os.path.dirname(os.path.abspath(__file__))
|
DIR_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||||
ROOT_PATH = os.path.abspath(os.path.join(DIR_PATH, '..', ''))
|
ROOT_PATH = os.path.abspath(os.path.join(DIR_PATH, "..", ""))
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webvirtcloud.settings')
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webvirtcloud.settings")
|
||||||
CERT = DIR_PATH + '/cert.pem'
|
CERT = DIR_PATH + "/cert.pem"
|
||||||
|
|
||||||
if ROOT_PATH not in sys.path:
|
if ROOT_PATH not in sys.path:
|
||||||
sys.path.append(ROOT_PATH)
|
sys.path.append(ROOT_PATH)
|
||||||
|
@ -17,7 +17,8 @@ django.setup()
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
#from six.moves import http_cookies as Cookie
|
|
||||||
|
# from six.moves import http_cookies as Cookie
|
||||||
from http import cookies as Cookie
|
from http import cookies as Cookie
|
||||||
from webvirtcloud.settings import WS_PORT, WS_HOST, WS_CERT
|
from webvirtcloud.settings import WS_PORT, WS_HOST, WS_CERT
|
||||||
from vrtManager.connection import CONN_SSH, CONN_SOCKET
|
from vrtManager.connection import CONN_SSH, CONN_SOCKET
|
||||||
|
@ -26,40 +27,40 @@ from optparse import OptionParser
|
||||||
|
|
||||||
parser = OptionParser()
|
parser = OptionParser()
|
||||||
|
|
||||||
parser.add_option("-v",
|
parser.add_option(
|
||||||
"--verbose",
|
"-v",
|
||||||
dest="verbose",
|
"--verbose",
|
||||||
action="store_true",
|
dest="verbose",
|
||||||
help="Verbose mode",
|
action="store_true",
|
||||||
default=False)
|
help="Verbose mode",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_option("-d",
|
parser.add_option(
|
||||||
"--debug",
|
"-d", "--debug", dest="debug", action="store_true", help="Debug mode", default=False
|
||||||
dest="debug",
|
)
|
||||||
action="store_true",
|
|
||||||
help="Debug mode",
|
|
||||||
default=False)
|
|
||||||
|
|
||||||
parser.add_option("-H",
|
parser.add_option(
|
||||||
"--host",
|
"-H", "--host", dest="host", action="store", help="Listen host", default=WS_HOST
|
||||||
dest="host",
|
)
|
||||||
action="store",
|
|
||||||
help="Listen host",
|
|
||||||
default=WS_HOST)
|
|
||||||
|
|
||||||
parser.add_option("-p",
|
parser.add_option(
|
||||||
"--port",
|
"-p",
|
||||||
dest="port",
|
"--port",
|
||||||
action="store",
|
dest="port",
|
||||||
help="Listen port",
|
action="store",
|
||||||
default=WS_PORT or 6080)
|
help="Listen port",
|
||||||
|
default=WS_PORT or 6080,
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_option("-c",
|
parser.add_option(
|
||||||
"--cert",
|
"-c",
|
||||||
dest="cert",
|
"--cert",
|
||||||
action="store",
|
dest="cert",
|
||||||
help="Certificate file path",
|
action="store",
|
||||||
default=WS_CERT or CERT)
|
help="Certificate file path",
|
||||||
|
default=WS_CERT or CERT,
|
||||||
|
)
|
||||||
|
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
|
@ -74,6 +75,7 @@ else:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from websockify import WebSocketProxy
|
from websockify import WebSocketProxy
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from websockify import ProxyRequestHandler
|
from websockify import ProxyRequestHandler
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -84,7 +86,7 @@ except ImportError:
|
||||||
try:
|
try:
|
||||||
from novnc.wsproxy import WebSocketProxy
|
from novnc.wsproxy import WebSocketProxy
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print('Unable to import a websockify implementation,\n please install one')
|
print("Unable to import a websockify implementation,\n please install one")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
USE_HANDLER = False
|
USE_HANDLER = False
|
||||||
|
@ -95,18 +97,20 @@ def get_connection_infos(token):
|
||||||
from vrtManager.instance import wvmInstance
|
from vrtManager.instance import wvmInstance
|
||||||
|
|
||||||
try:
|
try:
|
||||||
temptoken = token.split('-', 1)
|
temptoken = token.split("-", 1)
|
||||||
host = int(temptoken[0])
|
host = int(temptoken[0])
|
||||||
uuid = temptoken[1]
|
uuid = temptoken[1]
|
||||||
instance = Instance.objects.get(compute_id=host, uuid=uuid)
|
instance = Instance.objects.get(compute_id=host, uuid=uuid)
|
||||||
conn = wvmInstance(instance.compute.hostname,
|
conn = wvmInstance(
|
||||||
instance.compute.login,
|
instance.compute.hostname,
|
||||||
instance.compute.password,
|
instance.compute.login,
|
||||||
instance.compute.type,
|
instance.compute.password,
|
||||||
instance.name)
|
instance.compute.type,
|
||||||
if instance.compute.hostname.count(':'):
|
instance.name,
|
||||||
connhost = instance.compute.hostname.split(':')[0]
|
)
|
||||||
connport = instance.compute.hostname.split(':')[1]
|
if instance.compute.hostname.count(":"):
|
||||||
|
connhost = instance.compute.hostname.split(":")[0]
|
||||||
|
connport = instance.compute.hostname.split(":")[1]
|
||||||
else:
|
else:
|
||||||
connhost = instance.compute.hostname
|
connhost = instance.compute.hostname
|
||||||
connport = 22
|
connport = 22
|
||||||
|
@ -117,10 +121,18 @@ def get_connection_infos(token):
|
||||||
console_socket = conn.get_console_socket()
|
console_socket = conn.get_console_socket()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(
|
logging.error(
|
||||||
'Fail to retrieve console connection infos for token %s : %s' % (token, e))
|
"Fail to retrieve console connection infos for token %s : %s" % (token, e)
|
||||||
|
)
|
||||||
raise
|
raise
|
||||||
return (connhost, connport, connuser, conntype, console_host,
|
return (
|
||||||
console_port, console_socket)
|
connhost,
|
||||||
|
connport,
|
||||||
|
connuser,
|
||||||
|
conntype,
|
||||||
|
console_host,
|
||||||
|
console_port,
|
||||||
|
console_socket,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CompatibilityMixIn(object):
|
class CompatibilityMixIn(object):
|
||||||
|
@ -128,25 +140,31 @@ class CompatibilityMixIn(object):
|
||||||
# NoVNC uses it's own convention that forward token
|
# NoVNC uses it's own convention that forward token
|
||||||
# from the request to a cookie header, we should check
|
# from the request to a cookie header, we should check
|
||||||
# also for this behavior
|
# also for this behavior
|
||||||
hcookie = self.headers.get('cookie')
|
hcookie = self.headers.get("cookie")
|
||||||
|
|
||||||
if hcookie:
|
if hcookie:
|
||||||
cookie = Cookie.SimpleCookie()
|
cookie = Cookie.SimpleCookie()
|
||||||
for hcookie_part in hcookie.split(';'):
|
for hcookie_part in hcookie.split(";"):
|
||||||
hcookie_part = hcookie_part.lstrip()
|
hcookie_part = hcookie_part.lstrip()
|
||||||
try:
|
try:
|
||||||
cookie.load(hcookie_part)
|
cookie.load(hcookie_part)
|
||||||
except Cookie.CookieError:
|
except Cookie.CookieError:
|
||||||
# NOTE(stgleb): Do not print out cookie content
|
# NOTE(stgleb): Do not print out cookie content
|
||||||
# for security reasons.
|
# for security reasons.
|
||||||
self.msg('Found malformed cookie')
|
self.msg("Found malformed cookie")
|
||||||
else:
|
else:
|
||||||
if 'token' in cookie:
|
if "token" in cookie:
|
||||||
token = cookie['token'].value
|
token = cookie["token"].value
|
||||||
|
|
||||||
|
(
|
||||||
(connhost, connport, connuser, conntype, console_host, console_port,
|
connhost,
|
||||||
console_socket) = get_connection_infos(token)
|
connport,
|
||||||
|
connuser,
|
||||||
|
conntype,
|
||||||
|
console_host,
|
||||||
|
console_port,
|
||||||
|
console_socket,
|
||||||
|
) = get_connection_infos(token)
|
||||||
|
|
||||||
cnx_debug_msg = "Connection infos :\n"
|
cnx_debug_msg = "Connection infos :\n"
|
||||||
cnx_debug_msg += "- connhost : '%s'\n" % connhost
|
cnx_debug_msg += "- connhost : '%s'\n" % connhost
|
||||||
|
@ -160,14 +178,16 @@ class CompatibilityMixIn(object):
|
||||||
|
|
||||||
if console_socket and conntype == CONN_SOCKET:
|
if console_socket and conntype == CONN_SOCKET:
|
||||||
# Local socket on local host
|
# Local socket on local host
|
||||||
self.msg('Try to open local socket %s' % console_socket)
|
self.msg("Try to open local socket %s" % console_socket)
|
||||||
tsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
tsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
tsock.connect(console_socket)
|
tsock.connect(console_socket)
|
||||||
elif console_socket or re.match('^127\.', console_host):
|
elif console_socket or re.match("^127\.", console_host):
|
||||||
# Need tunnel to physical host
|
# Need tunnel to physical host
|
||||||
if conntype != CONN_SSH:
|
if conntype != CONN_SSH:
|
||||||
self.msg("Need a tunnel to access console but can't mount " +
|
self.msg(
|
||||||
"one because it's not a SSH host")
|
"Need a tunnel to access console but can't mount "
|
||||||
|
+ "one because it's not a SSH host"
|
||||||
|
)
|
||||||
raise Exception(self.msg)
|
raise Exception(self.msg)
|
||||||
try:
|
try:
|
||||||
# generate a string with all placeholders to avoid TypeErrors
|
# generate a string with all placeholders to avoid TypeErrors
|
||||||
|
@ -175,10 +195,25 @@ class CompatibilityMixIn(object):
|
||||||
# https://github.com/retspen/webvirtmgr/pull/497
|
# https://github.com/retspen/webvirtmgr/pull/497
|
||||||
error_msg = "Try to open tunnel on %s@%s:%s on console %s:%s "
|
error_msg = "Try to open tunnel on %s@%s:%s on console %s:%s "
|
||||||
error_msg += "(or socket %s)"
|
error_msg += "(or socket %s)"
|
||||||
self.msg(error_msg % (connuser, connhost, connport,
|
self.msg(
|
||||||
console_host, console_port, console_socket))
|
error_msg
|
||||||
tunnel = SSHTunnels(connhost, connuser, connport,
|
% (
|
||||||
console_host, console_port, console_socket)
|
connuser,
|
||||||
|
connhost,
|
||||||
|
connport,
|
||||||
|
console_host,
|
||||||
|
console_port,
|
||||||
|
console_socket,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
tunnel = SSHTunnels(
|
||||||
|
connhost,
|
||||||
|
connuser,
|
||||||
|
connport,
|
||||||
|
console_host,
|
||||||
|
console_port,
|
||||||
|
console_socket,
|
||||||
|
)
|
||||||
fd = tunnel.open_new()
|
fd = tunnel.open_new()
|
||||||
tunnel.unlock()
|
tunnel.unlock()
|
||||||
tsock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
|
tsock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
@ -202,15 +237,18 @@ class CompatibilityMixIn(object):
|
||||||
except Exception:
|
except Exception:
|
||||||
if tunnel:
|
if tunnel:
|
||||||
self.vmsg(
|
self.vmsg(
|
||||||
"%s:%s (via %s@%s:%s) : Websocket client or Target closed" %
|
"%s:%s (via %s@%s:%s) : Websocket client or Target closed"
|
||||||
(console_host, console_port, connuser, connhost, connport))
|
% (console_host, console_port, connuser, connhost, connport)
|
||||||
|
)
|
||||||
if tsock:
|
if tsock:
|
||||||
tsock.shutdown(socket.SHUT_RDWR)
|
tsock.shutdown(socket.SHUT_RDWR)
|
||||||
tsock.close()
|
tsock.close()
|
||||||
tunnel.close_all()
|
tunnel.close_all()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
if USE_HANDLER:
|
if USE_HANDLER:
|
||||||
|
|
||||||
class NovaProxyRequestHandler(ProxyRequestHandler, CompatibilityMixIn):
|
class NovaProxyRequestHandler(ProxyRequestHandler, CompatibilityMixIn):
|
||||||
def msg(self, *args, **kwargs):
|
def msg(self, *args, **kwargs):
|
||||||
self.log_message(*args, **kwargs)
|
self.log_message(*args, **kwargs)
|
||||||
|
@ -228,9 +266,10 @@ if USE_HANDLER:
|
||||||
socket_factory = self.server.socket
|
socket_factory = self.server.socket
|
||||||
|
|
||||||
self._new_client(daemon, socket_factory)
|
self._new_client(daemon, socket_factory)
|
||||||
else:
|
|
||||||
class NovaWebSocketProxy(WebSocketProxy, CompatibilityMixIn):
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
class NovaWebSocketProxy(WebSocketProxy, CompatibilityMixIn):
|
||||||
def new_client(self):
|
def new_client(self):
|
||||||
"""
|
"""
|
||||||
Called after a new WebSocket connection has been established.
|
Called after a new WebSocket connection has been established.
|
||||||
|
@ -241,39 +280,44 @@ else:
|
||||||
|
|
||||||
self._new_client(daemon, socket_factory)
|
self._new_client(daemon, socket_factory)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
if USE_HANDLER:
|
if USE_HANDLER:
|
||||||
# Create the WebSocketProxy with NovaProxyRequestHandler handler
|
# Create the WebSocketProxy with NovaProxyRequestHandler handler
|
||||||
server = WebSocketProxy(RequestHandlerClass=NovaProxyRequestHandler,
|
server = WebSocketProxy(
|
||||||
listen_host=options.host,
|
RequestHandlerClass=NovaProxyRequestHandler,
|
||||||
listen_port=options.port,
|
listen_host=options.host,
|
||||||
source_is_ipv6=False,
|
listen_port=options.port,
|
||||||
verbose=options.verbose,
|
source_is_ipv6=False,
|
||||||
cert=options.cert,
|
verbose=options.verbose,
|
||||||
key=None,
|
cert=options.cert,
|
||||||
ssl_only=False,
|
key=None,
|
||||||
daemon=False,
|
ssl_only=False,
|
||||||
record=False,
|
daemon=False,
|
||||||
web=False,
|
record=False,
|
||||||
traffic=False,
|
web=False,
|
||||||
target_host='ignore',
|
traffic=False,
|
||||||
target_port='ignore',
|
target_host="ignore",
|
||||||
wrap_mode='exit',
|
target_port="ignore",
|
||||||
wrap_cmd=None)
|
wrap_mode="exit",
|
||||||
|
wrap_cmd=None,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Create the NovaWebSockets proxy
|
# Create the NovaWebSockets proxy
|
||||||
server = NovaWebSocketProxy(listen_host=options.host,
|
server = NovaWebSocketProxy(
|
||||||
listen_port=options.port,
|
listen_host=options.host,
|
||||||
source_is_ipv6=False,
|
listen_port=options.port,
|
||||||
verbose=options.verbose,
|
source_is_ipv6=False,
|
||||||
cert=options.cert,
|
verbose=options.verbose,
|
||||||
key=None,
|
cert=options.cert,
|
||||||
ssl_only=False,
|
key=None,
|
||||||
daemon=False,
|
ssl_only=False,
|
||||||
record=False,
|
daemon=False,
|
||||||
web=False,
|
record=False,
|
||||||
target_host='ignore',
|
web=False,
|
||||||
target_port='ignore',
|
target_host="ignore",
|
||||||
wrap_mode='exit',
|
target_port="ignore",
|
||||||
wrap_cmd=None)
|
wrap_mode="exit",
|
||||||
|
wrap_cmd=None,
|
||||||
|
)
|
||||||
server.start_server()
|
server.start_server()
|
||||||
|
|
|
@ -5,9 +5,9 @@ import logging
|
||||||
import django
|
import django
|
||||||
|
|
||||||
DIR_PATH = os.path.dirname(os.path.abspath(__file__))
|
DIR_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||||
ROOT_PATH = os.path.abspath(os.path.join(DIR_PATH, '..', ''))
|
ROOT_PATH = os.path.abspath(os.path.join(DIR_PATH, "..", ""))
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webvirtcloud.settings')
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webvirtcloud.settings")
|
||||||
CERT = DIR_PATH + '/cert.pem'
|
CERT = DIR_PATH + "/cert.pem"
|
||||||
|
|
||||||
if ROOT_PATH not in sys.path:
|
if ROOT_PATH not in sys.path:
|
||||||
sys.path.append(ROOT_PATH)
|
sys.path.append(ROOT_PATH)
|
||||||
|
@ -32,7 +32,7 @@ import tty
|
||||||
import termios
|
import termios
|
||||||
import libvirt
|
import libvirt
|
||||||
|
|
||||||
#from six.moves import http_cookies as Cookie
|
# from six.moves import http_cookies as Cookie
|
||||||
from http import cookies as Cookie
|
from http import cookies as Cookie
|
||||||
from webvirtcloud.settings import SOCKETIO_PORT, SOCKETIO_HOST
|
from webvirtcloud.settings import SOCKETIO_PORT, SOCKETIO_HOST
|
||||||
from vrtManager.connection import CONN_SSH, CONN_SOCKET
|
from vrtManager.connection import CONN_SSH, CONN_SOCKET
|
||||||
|
@ -40,33 +40,36 @@ from optparse import OptionParser
|
||||||
|
|
||||||
parser = OptionParser()
|
parser = OptionParser()
|
||||||
|
|
||||||
parser.add_option("-v",
|
parser.add_option(
|
||||||
"--verbose",
|
"-v",
|
||||||
dest="verbose",
|
"--verbose",
|
||||||
action="store_true",
|
dest="verbose",
|
||||||
help="Verbose mode",
|
action="store_true",
|
||||||
default=False)
|
help="Verbose mode",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_option("-d",
|
parser.add_option(
|
||||||
"--debug",
|
"-d", "--debug", dest="debug", action="store_true", help="Debug mode", default=False
|
||||||
dest="debug",
|
)
|
||||||
action="store_true",
|
|
||||||
help="Debug mode",
|
|
||||||
default=False)
|
|
||||||
|
|
||||||
parser.add_option("-H",
|
parser.add_option(
|
||||||
"--host",
|
"-H",
|
||||||
dest="host",
|
"--host",
|
||||||
action="store",
|
dest="host",
|
||||||
help="Listen host",
|
action="store",
|
||||||
default=SOCKETIO_HOST)
|
help="Listen host",
|
||||||
|
default=SOCKETIO_HOST,
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_option("-p",
|
parser.add_option(
|
||||||
"--port",
|
"-p",
|
||||||
dest="port",
|
"--port",
|
||||||
action="store",
|
dest="port",
|
||||||
help="Listen port",
|
action="store",
|
||||||
default=SOCKETIO_PORT or 6081)
|
help="Listen port",
|
||||||
|
default=SOCKETIO_PORT or 6081,
|
||||||
|
)
|
||||||
|
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
|
@ -85,26 +88,31 @@ sio = socketio.Server(async_mode=async_mode, cors_allowed_origins=[])
|
||||||
fd = None
|
fd = None
|
||||||
child_pid = None
|
child_pid = None
|
||||||
|
|
||||||
|
|
||||||
def get_connection_infos(token):
|
def get_connection_infos(token):
|
||||||
from instances.models import Instance
|
from instances.models import Instance
|
||||||
from vrtManager.instance import wvmInstance
|
from vrtManager.instance import wvmInstance
|
||||||
|
|
||||||
try:
|
try:
|
||||||
temptoken = token.split('-', 1)
|
temptoken = token.split("-", 1)
|
||||||
host = int(temptoken[0])
|
host = int(temptoken[0])
|
||||||
uuid = temptoken[1]
|
uuid = temptoken[1]
|
||||||
instance = Instance.objects.get(compute_id=host, uuid=uuid)
|
instance = Instance.objects.get(compute_id=host, uuid=uuid)
|
||||||
conn = wvmInstance(instance.compute.hostname,
|
conn = wvmInstance(
|
||||||
instance.compute.login,
|
instance.compute.hostname,
|
||||||
instance.compute.password,
|
instance.compute.login,
|
||||||
instance.compute.type,
|
instance.compute.password,
|
||||||
instance.name)
|
instance.compute.type,
|
||||||
|
instance.name,
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(
|
logging.error(
|
||||||
'Fail to retrieve console connection infos for token %s : %s' % (token, e))
|
"Fail to retrieve console connection infos for token %s : %s" % (token, e)
|
||||||
|
)
|
||||||
raise
|
raise
|
||||||
return (instance, conn)
|
return (instance, conn)
|
||||||
|
|
||||||
|
|
||||||
def set_winsize(fd, row, col, xpix=0, ypix=0):
|
def set_winsize(fd, row, col, xpix=0, ypix=0):
|
||||||
winsize = struct.pack("HHHH", row, col, xpix, ypix)
|
winsize = struct.pack("HHHH", row, col, xpix, ypix)
|
||||||
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
|
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
|
||||||
|
@ -123,41 +131,44 @@ def read_and_forward_pty_output():
|
||||||
sio.emit("pty_output", {"output": output})
|
sio.emit("pty_output", {"output": output})
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@sio.event
|
@sio.event
|
||||||
def resize(sid, message):
|
def resize(sid, message):
|
||||||
global fd
|
global fd
|
||||||
if fd:
|
if fd:
|
||||||
set_winsize(fd, message["rows"], message["cols"])
|
set_winsize(fd, message["rows"], message["cols"])
|
||||||
|
|
||||||
|
|
||||||
@sio.event
|
@sio.event
|
||||||
def pty_input(sid, message):
|
def pty_input(sid, message):
|
||||||
global fd
|
global fd
|
||||||
if fd:
|
if fd:
|
||||||
os.write(fd, message["input"].encode())
|
os.write(fd, message["input"].encode())
|
||||||
|
|
||||||
|
|
||||||
@sio.event
|
@sio.event
|
||||||
def disconnect_request(sid):
|
def disconnect_request(sid):
|
||||||
sio.disconnect(sid)
|
sio.disconnect(sid)
|
||||||
|
|
||||||
|
|
||||||
@sio.event
|
@sio.event
|
||||||
def connect(sid, environ):
|
def connect(sid, environ):
|
||||||
global fd
|
global fd
|
||||||
global child_pid
|
global child_pid
|
||||||
|
|
||||||
hcookie = environ.get('HTTP_COOKIE')
|
hcookie = environ.get("HTTP_COOKIE")
|
||||||
if hcookie:
|
if hcookie:
|
||||||
cookie = Cookie.SimpleCookie()
|
cookie = Cookie.SimpleCookie()
|
||||||
for hcookie_part in hcookie.split(';'):
|
for hcookie_part in hcookie.split(";"):
|
||||||
hcookie_part = hcookie_part.lstrip()
|
hcookie_part = hcookie_part.lstrip()
|
||||||
try:
|
try:
|
||||||
cookie.load(hcookie_part)
|
cookie.load(hcookie_part)
|
||||||
except Cookie.CookieError:
|
except Cookie.CookieError:
|
||||||
logging.warn('Found malformed cookie')
|
logging.warn("Found malformed cookie")
|
||||||
else:
|
else:
|
||||||
if 'token' in cookie:
|
if "token" in cookie:
|
||||||
token = cookie['token'].value
|
token = cookie["token"].value
|
||||||
|
|
||||||
if child_pid:
|
if child_pid:
|
||||||
# already started child process, don't start another
|
# already started child process, don't start another
|
||||||
|
@ -170,14 +181,15 @@ def connect(sid, environ):
|
||||||
|
|
||||||
if child_pid == 0:
|
if child_pid == 0:
|
||||||
(instance, conn) = get_connection_infos(token)
|
(instance, conn) = get_connection_infos(token)
|
||||||
uuid = conn.get_uuid()
|
uuid = conn.get_uuid()
|
||||||
uri = conn.wvm.getURI()
|
uri = conn.wvm.getURI()
|
||||||
|
|
||||||
subprocess.run(['conf/daemon/consolecallback', uri, uuid])
|
subprocess.run(["conf/daemon/consolecallback", uri, uuid])
|
||||||
else:
|
else:
|
||||||
# this is the parent process fork.
|
# this is the parent process fork.
|
||||||
sio.start_background_task(target=read_and_forward_pty_output)
|
sio.start_background_task(target=read_and_forward_pty_output)
|
||||||
|
|
||||||
|
|
||||||
@sio.event
|
@sio.event
|
||||||
def disconnect(sid):
|
def disconnect(sid):
|
||||||
|
|
||||||
|
@ -185,13 +197,15 @@ def disconnect(sid):
|
||||||
global child_pid
|
global child_pid
|
||||||
|
|
||||||
# kill pty process
|
# kill pty process
|
||||||
os.kill(child_pid,signal.SIGKILL)
|
os.kill(child_pid, signal.SIGKILL)
|
||||||
os.wait()
|
os.wait()
|
||||||
|
|
||||||
# reset the variables
|
# reset the variables
|
||||||
fd = None
|
fd = None
|
||||||
child_pid = None
|
child_pid = None
|
||||||
|
|
||||||
|
|
||||||
app = socketio.WSGIApp(sio)
|
app = socketio.WSGIApp(sio)
|
||||||
import eventlet
|
import eventlet
|
||||||
eventlet.wsgi.server(eventlet.listen((options.host,int(options.port))), app)
|
|
||||||
|
eventlet.wsgi.server(eventlet.listen((options.host, int(options.port))), app)
|
||||||
|
|
|
@ -28,15 +28,19 @@ class _TunnelScheduler(object):
|
||||||
|
|
||||||
def _handle_queue(self):
|
def _handle_queue(self):
|
||||||
while True:
|
while True:
|
||||||
lock_cb, cb, args, = self._queue.get()
|
(
|
||||||
|
lock_cb,
|
||||||
|
cb,
|
||||||
|
args,
|
||||||
|
) = self._queue.get()
|
||||||
lock_cb()
|
lock_cb()
|
||||||
cb(*args)
|
cb(*args)
|
||||||
|
|
||||||
def schedule(self, lock_cb, cb, *args):
|
def schedule(self, lock_cb, cb, *args):
|
||||||
if not self._thread:
|
if not self._thread:
|
||||||
self._thread = threading.Thread(name="Tunnel thread",
|
self._thread = threading.Thread(
|
||||||
target=self._handle_queue,
|
name="Tunnel thread", target=self._handle_queue, args=()
|
||||||
args=())
|
)
|
||||||
self._thread.daemon = True
|
self._thread.daemon = True
|
||||||
if not self._thread.is_alive():
|
if not self._thread.is_alive():
|
||||||
self._thread.start()
|
self._thread.start()
|
||||||
|
@ -63,8 +67,11 @@ class _Tunnel(object):
|
||||||
return
|
return
|
||||||
self._closed = True
|
self._closed = True
|
||||||
|
|
||||||
log.debug("Close tunnel PID=%s ERRFD=%s",
|
log.debug(
|
||||||
self._pid, self._errfd and self._errfd.fileno() or None)
|
"Close tunnel PID=%s ERRFD=%s",
|
||||||
|
self._pid,
|
||||||
|
self._errfd and self._errfd.fileno() or None,
|
||||||
|
)
|
||||||
|
|
||||||
# Since this is a socket object, the file descriptor is closed
|
# Since this is a socket object, the file descriptor is closed
|
||||||
# when it's garbage collected.
|
# when it's garbage collected.
|
||||||
|
@ -110,8 +117,7 @@ class _Tunnel(object):
|
||||||
|
|
||||||
self._errfd = errfds[0]
|
self._errfd = errfds[0]
|
||||||
self._errfd.setblocking(0)
|
self._errfd.setblocking(0)
|
||||||
log.debug("Opened tunnel PID=%d ERRFD=%d",
|
log.debug("Opened tunnel PID=%d ERRFD=%d", pid, self._errfd.fileno())
|
||||||
pid, self._errfd.fileno())
|
|
||||||
|
|
||||||
self._pid = pid
|
self._pid = pid
|
||||||
|
|
||||||
|
@ -124,7 +130,7 @@ def _make_ssh_command(connhost, connuser, connport, gaddr, gport, gsocket):
|
||||||
argv += ["-p", str(connport)]
|
argv += ["-p", str(connport)]
|
||||||
|
|
||||||
if connuser:
|
if connuser:
|
||||||
argv += ['-l', connuser]
|
argv += ["-l", connuser]
|
||||||
|
|
||||||
argv += [connhost]
|
argv += [connhost]
|
||||||
|
|
||||||
|
@ -151,8 +157,8 @@ def _make_ssh_command(connhost, connuser, connport, gaddr, gport, gsocket):
|
||||||
"""else"""
|
"""else"""
|
||||||
""" CMD="nc %(nc_params)s";"""
|
""" CMD="nc %(nc_params)s";"""
|
||||||
"""fi;"""
|
"""fi;"""
|
||||||
"""eval "$CMD";""" %
|
"""eval "$CMD";""" % {"nc_params": nc_params}
|
||||||
{'nc_params': nc_params})
|
)
|
||||||
|
|
||||||
argv.append("sh -c")
|
argv.append("sh -c")
|
||||||
argv.append("'%s'" % nc_cmd)
|
argv.append("'%s'" % nc_cmd)
|
||||||
|
@ -166,7 +172,8 @@ class SSHTunnels(object):
|
||||||
def __init__(self, connhost, connuser, connport, gaddr, gport, gsocket):
|
def __init__(self, connhost, connuser, connport, gaddr, gport, gsocket):
|
||||||
self._tunnels = []
|
self._tunnels = []
|
||||||
self._sshcommand = _make_ssh_command(
|
self._sshcommand = _make_ssh_command(
|
||||||
connhost, connuser, connport, gaddr, gport, gsocket)
|
connhost, connuser, connport, gaddr, gport, gsocket
|
||||||
|
)
|
||||||
self._locked = False
|
self._locked = False
|
||||||
|
|
||||||
def open_new(self):
|
def open_new(self):
|
||||||
|
|
|
@ -17,7 +17,7 @@ from webvirtcloud.settings import (
|
||||||
WS_PUBLIC_PORT,
|
WS_PUBLIC_PORT,
|
||||||
SOCKETIO_PUBLIC_HOST,
|
SOCKETIO_PUBLIC_HOST,
|
||||||
SOCKETIO_PUBLIC_PORT,
|
SOCKETIO_PUBLIC_PORT,
|
||||||
SOCKETIO_PUBLIC_PATH
|
SOCKETIO_PUBLIC_PATH,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,32 +31,39 @@ def console(request):
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
token = request.GET.get("token", "")
|
token = request.GET.get("token", "")
|
||||||
view_type = request.GET.get("view", "lite")
|
view_type = request.GET.get("view", "lite")
|
||||||
view_only = request.GET.get(
|
view_only = request.GET.get("view_only", app_settings.CONSOLE_VIEW_ONLY.lower())
|
||||||
"view_only", app_settings.CONSOLE_VIEW_ONLY.lower())
|
|
||||||
scale = request.GET.get("scale", app_settings.CONSOLE_SCALE.lower())
|
scale = request.GET.get("scale", app_settings.CONSOLE_SCALE.lower())
|
||||||
resize_session = request.GET.get(
|
resize_session = request.GET.get(
|
||||||
"resize_session", app_settings.CONSOLE_RESIZE_SESSION.lower())
|
"resize_session", app_settings.CONSOLE_RESIZE_SESSION.lower()
|
||||||
|
)
|
||||||
clip_viewport = request.GET.get(
|
clip_viewport = request.GET.get(
|
||||||
"clip_viewport", app_settings.CONSOLE_CLIP_VIEWPORT.lower())
|
"clip_viewport", app_settings.CONSOLE_CLIP_VIEWPORT.lower()
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
temptoken = token.split("-", 1)
|
temptoken = token.split("-", 1)
|
||||||
host = int(temptoken[0])
|
host = int(temptoken[0])
|
||||||
uuid = temptoken[1]
|
uuid = temptoken[1]
|
||||||
|
|
||||||
if not request.user.is_superuser and not request.user.has_perm("instances.view_instances"):
|
if not request.user.is_superuser and not request.user.has_perm(
|
||||||
|
"instances.view_instances"
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
userInstance = UserInstance.objects.get(
|
userInstance = UserInstance.objects.get(
|
||||||
instance__compute_id=host, instance__uuid=uuid, user__id=request.user.id
|
instance__compute_id=host,
|
||||||
|
instance__uuid=uuid,
|
||||||
|
user__id=request.user.id,
|
||||||
)
|
)
|
||||||
instance = Instance.objects.get(compute_id=host, uuid=uuid)
|
instance = Instance.objects.get(compute_id=host, uuid=uuid)
|
||||||
except UserInstance.DoesNotExist:
|
except UserInstance.DoesNotExist:
|
||||||
instance = None
|
instance = None
|
||||||
console_error = _("User does not have permission to access console or host/instance not exist")
|
console_error = _(
|
||||||
|
"User does not have permission to access console or host/instance not exist"
|
||||||
|
)
|
||||||
return HttpResponseServerError(console_error)
|
return HttpResponseServerError(console_error)
|
||||||
else:
|
else:
|
||||||
instance = Instance.objects.get(compute_id=host, uuid=uuid)
|
instance = Instance.objects.get(compute_id=host, uuid=uuid)
|
||||||
|
|
||||||
conn = wvmInstance(
|
conn = wvmInstance(
|
||||||
instance.compute.hostname,
|
instance.compute.hostname,
|
||||||
instance.compute.login,
|
instance.compute.login,
|
||||||
|
@ -83,7 +90,9 @@ def console(request):
|
||||||
console_page = "console-" + console_type + "-" + view_type + ".html"
|
console_page = "console-" + console_type + "-" + view_type + ".html"
|
||||||
response = render(request, console_page, locals())
|
response = render(request, console_page, locals())
|
||||||
elif console_type == "pty":
|
elif console_type == "pty":
|
||||||
socketio_host = SOCKETIO_PUBLIC_HOST if SOCKETIO_PUBLIC_HOST else request.get_host()
|
socketio_host = (
|
||||||
|
SOCKETIO_PUBLIC_HOST if SOCKETIO_PUBLIC_HOST else request.get_host()
|
||||||
|
)
|
||||||
socketio_port = SOCKETIO_PUBLIC_PORT if SOCKETIO_PUBLIC_PORT else 6081
|
socketio_port = SOCKETIO_PUBLIC_PORT if SOCKETIO_PUBLIC_PORT else 6081
|
||||||
socketio_path = SOCKETIO_PUBLIC_PATH if SOCKETIO_PUBLIC_PATH else "/"
|
socketio_path = SOCKETIO_PUBLIC_PATH if SOCKETIO_PUBLIC_PATH else "/"
|
||||||
|
|
||||||
|
@ -93,9 +102,13 @@ def console(request):
|
||||||
response = render(request, "console-xterm.html", locals())
|
response = render(request, "console-xterm.html", locals())
|
||||||
else:
|
else:
|
||||||
if console_type is None:
|
if console_type is None:
|
||||||
console_error = _("Fail to get console. Please check the console configuration of your VM.")
|
console_error = _(
|
||||||
|
"Fail to get console. Please check the console configuration of your VM."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
console_error = _("Console type '%(type)s' has not support") % {"type": console_type}
|
console_error = _("Console type '%(type)s' has not support") % {
|
||||||
|
"type": console_type
|
||||||
|
}
|
||||||
response = render(request, "console-vnc-lite.html", locals())
|
response = render(request, "console-vnc-lite.html", locals())
|
||||||
|
|
||||||
response.set_cookie("token", token)
|
response.set_cookie("token", token)
|
||||||
|
|
|
@ -3,8 +3,14 @@ from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('openstack/', views.os_index, name='ds_openstack_index'),
|
path("openstack/", views.os_index, name="ds_openstack_index"),
|
||||||
path('openstack/<version>/meta_data.json', views.os_metadata_json, name='ds_openstack_metadata'),
|
path(
|
||||||
path('openstack/<version>/user_data', views.os_userdata, name='ds_openstack_userdata'),
|
"openstack/<version>/meta_data.json",
|
||||||
path('vdi/<int:compute_id>/<vname>/', views.get_vdi_url, name='vdi_url'),
|
views.os_metadata_json,
|
||||||
|
name="ds_openstack_metadata",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"openstack/<version>/user_data", views.os_userdata, name="ds_openstack_userdata"
|
||||||
|
),
|
||||||
|
path("vdi/<int:compute_id>/<vname>/", views.get_vdi_url, name="vdi_url"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -99,7 +99,13 @@ def get_vdi_url(request, compute_id, vname):
|
||||||
compute = get_object_or_404(Compute, pk=compute_id)
|
compute = get_object_or_404(Compute, pk=compute_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = wvmInstance(compute.hostname, compute.login, compute.password, compute.type, vname)
|
conn = wvmInstance(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type,
|
||||||
|
vname
|
||||||
|
)
|
||||||
|
|
||||||
fqdn = get_hostname_by_ip(compute.hostname)
|
fqdn = get_hostname_by_ip(compute.hostname)
|
||||||
url = f"{conn.get_console_type()}://{fqdn}:{conn.get_console_port()}"
|
url = f"{conn.get_console_type()}://{fqdn}:{conn.get_console_port()}"
|
||||||
|
|
|
@ -5,3 +5,4 @@ pycodestyle==2.9.1
|
||||||
pyflakes==2.5.0
|
pyflakes==2.5.0
|
||||||
pylint==2.15.3
|
pylint==2.15.3
|
||||||
yapf==0.32.0
|
yapf==0.32.0
|
||||||
|
black
|
|
@ -4,90 +4,88 @@ from instances.models import Flavor, Instance, MigrateInstance, CreateInstance
|
||||||
|
|
||||||
|
|
||||||
class InstanceSerializer(serializers.ModelSerializer):
|
class InstanceSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Instance
|
model = Instance
|
||||||
fields = ['id', 'compute', 'name', 'uuid', 'is_template', 'created', 'drbd']
|
fields = ["id", "compute", "name", "uuid", "is_template", "created", "drbd"]
|
||||||
|
|
||||||
|
|
||||||
class InstanceDetailsSerializer(serializers.ModelSerializer):
|
class InstanceDetailsSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Instance
|
model = Instance
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
"id",
|
||||||
'compute',
|
"compute",
|
||||||
'status',
|
"status",
|
||||||
'uuid',
|
"uuid",
|
||||||
'name',
|
"name",
|
||||||
'title',
|
"title",
|
||||||
'description',
|
"description",
|
||||||
'is_template',
|
"is_template",
|
||||||
'created',
|
"created",
|
||||||
'drbd',
|
"drbd",
|
||||||
'arch',
|
"arch",
|
||||||
'machine',
|
"machine",
|
||||||
'vcpu',
|
"vcpu",
|
||||||
'memory',
|
"memory",
|
||||||
'firmware',
|
"firmware",
|
||||||
'nvram',
|
"nvram",
|
||||||
'bootmenu',
|
"bootmenu",
|
||||||
'boot_order',
|
"boot_order",
|
||||||
'disks',
|
"disks",
|
||||||
'media',
|
"media",
|
||||||
'media_iso',
|
"media_iso",
|
||||||
'snapshots',
|
"snapshots",
|
||||||
'networks',
|
"networks",
|
||||||
'console_type',
|
"console_type",
|
||||||
'console_port',
|
"console_port",
|
||||||
'console_keymap',
|
"console_keymap",
|
||||||
'console_listener_address',
|
"console_listener_address",
|
||||||
'video_model',
|
"video_model",
|
||||||
'guest_agent_ready',
|
"guest_agent_ready",
|
||||||
'autostart']
|
"autostart",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class FlavorSerializer(serializers.ModelSerializer):
|
class FlavorSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Flavor
|
model = Flavor
|
||||||
fields = ['label', 'memory', 'vcpu', 'disk']
|
fields = ["label", "memory", "vcpu", "disk"]
|
||||||
|
|
||||||
|
|
||||||
class CreateInstanceSerializer(serializers.ModelSerializer):
|
class CreateInstanceSerializer(serializers.ModelSerializer):
|
||||||
firmware_choices = (
|
firmware_choices = (
|
||||||
('', 'BIOS'),
|
("", "BIOS"),
|
||||||
#('UEFI', 'UEFI'),
|
# ('UEFI', 'UEFI'),
|
||||||
)
|
)
|
||||||
firmware = serializers.ChoiceField(choices = firmware_choices)
|
firmware = serializers.ChoiceField(choices=firmware_choices)
|
||||||
graphics = serializers.CharField(initial='vnc')
|
graphics = serializers.CharField(initial="vnc")
|
||||||
video = serializers.CharField(initial='vga')
|
video = serializers.CharField(initial="vga")
|
||||||
storage = serializers.CharField(initial='default')
|
storage = serializers.CharField(initial="default")
|
||||||
cache_mode = serializers.CharField(initial='none')
|
cache_mode = serializers.CharField(initial="none")
|
||||||
virtio = serializers.BooleanField(initial=True)
|
virtio = serializers.BooleanField(initial=True)
|
||||||
qemu_ga = serializers.BooleanField(initial=True)
|
qemu_ga = serializers.BooleanField(initial=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CreateInstance
|
model = CreateInstance
|
||||||
fields = [
|
fields = [
|
||||||
'name',
|
"name",
|
||||||
'firmware',
|
"firmware",
|
||||||
'vcpu',
|
"vcpu",
|
||||||
'vcpu_mode',
|
"vcpu_mode",
|
||||||
'memory',
|
"memory",
|
||||||
'networks',
|
"networks",
|
||||||
'mac',
|
"mac",
|
||||||
'nwfilter',
|
"nwfilter",
|
||||||
'storage',
|
"storage",
|
||||||
'hdd_size',
|
"hdd_size",
|
||||||
'cache_mode',
|
"cache_mode",
|
||||||
'meta_prealloc',
|
"meta_prealloc",
|
||||||
'virtio',
|
"virtio",
|
||||||
'qemu_ga',
|
"qemu_ga",
|
||||||
'console_pass',
|
"console_pass",
|
||||||
'graphics',
|
"graphics",
|
||||||
'video',
|
"video",
|
||||||
'listener_addr'
|
"listener_addr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,6 +93,17 @@ class MigrateSerializer(serializers.ModelSerializer):
|
||||||
instance = Instance.objects.all().prefetch_related("userinstance_set")
|
instance = Instance.objects.all().prefetch_related("userinstance_set")
|
||||||
live = serializers.BooleanField(initial=True)
|
live = serializers.BooleanField(initial=True)
|
||||||
xml_del = serializers.BooleanField(initial=True)
|
xml_del = serializers.BooleanField(initial=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MigrateInstance
|
model = MigrateInstance
|
||||||
fields = ['instance', 'target_compute', 'live', 'xml_del', 'offline', 'autoconverge', 'compress', 'postcopy', 'unsafe']
|
fields = [
|
||||||
|
"instance",
|
||||||
|
"target_compute",
|
||||||
|
"live",
|
||||||
|
"xml_del",
|
||||||
|
"offline",
|
||||||
|
"autoconverge",
|
||||||
|
"compress",
|
||||||
|
"postcopy",
|
||||||
|
"unsafe",
|
||||||
|
]
|
||||||
|
|
|
@ -5,7 +5,15 @@ from computes import utils
|
||||||
from instances.models import Flavor, Instance
|
from instances.models import Flavor, Instance
|
||||||
from instances.views import get_instance
|
from instances.views import get_instance
|
||||||
from instances.utils import migrate_instance
|
from instances.utils import migrate_instance
|
||||||
from instances.views import poweron, powercycle, poweroff, force_off, suspend, resume, destroy as instance_destroy
|
from instances.views import (
|
||||||
|
poweron,
|
||||||
|
powercycle,
|
||||||
|
poweroff,
|
||||||
|
force_off,
|
||||||
|
suspend,
|
||||||
|
resume,
|
||||||
|
destroy as instance_destroy,
|
||||||
|
)
|
||||||
|
|
||||||
from rest_framework import status, viewsets, permissions
|
from rest_framework import status, viewsets, permissions
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
@ -14,26 +22,39 @@ from rest_framework.response import Response
|
||||||
from vrtManager import util
|
from vrtManager import util
|
||||||
|
|
||||||
from vrtManager.create import wvmCreate
|
from vrtManager.create import wvmCreate
|
||||||
from .serializers import FlavorSerializer, InstanceSerializer, InstanceDetailsSerializer, MigrateSerializer, CreateInstanceSerializer
|
from .serializers import (
|
||||||
|
FlavorSerializer,
|
||||||
|
InstanceSerializer,
|
||||||
|
InstanceDetailsSerializer,
|
||||||
|
MigrateSerializer,
|
||||||
|
CreateInstanceSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InstancesViewSet(viewsets.ViewSet):
|
class InstancesViewSet(viewsets.ViewSet):
|
||||||
"""
|
"""
|
||||||
A simple ViewSet for listing or retrieving ALL/Compute Instances.
|
A simple ViewSet for listing or retrieving ALL/Compute Instances.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
def list(self, request):
|
def list(self, request):
|
||||||
|
|
||||||
if request.user.is_superuser or request.user.has_perm("instances.view_instances"):
|
if request.user.is_superuser or request.user.has_perm("instances.view_instances"):
|
||||||
queryset = Instance.objects.all().prefetch_related("userinstance_set")
|
queryset = Instance.objects.all().prefetch_related("userinstance_set")
|
||||||
else:
|
else:
|
||||||
queryset = Instance.objects.filter(userinstance__user=request.user).prefetch_related("userinstance_set")
|
queryset = Instance.objects.filter(userinstance__user=request.user).prefetch_related("userinstance_set")
|
||||||
serializer = InstanceSerializer(queryset, many=True, context={'request': request})
|
serializer = InstanceSerializer(
|
||||||
|
queryset,
|
||||||
|
many=True,
|
||||||
|
context={"request": request}
|
||||||
|
)
|
||||||
|
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
def retrieve(self, request, pk=None, compute_pk=None):
|
def retrieve(self, request, pk=None, compute_pk=None):
|
||||||
queryset = get_instance(request.user, pk)
|
queryset = get_instance(request.user, pk)
|
||||||
serializer = InstanceSerializer(queryset, context={'request': request})
|
serializer = InstanceSerializer(queryset, context={"request": request})
|
||||||
|
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@ -42,94 +63,110 @@ class InstanceViewSet(viewsets.ViewSet):
|
||||||
"""
|
"""
|
||||||
A simple ViewSet for listing or retrieving Compute Instances.
|
A simple ViewSet for listing or retrieving Compute Instances.
|
||||||
"""
|
"""
|
||||||
#serializer_class = CreateInstanceSerializer
|
|
||||||
|
# serializer_class = CreateInstanceSerializer
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
def list(self, request, compute_pk=None):
|
def list(self, request, compute_pk=None):
|
||||||
compute = get_object_or_404(Compute, pk=compute_pk)
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
||||||
|
|
||||||
utils.refresh_instance_database(compute)
|
utils.refresh_instance_database(compute)
|
||||||
|
|
||||||
queryset = Instance.objects.filter(compute=compute).prefetch_related("userinstance_set")
|
|
||||||
serializer = InstanceSerializer(queryset, many=True, context={'request': request})
|
|
||||||
|
|
||||||
return Response(serializer.data)
|
|
||||||
|
|
||||||
|
queryset = Instance.objects.filter(compute=compute).prefetch_related("userinstance_set")
|
||||||
|
serializer = InstanceSerializer(
|
||||||
|
queryset,
|
||||||
|
many=True,
|
||||||
|
context={"request": request}
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
def retrieve(self, request, pk=None, compute_pk=None):
|
def retrieve(self, request, pk=None, compute_pk=None):
|
||||||
queryset = get_instance(request.user, pk)
|
queryset = get_instance(request.user, pk)
|
||||||
serializer = InstanceDetailsSerializer(queryset, context={'request': request})
|
serializer = InstanceDetailsSerializer(queryset, context={"request": request})
|
||||||
|
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
def destroy(self, request, pk=None, compute_pk=None):
|
def destroy(self, request, pk=None, compute_pk=None):
|
||||||
instance_destroy(request, pk)
|
instance_destroy(request, pk)
|
||||||
return Response({'status': 'Instance is destroyed'})
|
return Response({"status": "Instance is destroyed"})
|
||||||
|
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=["post"])
|
||||||
def poweron(self, request, pk=None):
|
def poweron(self, request, pk=None):
|
||||||
poweron(request, pk)
|
poweron(request, pk)
|
||||||
return Response({'status': 'poweron command send'})
|
return Response({"status": "poweron command send"})
|
||||||
|
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=["post"])
|
||||||
def poweroff(self, request, pk=None):
|
def poweroff(self, request, pk=None):
|
||||||
poweroff(request, pk)
|
poweroff(request, pk)
|
||||||
return Response({'status': 'poweroff command send'})
|
return Response({"status": "poweroff command send"})
|
||||||
|
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=["post"])
|
||||||
def powercycle(self, request, pk=None):
|
def powercycle(self, request, pk=None):
|
||||||
powercycle(request, pk)
|
powercycle(request, pk)
|
||||||
return Response({'status': 'powercycle command send'})
|
return Response({"status": "powercycle command send"})
|
||||||
|
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=["post"])
|
||||||
def forceoff(self, request, pk=None):
|
def forceoff(self, request, pk=None):
|
||||||
force_off(request, pk)
|
force_off(request, pk)
|
||||||
return Response({'status': 'force off command send'})
|
return Response({"status": "force off command send"})
|
||||||
|
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=["post"])
|
||||||
def suspend(self, request, pk=None):
|
def suspend(self, request, pk=None):
|
||||||
suspend(request, pk)
|
suspend(request, pk)
|
||||||
return Response({'status': 'suspend command send'})
|
return Response({"status": "suspend command send"})
|
||||||
|
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=["post"])
|
||||||
def resume(self, request, pk=None):
|
def resume(self, request, pk=None):
|
||||||
resume(request, pk)
|
resume(request, pk)
|
||||||
return Response({'status': 'resume command send'})
|
return Response({"status": "resume command send"})
|
||||||
|
|
||||||
|
|
||||||
class MigrateViewSet(viewsets.ViewSet):
|
class MigrateViewSet(viewsets.ViewSet):
|
||||||
"""
|
"""
|
||||||
A viewset for migrating instances.
|
A viewset for migrating instances.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
serializer_class = MigrateSerializer
|
serializer_class = MigrateSerializer
|
||||||
queryset = ""
|
queryset = ""
|
||||||
|
|
||||||
def create(self, request):
|
def create(self, request):
|
||||||
serializer = MigrateSerializer(data=request.data)
|
serializer = MigrateSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
instance = serializer.validated_data['instance']
|
instance = serializer.validated_data["instance"]
|
||||||
target_host = serializer.validated_data['target_compute']
|
target_host = serializer.validated_data["target_compute"]
|
||||||
live = serializer.validated_data['live']
|
live = serializer.validated_data["live"]
|
||||||
unsafe = serializer.validated_data['unsafe']
|
unsafe = serializer.validated_data["unsafe"]
|
||||||
xml_del = serializer.validated_data['xml_del']
|
xml_del = serializer.validated_data["xml_del"]
|
||||||
offline = serializer.validated_data['offline']
|
offline = serializer.validated_data["offline"]
|
||||||
autoconverge = serializer.validated_data['autoconverge']
|
autoconverge = serializer.validated_data["autoconverge"]
|
||||||
postcopy = serializer.validated_data['postcopy']
|
postcopy = serializer.validated_data["postcopy"]
|
||||||
compress = serializer.validated_data['compress']
|
compress = serializer.validated_data["compress"]
|
||||||
|
|
||||||
migrate_instance(target_host, instance, request.user, live, unsafe, xml_del, offline, autoconverge, compress, postcopy)
|
migrate_instance(
|
||||||
|
target_host,
|
||||||
|
instance,
|
||||||
|
request.user,
|
||||||
|
live,
|
||||||
|
unsafe,
|
||||||
|
xml_del,
|
||||||
|
offline,
|
||||||
|
autoconverge,
|
||||||
|
compress,
|
||||||
|
postcopy,
|
||||||
|
)
|
||||||
|
|
||||||
return Response({'status': 'instance migrate is started'})
|
return Response({"status": "instance migrate is started"})
|
||||||
else:
|
else:
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class FlavorViewSet(viewsets.ModelViewSet):
|
class FlavorViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
API endpoint that allows flavor to be viewed.
|
API endpoint that allows flavor to be viewed.
|
||||||
"""
|
"""
|
||||||
queryset = Flavor.objects.all().order_by('id')
|
|
||||||
|
queryset = Flavor.objects.all().order_by("id")
|
||||||
serializer_class = FlavorSerializer
|
serializer_class = FlavorSerializer
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
@ -138,15 +175,15 @@ class CreateInstanceViewSet(viewsets.ViewSet):
|
||||||
"""
|
"""
|
||||||
A viewset for creating instances.
|
A viewset for creating instances.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
serializer_class = CreateInstanceSerializer
|
serializer_class = CreateInstanceSerializer
|
||||||
queryset = ""
|
queryset = ""
|
||||||
|
|
||||||
def create(self, request, compute_pk=None, arch=None, machine=None):
|
def create(self, request, compute_pk=None, arch=None, machine=None):
|
||||||
serializer = CreateInstanceSerializer(data=request.data,
|
serializer = CreateInstanceSerializer(
|
||||||
context = {'compute_pk': compute_pk,
|
data=request.data,
|
||||||
'arch': arch,
|
context={"compute_pk": compute_pk, "arch": arch, "machine": machine},
|
||||||
'machine': machine
|
)
|
||||||
})
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
volume_list = []
|
volume_list = []
|
||||||
default_bus = app_settings.INSTANCE_VOLUME_DEFAULT_BUS
|
default_bus = app_settings.INSTANCE_VOLUME_DEFAULT_BUS
|
||||||
|
@ -166,14 +203,14 @@ class CreateInstanceViewSet(viewsets.ViewSet):
|
||||||
)
|
)
|
||||||
|
|
||||||
path = conn.create_volume(
|
path = conn.create_volume(
|
||||||
serializer.validated_data['storage'],
|
serializer.validated_data["storage"],
|
||||||
serializer.validated_data['name'],
|
serializer.validated_data["name"],
|
||||||
serializer.validated_data['hdd_size'],
|
serializer.validated_data["hdd_size"],
|
||||||
default_disk_format,
|
default_disk_format,
|
||||||
serializer.validated_data['meta_prealloc'],
|
serializer.validated_data["meta_prealloc"],
|
||||||
default_disk_owner_uid,
|
default_disk_owner_uid,
|
||||||
default_disk_owner_gid,
|
default_disk_owner_gid,
|
||||||
)
|
)
|
||||||
volume = {}
|
volume = {}
|
||||||
firmware = {}
|
firmware = {}
|
||||||
volume["device"] = "disk"
|
volume["device"] = "disk"
|
||||||
|
@ -189,8 +226,8 @@ class CreateInstanceViewSet(viewsets.ViewSet):
|
||||||
|
|
||||||
volume_list.append(volume)
|
volume_list.append(volume)
|
||||||
|
|
||||||
if "UEFI" in serializer.validated_data['firmware']:
|
if "UEFI" in serializer.validated_data["firmware"]:
|
||||||
firmware["loader"] = serializer.validated_data['firmware'].split(":")[1].strip()
|
firmware["loader"] = (serializer.validated_data["firmware"].split(":")[1].strip())
|
||||||
firmware["secure"] = "no"
|
firmware["secure"] = "no"
|
||||||
firmware["readonly"] = "yes"
|
firmware["readonly"] = "yes"
|
||||||
firmware["type"] = "pflash"
|
firmware["type"] = "pflash"
|
||||||
|
@ -199,26 +236,26 @@ class CreateInstanceViewSet(viewsets.ViewSet):
|
||||||
firmware["secure"] = "yes"
|
firmware["secure"] = "yes"
|
||||||
|
|
||||||
ret = conn.create_instance(
|
ret = conn.create_instance(
|
||||||
name=serializer.validated_data['name'],
|
name=serializer.validated_data["name"],
|
||||||
memory=serializer.validated_data['memory'],
|
memory=serializer.validated_data["memory"],
|
||||||
vcpu=serializer.validated_data['vcpu'],
|
vcpu=serializer.validated_data["vcpu"],
|
||||||
vcpu_mode=serializer.validated_data['vcpu_mode'],
|
vcpu_mode=serializer.validated_data["vcpu_mode"],
|
||||||
uuid=util.randomUUID(),
|
uuid=util.randomUUID(),
|
||||||
arch=arch,
|
arch=arch,
|
||||||
machine=machine,
|
machine=machine,
|
||||||
firmware=firmware,
|
firmware=firmware,
|
||||||
volumes=volume_list,
|
volumes=volume_list,
|
||||||
networks=serializer.validated_data['networks'],
|
networks=serializer.validated_data["networks"],
|
||||||
nwfilter=serializer.validated_data['nwfilter'],
|
nwfilter=serializer.validated_data["nwfilter"],
|
||||||
graphics=serializer.validated_data['graphics'],
|
graphics=serializer.validated_data["graphics"],
|
||||||
virtio=serializer.validated_data['virtio'],
|
virtio=serializer.validated_data["virtio"],
|
||||||
listener_addr=serializer.validated_data['listener_addr'],
|
listener_addr=serializer.validated_data["listener_addr"],
|
||||||
video=serializer.validated_data['video'],
|
video=serializer.validated_data["video"],
|
||||||
console_pass=serializer.validated_data['console_pass'],
|
console_pass=serializer.validated_data["console_pass"],
|
||||||
mac=serializer.validated_data['mac'],
|
mac=serializer.validated_data["mac"],
|
||||||
qemu_ga=serializer.validated_data['qemu_ga'],
|
qemu_ga=serializer.validated_data["qemu_ga"],
|
||||||
)
|
)
|
||||||
msg = f"Instance {serializer.validated_data['name']} is created"
|
msg = f"Instance {serializer.validated_data['name']} is created"
|
||||||
return Response({'status': msg })
|
return Response({"status": msg})
|
||||||
else:
|
else:
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
|
@ -3,17 +3,19 @@ from django.db.models.signals import post_migrate
|
||||||
|
|
||||||
|
|
||||||
def migrate_can_clone_instances(sender, **kwargs):
|
def migrate_can_clone_instances(sender, **kwargs):
|
||||||
'''
|
"""
|
||||||
Migrate can clone instances user attribute to permission
|
Migrate can clone instances user attribute to permission
|
||||||
'''
|
"""
|
||||||
from django.contrib.auth.models import Permission, User
|
from django.contrib.auth.models import Permission, User
|
||||||
|
|
||||||
plan = kwargs.get('plan', [])
|
plan = kwargs.get("plan", [])
|
||||||
for migration, rolled_back in plan:
|
for migration, rolled_back in plan:
|
||||||
if migration.app_label == 'instances' and migration.name == '0002_permissionset' and not rolled_back:
|
if (migration.app_label == "instances" and migration.name == "0002_permissionset" and not rolled_back):
|
||||||
users = User.objects.all()
|
users = User.objects.all()
|
||||||
permission = Permission.objects.get(codename='clone_instances')
|
permission = Permission.objects.get(codename="clone_instances")
|
||||||
print('\033[1m* \033[92mMigrating can_clone_instaces user attribute to permission\033[0m')
|
print(
|
||||||
|
"\033[1m* \033[92mMigrating can_clone_instaces user attribute to permission\033[0m"
|
||||||
|
)
|
||||||
for user in users:
|
for user in users:
|
||||||
if user.userattributes:
|
if user.userattributes:
|
||||||
if user.userattributes.can_clone_instances:
|
if user.userattributes.can_clone_instances:
|
||||||
|
@ -22,25 +24,26 @@ def migrate_can_clone_instances(sender, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
def apply_passwordless_console(sender, **kwargs):
|
def apply_passwordless_console(sender, **kwargs):
|
||||||
'''
|
"""
|
||||||
Apply new passwordless_console permission for all users
|
Apply new passwordless_console permission for all users
|
||||||
'''
|
"""
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
plan = kwargs.get('plan', [])
|
plan = kwargs.get("plan", [])
|
||||||
for migration, rolled_back in plan:
|
for migration, rolled_back in plan:
|
||||||
if migration.app_label == 'instances' and migration.name == '0009_auto_20200717_0524' and not rolled_back:
|
if (migration.app_label == "instances" and migration.name == "0009_auto_20200717_0524" and not rolled_back):
|
||||||
print('\033[1m* \033[92mApplying permission passwordless_console for all users\033[0m')
|
print("\033[1m* \033[92mApplying permission passwordless_console for all users\033[0m")
|
||||||
users = User.objects.all()
|
users = User.objects.all()
|
||||||
permission = Permission.objects.get(codename='passwordless_console')
|
permission = Permission.objects.get(codename="passwordless_console")
|
||||||
for user in users:
|
for user in users:
|
||||||
user.user_permissions.add(permission)
|
user.user_permissions.add(permission)
|
||||||
|
|
||||||
|
|
||||||
class InstancesConfig(AppConfig):
|
class InstancesConfig(AppConfig):
|
||||||
name = 'instances'
|
name = "instances"
|
||||||
verbose_name = 'Instances'
|
verbose_name = "Instances"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
post_migrate.connect(migrate_can_clone_instances, sender=self)
|
post_migrate.connect(migrate_can_clone_instances, sender=self)
|
||||||
|
|
|
@ -12,7 +12,7 @@ from .models import CreateInstance, Flavor
|
||||||
class FlavorForm(forms.ModelForm):
|
class FlavorForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Flavor
|
model = Flavor
|
||||||
fields = '__all__'
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class ConsoleForm(forms.Form):
|
class ConsoleForm(forms.Form):
|
||||||
|
@ -20,17 +20,25 @@ class ConsoleForm(forms.Form):
|
||||||
listen_on = forms.ChoiceField()
|
listen_on = forms.ChoiceField()
|
||||||
generate_password = forms.BooleanField(required=False)
|
generate_password = forms.BooleanField(required=False)
|
||||||
clear_password = forms.BooleanField(required=False)
|
clear_password = forms.BooleanField(required=False)
|
||||||
password = forms.CharField(widget=forms.PasswordInput(render_value=True), required=False)
|
password = forms.CharField(
|
||||||
|
widget=forms.PasswordInput(render_value=True),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
clear_keymap = forms.BooleanField(required=False)
|
clear_keymap = forms.BooleanField(required=False)
|
||||||
keymap = forms.ChoiceField(required=False)
|
keymap = forms.ChoiceField(required=False)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(ConsoleForm, self).__init__(*args, **kwargs)
|
super(ConsoleForm, self).__init__(*args, **kwargs)
|
||||||
type_choices = ((c, c) for c in AppSettings.objects.get(key="QEMU_CONSOLE_DEFAULT_TYPE").choices_as_list())
|
type_choices = (
|
||||||
keymap_choices = [('auto', 'Auto')] + list((c, c) for c in QEMU_KEYMAPS)
|
(c, c)
|
||||||
self.fields['type'] = forms.ChoiceField(choices=type_choices)
|
for c in AppSettings.objects.get(key="QEMU_CONSOLE_DEFAULT_TYPE").choices_as_list()
|
||||||
self.fields['listen_on'] = forms.ChoiceField(choices=QEMU_CONSOLE_LISTENER_ADDRESSES)
|
)
|
||||||
self.fields['keymap'] = forms.ChoiceField(choices=keymap_choices)
|
keymap_choices = [("auto", "Auto")] + list((c, c) for c in QEMU_KEYMAPS)
|
||||||
|
self.fields["type"] = forms.ChoiceField(choices=type_choices)
|
||||||
|
self.fields["listen_on"] = forms.ChoiceField(
|
||||||
|
choices=QEMU_CONSOLE_LISTENER_ADDRESSES
|
||||||
|
)
|
||||||
|
self.fields["keymap"] = forms.ChoiceField(choices=keymap_choices)
|
||||||
|
|
||||||
|
|
||||||
class NewVMForm(forms.ModelForm):
|
class NewVMForm(forms.ModelForm):
|
||||||
|
@ -57,12 +65,16 @@ class NewVMForm(forms.ModelForm):
|
||||||
# listener_addr = forms.ChoiceField(required=True, widget=forms.RadioSelect, choices=QEMU_CONSOLE_LISTENER_ADDRESSES)
|
# listener_addr = forms.ChoiceField(required=True, widget=forms.RadioSelect, choices=QEMU_CONSOLE_LISTENER_ADDRESSES)
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CreateInstance
|
model = CreateInstance
|
||||||
fields = '__all__'
|
fields = "__all__"
|
||||||
exclude = ['compute']
|
exclude = ["compute"]
|
||||||
|
|
||||||
def clean_name(self):
|
def clean_name(self):
|
||||||
name = self.cleaned_data['name']
|
name = self.cleaned_data["name"]
|
||||||
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
|
have_symbol = re.match("^[a-zA-Z0-9._-]+$", name)
|
||||||
if not have_symbol:
|
if not have_symbol:
|
||||||
raise forms.ValidationError(_('The name of the virtual machine must not contain any special characters'))
|
raise forms.ValidationError(
|
||||||
|
_(
|
||||||
|
"The name of the virtual machine must not contain any special characters"
|
||||||
|
)
|
||||||
|
)
|
||||||
return name
|
return name
|
||||||
|
|
|
@ -10,10 +10,10 @@ from vrtManager.instance import wvmInstance
|
||||||
|
|
||||||
|
|
||||||
class Flavor(models.Model):
|
class Flavor(models.Model):
|
||||||
label = models.CharField(_('label'), max_length=12, unique=True)
|
label = models.CharField(_("label"), max_length=12, unique=True)
|
||||||
memory = models.IntegerField(_('memory'))
|
memory = models.IntegerField(_("memory"))
|
||||||
vcpu = models.IntegerField(_('vcpu'))
|
vcpu = models.IntegerField(_("vcpu"))
|
||||||
disk = models.IntegerField(_('disk'))
|
disk = models.IntegerField(_("disk"))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.label
|
return self.label
|
||||||
|
@ -21,21 +21,21 @@ class Flavor(models.Model):
|
||||||
|
|
||||||
class InstanceManager(models.Manager):
|
class InstanceManager(models.Manager):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().select_related('compute')
|
return super().get_queryset().select_related("compute")
|
||||||
|
|
||||||
|
|
||||||
class Instance(models.Model):
|
class Instance(models.Model):
|
||||||
compute = models.ForeignKey(Compute, on_delete=models.CASCADE)
|
compute = models.ForeignKey(Compute, on_delete=models.CASCADE)
|
||||||
name = models.CharField(_('name'), max_length=120, db_index=True)
|
name = models.CharField(_("name"), max_length=120, db_index=True)
|
||||||
uuid = models.CharField(_('uuid'), max_length=36, db_index=True)
|
uuid = models.CharField(_("uuid"), max_length=36, db_index=True)
|
||||||
is_template = models.BooleanField(_('is template'), default=False)
|
is_template = models.BooleanField(_("is template"), default=False)
|
||||||
created = models.DateTimeField(_('created'), auto_now_add=True)
|
created = models.DateTimeField(_("created"), auto_now_add=True)
|
||||||
drbd = models.CharField(_('drbd'), max_length=24, default="None")
|
drbd = models.CharField(_("drbd"), max_length=24, default="None")
|
||||||
|
|
||||||
objects = InstanceManager()
|
objects = InstanceManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.compute}/{self.name}'
|
return f"{self.compute}/{self.name}"
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def proxy(self):
|
def proxy(self):
|
||||||
|
@ -173,7 +173,7 @@ class Instance(models.Model):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def snapshots(self):
|
def snapshots(self):
|
||||||
return sorted(self.proxy.get_snapshot(), reverse=True, key=lambda k: k['date'])
|
return sorted(self.proxy.get_snapshot(), reverse=True, key=lambda k: k["date"])
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def inst_xml(self):
|
def inst_xml(self):
|
||||||
|
@ -209,35 +209,59 @@ class Instance(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class MigrateInstance(models.Model):
|
class MigrateInstance(models.Model):
|
||||||
instance = models.ForeignKey(Instance, related_name='source_host', on_delete=models.DO_NOTHING)
|
instance = models.ForeignKey(
|
||||||
target_compute = models.ForeignKey(Compute, related_name='target_host', on_delete=models.DO_NOTHING)
|
Instance,
|
||||||
|
related_name="source_host",
|
||||||
|
on_delete=models.DO_NOTHING
|
||||||
|
)
|
||||||
|
target_compute = models.ForeignKey(
|
||||||
|
Compute,
|
||||||
|
related_name="target_host",
|
||||||
|
on_delete=models.DO_NOTHING
|
||||||
|
)
|
||||||
|
|
||||||
live = models.BooleanField(_('Live'))
|
live = models.BooleanField(_("Live"))
|
||||||
xml_del = models.BooleanField(_('Undefine XML'), default=True)
|
xml_del = models.BooleanField(_("Undefine XML"), default=True)
|
||||||
offline = models.BooleanField(_('Offline'))
|
offline = models.BooleanField(_("Offline"))
|
||||||
autoconverge = models.BooleanField(_('Auto Converge'), default=True)
|
autoconverge = models.BooleanField(_("Auto Converge"), default=True)
|
||||||
compress = models.BooleanField(_('Compress'), default=False)
|
compress = models.BooleanField(_("Compress"), default=False)
|
||||||
postcopy = models.BooleanField(_('Post Copy'), default=False)
|
postcopy = models.BooleanField(_("Post Copy"), default=False)
|
||||||
unsafe = models.BooleanField(_('Unsafe'), default=False)
|
unsafe = models.BooleanField(_("Unsafe"), default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
managed = False
|
managed = False
|
||||||
|
|
||||||
|
|
||||||
class CreateInstance(models.Model):
|
class CreateInstance(models.Model):
|
||||||
compute = models.ForeignKey(Compute, related_name='host', on_delete=models.DO_NOTHING)
|
compute = models.ForeignKey(
|
||||||
name = models.CharField(max_length=64, error_messages={'required': _('No Virtual Machine name has been entered')})
|
Compute,
|
||||||
|
related_name="host",
|
||||||
|
on_delete=models.DO_NOTHING
|
||||||
|
)
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
error_messages={"required": _("No Virtual Machine name has been entered")},
|
||||||
|
)
|
||||||
firmware = models.CharField(max_length=64)
|
firmware = models.CharField(max_length=64)
|
||||||
vcpu = models.IntegerField(error_messages={'required': _('No VCPU has been entered')})
|
vcpu = models.IntegerField(
|
||||||
|
error_messages={"required": _("No VCPU has been entered")}
|
||||||
|
)
|
||||||
vcpu_mode = models.CharField(max_length=20, blank=True)
|
vcpu_mode = models.CharField(max_length=20, blank=True)
|
||||||
disk = models.IntegerField(blank=True)
|
disk = models.IntegerField(blank=True)
|
||||||
memory = models.IntegerField(error_messages={'required': _('No RAM size has been entered')})
|
memory = models.IntegerField(
|
||||||
networks = models.CharField(max_length=256, error_messages={'required': _('No Network pool has been choosen')})
|
error_messages={"required": _("No RAM size has been entered")}
|
||||||
|
)
|
||||||
|
networks = models.CharField(
|
||||||
|
max_length=256,
|
||||||
|
error_messages={"required": _("No Network pool has been choosen")},
|
||||||
|
)
|
||||||
nwfilter = models.CharField(max_length=256, blank=True)
|
nwfilter = models.CharField(max_length=256, blank=True)
|
||||||
storage = models.CharField(max_length=256, blank=True)
|
storage = models.CharField(max_length=256, blank=True)
|
||||||
template = models.CharField(max_length=256, blank=True)
|
template = models.CharField(max_length=256, blank=True)
|
||||||
images = models.CharField(max_length=256, blank=True)
|
images = models.CharField(max_length=256, blank=True)
|
||||||
cache_mode = models.CharField(max_length=16, error_messages={'required': _('Please select HDD cache mode')})
|
cache_mode = models.CharField(
|
||||||
|
max_length=16, error_messages={"required": _("Please select HDD cache mode")}
|
||||||
|
)
|
||||||
hdd_size = models.IntegerField(blank=True)
|
hdd_size = models.IntegerField(blank=True)
|
||||||
meta_prealloc = models.BooleanField(default=False, blank=True)
|
meta_prealloc = models.BooleanField(default=False, blank=True)
|
||||||
virtio = models.BooleanField(default=True)
|
virtio = models.BooleanField(default=True)
|
||||||
|
@ -246,9 +270,15 @@ class CreateInstance(models.Model):
|
||||||
console_pass = models.CharField(max_length=64, blank=True)
|
console_pass = models.CharField(max_length=64, blank=True)
|
||||||
add_cdrom = models.CharField(max_length=16)
|
add_cdrom = models.CharField(max_length=16)
|
||||||
add_input = models.CharField(max_length=16)
|
add_input = models.CharField(max_length=16)
|
||||||
graphics = models.CharField(max_length=16, error_messages={'required': _('Please select a graphics type')})
|
graphics = models.CharField(
|
||||||
video = models.CharField(max_length=16, error_messages={'required': _('Please select a video driver')})
|
max_length=16, error_messages={"required": _("Please select a graphics type")}
|
||||||
listener_addr = models.CharField(max_length=20, choices=QEMU_CONSOLE_LISTENER_ADDRESSES)
|
)
|
||||||
|
video = models.CharField(
|
||||||
|
max_length=16, error_messages={"required": _("Please select a video driver")}
|
||||||
|
)
|
||||||
|
listener_addr = models.CharField(
|
||||||
|
max_length=20, choices=QEMU_CONSOLE_LISTENER_ADDRESSES
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
managed = False
|
managed = False
|
||||||
|
@ -258,13 +288,14 @@ class PermissionSet(models.Model):
|
||||||
"""
|
"""
|
||||||
Dummy model for holding set of permissions we need to be automatically added by Django
|
Dummy model for holding set of permissions we need to be automatically added by Django
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
default_permissions = ()
|
default_permissions = ()
|
||||||
permissions = [
|
permissions = [
|
||||||
('clone_instances', 'Can clone instances'),
|
("clone_instances", "Can clone instances"),
|
||||||
('passwordless_console', _('Can access console without password')),
|
("passwordless_console", _("Can access console without password")),
|
||||||
('view_instances', 'Can view instances'),
|
("view_instances", "Can view instances"),
|
||||||
('snapshot_instances', 'Can snapshot instances'),
|
("snapshot_instances", "Can snapshot instances"),
|
||||||
]
|
]
|
||||||
|
|
||||||
managed = False
|
managed = False
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,69 +2,83 @@ from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
app_name = 'instances'
|
app_name = "instances"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.index, name='index'),
|
path("", views.index, name="index"),
|
||||||
path('flavor/create/', views.flavor_create, name='flavor_create'),
|
path("flavor/create/", views.flavor_create, name="flavor_create"),
|
||||||
path('flavor/<int:pk>/update/', views.flavor_update, name='flavor_update'),
|
path("flavor/<int:pk>/update/", views.flavor_update, name="flavor_update"),
|
||||||
path('flavor/<int:pk>/delete/', views.flavor_delete, name='flavor_delete'),
|
path("flavor/<int:pk>/delete/", views.flavor_delete, name="flavor_delete"),
|
||||||
path('<int:pk>/', views.instance, name='instance'),
|
path("<int:pk>/", views.instance, name="instance"),
|
||||||
path('<int:pk>/poweron/', views.poweron, name='poweron'),
|
path("<int:pk>/poweron/", views.poweron, name="poweron"),
|
||||||
path('<int:pk>/powercycle/', views.powercycle, name='powercycle'),
|
path("<int:pk>/powercycle/", views.powercycle, name="powercycle"),
|
||||||
path('<int:pk>/poweroff/', views.poweroff, name='poweroff'),
|
path("<int:pk>/poweroff/", views.poweroff, name="poweroff"),
|
||||||
path('<int:pk>/suspend/', views.suspend, name='suspend'),
|
path("<int:pk>/suspend/", views.suspend, name="suspend"),
|
||||||
path('<int:pk>/resume/', views.resume, name='resume'),
|
path("<int:pk>/resume/", views.resume, name="resume"),
|
||||||
path('<int:pk>/force_off/', views.force_off, name='force_off'),
|
path("<int:pk>/force_off/", views.force_off, name="force_off"),
|
||||||
path('<int:pk>/destroy/', views.destroy, name='destroy'),
|
path("<int:pk>/destroy/", views.destroy, name="destroy"),
|
||||||
path('<int:pk>/migrate/', views.migrate, name='migrate'),
|
path("<int:pk>/migrate/", views.migrate, name="migrate"),
|
||||||
path('<int:pk>/status/', views.status, name='status'),
|
path("<int:pk>/status/", views.status, name="status"),
|
||||||
path('<int:pk>/stats/', views.stats, name='stats'),
|
path("<int:pk>/stats/", views.stats, name="stats"),
|
||||||
path('<int:pk>/osinfo/', views.osinfo, name='osinfo'),
|
path("<int:pk>/osinfo/", views.osinfo, name="osinfo"),
|
||||||
path('<int:pk>/rootpasswd/', views.set_root_pass, name='rootpasswd'),
|
path("<int:pk>/rootpasswd/", views.set_root_pass, name="rootpasswd"),
|
||||||
path('<int:pk>/add_public_key/', views.add_public_key, name='add_public_key'),
|
path("<int:pk>/add_public_key/", views.add_public_key, name="add_public_key"),
|
||||||
path('<int:pk>/resizevm_cpu/', views.resizevm_cpu, name='resizevm_cpu'),
|
path("<int:pk>/resizevm_cpu/", views.resizevm_cpu, name="resizevm_cpu"),
|
||||||
path('<int:pk>/resize_memory/', views.resize_memory, name='resize_memory'),
|
path("<int:pk>/resize_memory/", views.resize_memory, name="resize_memory"),
|
||||||
path('<int:pk>/resize_disk/', views.resize_disk, name='resize_disk'),
|
path("<int:pk>/resize_disk/", views.resize_disk, name="resize_disk"),
|
||||||
path('<int:pk>/add_new_vol/', views.add_new_vol, name='add_new_vol'),
|
path("<int:pk>/add_new_vol/", views.add_new_vol, name="add_new_vol"),
|
||||||
path('<int:pk>/delete_vol/', views.delete_vol, name='delete_vol'),
|
path("<int:pk>/delete_vol/", views.delete_vol, name="delete_vol"),
|
||||||
path('<int:pk>/add_owner/', views.add_owner, name='add_owner'),
|
path("<int:pk>/add_owner/", views.add_owner, name="add_owner"),
|
||||||
path('<int:pk>/add_existing_vol/', views.add_existing_vol, name='add_existing_vol'),
|
path("<int:pk>/add_existing_vol/", views.add_existing_vol, name="add_existing_vol"),
|
||||||
path('<int:pk>/edit_volume/', views.edit_volume, name='edit_volume'),
|
path("<int:pk>/edit_volume/", views.edit_volume, name="edit_volume"),
|
||||||
path('<int:pk>/detach_vol/', views.detach_vol, name='detach_vol'),
|
path("<int:pk>/detach_vol/", views.detach_vol, name="detach_vol"),
|
||||||
path('<int:pk>/add_cdrom/', views.add_cdrom, name='add_cdrom'),
|
path("<int:pk>/add_cdrom/", views.add_cdrom, name="add_cdrom"),
|
||||||
path('<int:pk>/detach_cdrom/<str:dev>/', views.detach_cdrom, name='detach_cdrom'),
|
path("<int:pk>/detach_cdrom/<str:dev>/", views.detach_cdrom, name="detach_cdrom"),
|
||||||
path('<int:pk>/unmount_iso/', views.unmount_iso, name='unmount_iso'),
|
path("<int:pk>/unmount_iso/", views.unmount_iso, name="unmount_iso"),
|
||||||
path('<int:pk>/mount_iso/', views.mount_iso, name='mount_iso'),
|
path("<int:pk>/mount_iso/", views.mount_iso, name="mount_iso"),
|
||||||
path('<int:pk>/snapshot/', views.snapshot, name='snapshot'),
|
path("<int:pk>/snapshot/", views.snapshot, name="snapshot"),
|
||||||
path('<int:pk>/delete_snapshot/', views.delete_snapshot, name='delete_snapshot'),
|
path("<int:pk>/delete_snapshot/", views.delete_snapshot, name="delete_snapshot"),
|
||||||
path('<int:pk>/revert_snapshot/', views.revert_snapshot, name='revert_snapshot'),
|
path("<int:pk>/revert_snapshot/", views.revert_snapshot, name="revert_snapshot"),
|
||||||
path('<int:pk>/set_vcpu/', views.set_vcpu, name='set_vcpu'),
|
path("<int:pk>/set_vcpu/", views.set_vcpu, name="set_vcpu"),
|
||||||
path('<int:pk>/set_vcpu_hotplug/', views.set_vcpu_hotplug, name='set_vcpu_hotplug'),
|
path("<int:pk>/set_vcpu_hotplug/", views.set_vcpu_hotplug, name="set_vcpu_hotplug"),
|
||||||
path('<int:pk>/set_autostart/', views.set_autostart, name='set_autostart'),
|
path("<int:pk>/set_autostart/", views.set_autostart, name="set_autostart"),
|
||||||
path('<int:pk>/unset_autostart/', views.unset_autostart, name='unset_autostart'),
|
path("<int:pk>/unset_autostart/", views.unset_autostart, name="unset_autostart"),
|
||||||
path('<int:pk>/set_bootmenu/', views.set_bootmenu, name='set_bootmenu'),
|
path("<int:pk>/set_bootmenu/", views.set_bootmenu, name="set_bootmenu"),
|
||||||
path('<int:pk>/unset_bootmenu/', views.unset_bootmenu, name='unset_bootmenu'),
|
path("<int:pk>/unset_bootmenu/", views.unset_bootmenu, name="unset_bootmenu"),
|
||||||
path('<int:pk>/set_bootorder/', views.set_bootorder, name='set_bootorder'),
|
path("<int:pk>/set_bootorder/", views.set_bootorder, name="set_bootorder"),
|
||||||
path('<int:pk>/change_xml/', views.change_xml, name='change_xml'),
|
path("<int:pk>/change_xml/", views.change_xml, name="change_xml"),
|
||||||
path('<int:pk>/set_guest_agent/', views.set_guest_agent, name='set_guest_agent'),
|
path("<int:pk>/set_guest_agent/", views.set_guest_agent, name="set_guest_agent"),
|
||||||
path('<int:pk>/set_video_model/', views.set_video_model, name='set_video_model'),
|
path("<int:pk>/set_video_model/", views.set_video_model, name="set_video_model"),
|
||||||
path('<int:pk>/change_network/', views.change_network, name='change_network'),
|
path("<int:pk>/change_network/", views.change_network, name="change_network"),
|
||||||
path('<int:pk>/add_network/', views.add_network, name='add_network'),
|
path("<int:pk>/add_network/", views.add_network, name="add_network"),
|
||||||
path('<int:pk>/delete_network/', views.delete_network, name='delete_network'),
|
path("<int:pk>/delete_network/", views.delete_network, name="delete_network"),
|
||||||
path('<int:pk>/set_link_state/', views.set_link_state, name='set_link_state'),
|
path("<int:pk>/set_link_state/", views.set_link_state, name="set_link_state"),
|
||||||
path('<int:pk>/set_qos/', views.set_qos, name='set_qos'),
|
path("<int:pk>/set_qos/", views.set_qos, name="set_qos"),
|
||||||
path('<int:pk>/unset_qos/', views.unset_qos, name='unset_qos'),
|
path("<int:pk>/unset_qos/", views.unset_qos, name="unset_qos"),
|
||||||
path('<int:pk>/del_owner/', views.del_owner, name='del_owner'), # no links to this one???
|
path(
|
||||||
path('<int:pk>/clone/', views.clone, name='clone'),
|
"<int:pk>/del_owner/", views.del_owner, name="del_owner"
|
||||||
path('<int:pk>/update_console/', views.update_console, name='update_console'),
|
), # no links to this one???
|
||||||
path('<int:pk>/change_options/', views.change_options, name='change_options'),
|
path("<int:pk>/clone/", views.clone, name="clone"),
|
||||||
path('<int:pk>/getvvfile/', views.getvvfile, name='getvvfile'), # no links to this one???
|
path("<int:pk>/update_console/", views.update_console, name="update_console"),
|
||||||
path('create/<int:compute_id>/', views.create_instance_select_type, name='create_instance_select_type'),
|
path("<int:pk>/change_options/", views.change_options, name="change_options"),
|
||||||
path('create/<int:compute_id>/<str:arch>/<str:machine>/', views.create_instance, name='create_instance'),
|
path(
|
||||||
path('guess_mac_address/<vname>/', views.guess_mac_address, name='guess_mac_address'),
|
"<int:pk>/getvvfile/", views.getvvfile, name="getvvfile"
|
||||||
path('guess_clone_name/', views.guess_clone_name, name='guess_clone_name'),
|
), # no links to this one???
|
||||||
path('random_mac_address/', views.random_mac_address, name='random_mac_address'),
|
path(
|
||||||
path('check_instance/<vname>/', views.check_instance, name='check_instance'),
|
"create/<int:compute_id>/",
|
||||||
path('<int:pk>/sshkeys/', views.sshkeys, name='sshkeys'),
|
views.create_instance_select_type,
|
||||||
|
name="create_instance_select_type",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"create/<int:compute_id>/<str:arch>/<str:machine>/",
|
||||||
|
views.create_instance,
|
||||||
|
name="create_instance",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"guess_mac_address/<vname>/", views.guess_mac_address, name="guess_mac_address"
|
||||||
|
),
|
||||||
|
path("guess_clone_name/", views.guess_clone_name, name="guess_clone_name"),
|
||||||
|
path("random_mac_address/", views.random_mac_address, name="random_mac_address"),
|
||||||
|
path("check_instance/<vname>/", views.check_instance, name="check_instance"),
|
||||||
|
path("<int:pk>/sshkeys/", views.sshkeys, name="sshkeys"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -38,8 +38,8 @@ def check_user_quota(user, instance, cpu, memory, disk_size):
|
||||||
instance += user_instances.count()
|
instance += user_instances.count()
|
||||||
for usr_inst in user_instances:
|
for usr_inst in user_instances:
|
||||||
if connection_manager.host_is_up(
|
if connection_manager.host_is_up(
|
||||||
usr_inst.instance.compute.type,
|
usr_inst.instance.compute.type,
|
||||||
usr_inst.instance.compute.hostname,
|
usr_inst.instance.compute.hostname,
|
||||||
):
|
):
|
||||||
conn = wvmInstance(
|
conn = wvmInstance(
|
||||||
usr_inst.instance.compute.hostname,
|
usr_inst.instance.compute.hostname,
|
||||||
|
@ -51,8 +51,8 @@ def check_user_quota(user, instance, cpu, memory, disk_size):
|
||||||
cpu += int(conn.get_vcpu())
|
cpu += int(conn.get_vcpu())
|
||||||
memory += int(conn.get_memory())
|
memory += int(conn.get_memory())
|
||||||
for disk in conn.get_disk_devices():
|
for disk in conn.get_disk_devices():
|
||||||
if disk['size']:
|
if disk["size"]:
|
||||||
disk_size += int(disk['size']) >> 30
|
disk_size += int(disk["size"]) >> 30
|
||||||
|
|
||||||
if ua.max_instances > 0 and instance > ua.max_instances:
|
if ua.max_instances > 0 and instance > ua.max_instances:
|
||||||
msg = "instance"
|
msg = "instance"
|
||||||
|
@ -86,17 +86,17 @@ def get_new_disk_dev(media, disks, bus):
|
||||||
dev_base = "sd"
|
dev_base = "sd"
|
||||||
|
|
||||||
if disks:
|
if disks:
|
||||||
existing_disk_devs = [disk['dev'] for disk in disks]
|
existing_disk_devs = [disk["dev"] for disk in disks]
|
||||||
|
|
||||||
# cd-rom bus could be virtio/sata, because of that we should check it also
|
# cd-rom bus could be virtio/sata, because of that we should check it also
|
||||||
if media:
|
if media:
|
||||||
existing_media_devs = [m['dev'] for m in media]
|
existing_media_devs = [m["dev"] for m in media]
|
||||||
|
|
||||||
for al in string.ascii_lowercase:
|
for al in string.ascii_lowercase:
|
||||||
dev = dev_base + al
|
dev = dev_base + al
|
||||||
if dev not in existing_disk_devs and dev not in existing_media_devs:
|
if dev not in existing_disk_devs and dev not in existing_media_devs:
|
||||||
return dev
|
return dev
|
||||||
raise Exception(_('None available device name'))
|
raise Exception(_("None available device name"))
|
||||||
|
|
||||||
|
|
||||||
def get_network_tuple(network_source_str):
|
def get_network_tuple(network_source_str):
|
||||||
|
@ -104,7 +104,7 @@ def get_network_tuple(network_source_str):
|
||||||
if len(network_source_pack) > 1:
|
if len(network_source_pack) > 1:
|
||||||
return network_source_pack[1], network_source_pack[0]
|
return network_source_pack[1], network_source_pack[0]
|
||||||
else:
|
else:
|
||||||
return network_source_pack[0], 'net'
|
return network_source_pack[0], "net"
|
||||||
|
|
||||||
|
|
||||||
def migrate_instance(
|
def migrate_instance(
|
||||||
|
@ -174,44 +174,44 @@ def refr(compute):
|
||||||
Instance.objects.filter(compute=compute).exclude(name__in=domain_names).delete()
|
Instance.objects.filter(compute=compute).exclude(name__in=domain_names).delete()
|
||||||
Instance.objects.filter(compute=compute).exclude(uuid__in=domain_uuids).delete()
|
Instance.objects.filter(compute=compute).exclude(uuid__in=domain_uuids).delete()
|
||||||
# Create instances that're not in DB
|
# Create instances that're not in DB
|
||||||
names = Instance.objects.filter(compute=compute).values_list('name', flat=True)
|
names = Instance.objects.filter(compute=compute).values_list("name", flat=True)
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
if domain.name() not in names:
|
if domain.name() not in names:
|
||||||
Instance(compute=compute, name=domain.name(), uuid=domain.UUIDString()).save()
|
Instance(compute=compute, name=domain.name(), uuid=domain.UUIDString()).save()
|
||||||
|
|
||||||
|
|
||||||
def get_dhcp_mac_address(vname):
|
def get_dhcp_mac_address(vname):
|
||||||
dhcp_file = str(settings.BASE_DIR) + '/dhcpd.conf'
|
dhcp_file = str(settings.BASE_DIR) + "/dhcpd.conf"
|
||||||
mac = ''
|
mac = ""
|
||||||
if os.path.isfile(dhcp_file):
|
if os.path.isfile(dhcp_file):
|
||||||
with open(dhcp_file, 'r') as f:
|
with open(dhcp_file, "r") as f:
|
||||||
name_found = False
|
name_found = False
|
||||||
for line in f:
|
for line in f:
|
||||||
if "host %s." % vname in line:
|
if "host %s." % vname in line:
|
||||||
name_found = True
|
name_found = True
|
||||||
if name_found and "hardware ethernet" in line:
|
if name_found and "hardware ethernet" in line:
|
||||||
mac = line.split(' ')[-1].strip().strip(';')
|
mac = line.split(" ")[-1].strip().strip(";")
|
||||||
break
|
break
|
||||||
return mac
|
return mac
|
||||||
|
|
||||||
|
|
||||||
def get_random_mac_address():
|
def get_random_mac_address():
|
||||||
mac = '52:54:00:%02x:%02x:%02x' % (
|
mac = "52:54:00:%02x:%02x:%02x" % (
|
||||||
random.randint(0x00, 0xff),
|
random.randint(0x00, 0xFF),
|
||||||
random.randint(0x00, 0xff),
|
random.randint(0x00, 0xFF),
|
||||||
random.randint(0x00, 0xff),
|
random.randint(0x00, 0xFF),
|
||||||
)
|
)
|
||||||
return mac
|
return mac
|
||||||
|
|
||||||
|
|
||||||
def get_clone_disk_name(disk, prefix, clone_name=''):
|
def get_clone_disk_name(disk, prefix, clone_name=""):
|
||||||
if not disk['image']:
|
if not disk["image"]:
|
||||||
return None
|
return None
|
||||||
if disk['image'].startswith(prefix) and clone_name:
|
if disk["image"].startswith(prefix) and clone_name:
|
||||||
suffix = disk['image'][len(prefix):]
|
suffix = disk["image"][len(prefix) :]
|
||||||
image = f"{clone_name}{suffix}"
|
image = f"{clone_name}{suffix}"
|
||||||
elif "." in disk['image'] and len(disk['image'].rsplit(".", 1)[1]) <= 7:
|
elif "." in disk["image"] and len(disk["image"].rsplit(".", 1)[1]) <= 7:
|
||||||
name, suffix = disk['image'].rsplit(".", 1)
|
name, suffix = disk["image"].rsplit(".", 1)
|
||||||
image = f"{name}-clone.{suffix}"
|
image = f"{name}-clone.{suffix}"
|
||||||
else:
|
else:
|
||||||
image = f"{disk['image']}-clone"
|
image = f"{disk['image']}-clone"
|
||||||
|
|
|
@ -21,7 +21,11 @@ from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from instances.models import Instance
|
from instances.models import Instance
|
||||||
from libvirt import VIR_DOMAIN_UNDEFINE_KEEP_NVRAM, VIR_DOMAIN_UNDEFINE_NVRAM, libvirtError
|
from libvirt import (
|
||||||
|
VIR_DOMAIN_UNDEFINE_KEEP_NVRAM,
|
||||||
|
VIR_DOMAIN_UNDEFINE_NVRAM,
|
||||||
|
libvirtError,
|
||||||
|
)
|
||||||
from logs.views import addlogmsg
|
from logs.views import addlogmsg
|
||||||
from vrtManager import util
|
from vrtManager import util
|
||||||
from vrtManager.create import wvmCreate
|
from vrtManager.create import wvmCreate
|
||||||
|
@ -50,9 +54,13 @@ def index(request):
|
||||||
if request.user.is_superuser or request.user.has_perm("instances.view_instances"):
|
if request.user.is_superuser or request.user.has_perm("instances.view_instances"):
|
||||||
instances = Instance.objects.all().prefetch_related("userinstance_set")
|
instances = Instance.objects.all().prefetch_related("userinstance_set")
|
||||||
else:
|
else:
|
||||||
instances = Instance.objects.filter(userinstance__user=request.user).prefetch_related("userinstance_set")
|
instances = Instance.objects.filter(
|
||||||
|
userinstance__user=request.user
|
||||||
|
).prefetch_related("userinstance_set")
|
||||||
|
|
||||||
return render(request, "allinstances.html", {"computes": computes, "instances": instances})
|
return render(
|
||||||
|
request, "allinstances.html", {"computes": computes, "instances": instances}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def instance(request, pk):
|
def instance(request, pk):
|
||||||
|
@ -63,7 +71,9 @@ def instance(request, pk):
|
||||||
users = User.objects.all().order_by("username")
|
users = User.objects.all().order_by("username")
|
||||||
publickeys = UserSSHKey.objects.filter(user_id=request.user.id)
|
publickeys = UserSSHKey.objects.filter(user_id=request.user.id)
|
||||||
keymaps = settings.QEMU_KEYMAPS
|
keymaps = settings.QEMU_KEYMAPS
|
||||||
console_types = AppSettings.objects.get(key="QEMU_CONSOLE_DEFAULT_TYPE").choices_as_list()
|
console_types = AppSettings.objects.get(
|
||||||
|
key="QEMU_CONSOLE_DEFAULT_TYPE"
|
||||||
|
).choices_as_list()
|
||||||
console_form = ConsoleForm(
|
console_form = ConsoleForm(
|
||||||
initial={
|
initial={
|
||||||
"type": instance.console_type,
|
"type": instance.console_type,
|
||||||
|
@ -74,10 +84,14 @@ def instance(request, pk):
|
||||||
)
|
)
|
||||||
console_listener_addresses = settings.QEMU_CONSOLE_LISTENER_ADDRESSES
|
console_listener_addresses = settings.QEMU_CONSOLE_LISTENER_ADDRESSES
|
||||||
bottom_bar = app_settings.VIEW_INSTANCE_DETAIL_BOTTOM_BAR
|
bottom_bar = app_settings.VIEW_INSTANCE_DETAIL_BOTTOM_BAR
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
userinstance = UserInstance.objects.get(
|
userinstance = UserInstance.objects.get(
|
||||||
instance__compute_id=compute.id, instance__name=instance.name, user__id=request.user.id
|
instance__compute_id=compute.id,
|
||||||
|
instance__name=instance.name,
|
||||||
|
user__id=request.user.id,
|
||||||
)
|
)
|
||||||
except UserInstance.DoesNotExist:
|
except UserInstance.DoesNotExist:
|
||||||
userinstance = None
|
userinstance = None
|
||||||
|
@ -116,7 +130,9 @@ def instance(request, pk):
|
||||||
|
|
||||||
# userinstances = UserInstance.objects.filter(instance=instance).order_by('user__username')
|
# userinstances = UserInstance.objects.filter(instance=instance).order_by('user__username')
|
||||||
userinstances = instance.userinstance_set.order_by("user__username")
|
userinstances = instance.userinstance_set.order_by("user__username")
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
|
|
||||||
# Host resources
|
# Host resources
|
||||||
vcpu_host = len(instance.vcpu_range)
|
vcpu_host = len(instance.vcpu_range)
|
||||||
|
@ -128,7 +144,7 @@ def instance(request, pk):
|
||||||
storages_host = sorted(instance.proxy.get_storages(True))
|
storages_host = sorted(instance.proxy.get_storages(True))
|
||||||
net_models_host = instance.proxy.get_network_models()
|
net_models_host = instance.proxy.get_network_models()
|
||||||
|
|
||||||
if app_settings.VM_DRBD_STATUS == 'True':
|
if app_settings.VM_DRBD_STATUS == "True":
|
||||||
instance.drbd = drbd_status(request, pk)
|
instance.drbd = drbd_status(request, pk)
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
|
@ -139,17 +155,25 @@ def status(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
return JsonResponse({"status": instance.proxy.get_status()})
|
return JsonResponse({"status": instance.proxy.get_status()})
|
||||||
|
|
||||||
|
|
||||||
def drbd_status(request, pk):
|
def drbd_status(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
result = "None DRBD"
|
result = "None DRBD"
|
||||||
|
|
||||||
if instance.compute.type == 2:
|
if instance.compute.type == 2:
|
||||||
conn = instance.compute.login + "@" + instance.compute.hostname
|
conn = instance.compute.login + "@" + instance.compute.hostname
|
||||||
remoteDrbdStatus = subprocess.run(["ssh", conn, "sudo", "drbdadm", "status", "&&", "exit"], stdout=subprocess.PIPE, text=True)
|
remoteDrbdStatus = subprocess.run(
|
||||||
|
["ssh", conn, "sudo", "drbdadm", "status", "&&", "exit"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
if remoteDrbdStatus.stdout:
|
if remoteDrbdStatus.stdout:
|
||||||
try:
|
try:
|
||||||
instanceFindDrbd = re.compile(instance.name + '[_]*[A-Z]* role:(.+?)\n disk:(.+?)\n', re.IGNORECASE)
|
instanceFindDrbd = re.compile(
|
||||||
|
instance.name + "[_]*[A-Z]* role:(.+?)\n disk:(.+?)\n",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
instanceDrbd = instanceFindDrbd.findall(remoteDrbdStatus.stdout)
|
instanceDrbd = instanceFindDrbd.findall(remoteDrbdStatus.stdout)
|
||||||
|
|
||||||
primaryCount = 0
|
primaryCount = 0
|
||||||
|
@ -179,6 +203,7 @@ def drbd_status(request, pk):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def stats(request, pk):
|
def stats(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
json_blk = []
|
json_blk = []
|
||||||
|
@ -192,10 +217,20 @@ def stats(request, pk):
|
||||||
|
|
||||||
current_time = time.strftime("%H:%M:%S")
|
current_time = time.strftime("%H:%M:%S")
|
||||||
for blk in blk_usage:
|
for blk in blk_usage:
|
||||||
json_blk.append({"dev": blk["dev"], "data": [int(blk["rd"]) / 1048576, int(blk["wr"]) / 1048576]})
|
json_blk.append(
|
||||||
|
{
|
||||||
|
"dev": blk["dev"],
|
||||||
|
"data": [int(blk["rd"]) / 1048576, int(blk["wr"]) / 1048576],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
for net in net_usage:
|
for net in net_usage:
|
||||||
json_net.append({"dev": net["dev"], "data": [int(net["rx"]) / 1048576, int(net["tx"]) / 1048576]})
|
json_net.append(
|
||||||
|
{
|
||||||
|
"dev": net["dev"],
|
||||||
|
"data": [int(net["rx"]) / 1048576, int(net["tx"]) / 1048576],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
{
|
{
|
||||||
|
@ -207,12 +242,14 @@ def stats(request, pk):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def osinfo(request, pk):
|
def osinfo(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
results = instance.proxy.osinfo()
|
results = instance.proxy.osinfo()
|
||||||
|
|
||||||
return JsonResponse(results)
|
return JsonResponse(results)
|
||||||
|
|
||||||
|
|
||||||
def guess_mac_address(request, vname):
|
def guess_mac_address(request, vname):
|
||||||
data = {"vname": vname}
|
data = {"vname": vname}
|
||||||
mac = utils.get_dhcp_mac_address(vname)
|
mac = utils.get_dhcp_mac_address(vname)
|
||||||
|
@ -232,7 +269,9 @@ def guess_clone_name(request):
|
||||||
dhcp_file = "/srv/webvirtcloud/dhcpd.conf"
|
dhcp_file = "/srv/webvirtcloud/dhcpd.conf"
|
||||||
prefix = app_settings.CLONE_INSTANCE_DEFAULT_PREFIX
|
prefix = app_settings.CLONE_INSTANCE_DEFAULT_PREFIX
|
||||||
if os.path.isfile(dhcp_file):
|
if os.path.isfile(dhcp_file):
|
||||||
instance_names = [i.name for i in Instance.objects.filter(name__startswith=prefix)]
|
instance_names = [
|
||||||
|
i.name for i in Instance.objects.filter(name__startswith=prefix)
|
||||||
|
]
|
||||||
with open(dhcp_file, "r") as f:
|
with open(dhcp_file, "r") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
|
@ -281,7 +320,11 @@ def get_instance(user, pk):
|
||||||
instance = get_object_or_404(Instance, pk=pk)
|
instance = get_object_or_404(Instance, pk=pk)
|
||||||
user_instances = user.userinstance_set.all().values_list("instance", flat=True)
|
user_instances = user.userinstance_set.all().values_list("instance", flat=True)
|
||||||
|
|
||||||
if user.is_superuser or user.has_perm("instances.view_instances") or instance.id in user_instances:
|
if (
|
||||||
|
user.is_superuser
|
||||||
|
or user.has_perm("instances.view_instances")
|
||||||
|
or instance.id in user_instances
|
||||||
|
):
|
||||||
return instance
|
return instance
|
||||||
else:
|
else:
|
||||||
raise Http404()
|
raise Http404()
|
||||||
|
@ -293,7 +336,9 @@ def poweron(request, pk):
|
||||||
messages.warning(request, _("Templates cannot be started."))
|
messages.warning(request, _("Templates cannot be started."))
|
||||||
else:
|
else:
|
||||||
instance.proxy.start()
|
instance.proxy.start()
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, _("Power On"))
|
addlogmsg(
|
||||||
|
request.user.username, instance.compute.name, instance.name, _("Power On")
|
||||||
|
)
|
||||||
|
|
||||||
return redirect(request.META.get("HTTP_REFERER"))
|
return redirect(request.META.get("HTTP_REFERER"))
|
||||||
|
|
||||||
|
@ -302,14 +347,18 @@ def powercycle(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
instance.proxy.force_shutdown()
|
instance.proxy.force_shutdown()
|
||||||
instance.proxy.start()
|
instance.proxy.start()
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, _("Power Cycle"))
|
addlogmsg(
|
||||||
|
request.user.username, instance.compute.name, instance.name, _("Power Cycle")
|
||||||
|
)
|
||||||
return redirect(request.META.get("HTTP_REFERER"))
|
return redirect(request.META.get("HTTP_REFERER"))
|
||||||
|
|
||||||
|
|
||||||
def poweroff(request, pk):
|
def poweroff(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
instance.proxy.shutdown()
|
instance.proxy.shutdown()
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, _("Power Off"))
|
addlogmsg(
|
||||||
|
request.user.username, instance.compute.name, instance.name, _("Power Off")
|
||||||
|
)
|
||||||
|
|
||||||
return redirect(request.META.get("HTTP_REFERER"))
|
return redirect(request.META.get("HTTP_REFERER"))
|
||||||
|
|
||||||
|
@ -333,7 +382,9 @@ def resume(request, pk):
|
||||||
def force_off(request, pk):
|
def force_off(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
instance.proxy.force_shutdown()
|
instance.proxy.force_shutdown()
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, _("Force Off"))
|
addlogmsg(
|
||||||
|
request.user.username, instance.compute.name, instance.name, _("Force Off")
|
||||||
|
)
|
||||||
return redirect(request.META.get("HTTP_REFERER"))
|
return redirect(request.META.get("HTTP_REFERER"))
|
||||||
|
|
||||||
|
|
||||||
|
@ -349,7 +400,9 @@ def destroy(request, pk):
|
||||||
instance.proxy.force_shutdown()
|
instance.proxy.force_shutdown()
|
||||||
|
|
||||||
if request.POST.get("delete_disk", ""):
|
if request.POST.get("delete_disk", ""):
|
||||||
snapshots = sorted(instance.proxy.get_snapshot(), reverse=True, key=lambda k: k["date"])
|
snapshots = sorted(
|
||||||
|
instance.proxy.get_snapshot(), reverse=True, key=lambda k: k["date"]
|
||||||
|
)
|
||||||
for snapshot in snapshots:
|
for snapshot in snapshots:
|
||||||
instance.proxy.snapshot_delete(snapshot["name"])
|
instance.proxy.snapshot_delete(snapshot["name"])
|
||||||
instance.proxy.delete_all_disks()
|
instance.proxy.delete_all_disks()
|
||||||
|
@ -360,7 +413,9 @@ def destroy(request, pk):
|
||||||
instance.proxy.delete(VIR_DOMAIN_UNDEFINE_KEEP_NVRAM)
|
instance.proxy.delete(VIR_DOMAIN_UNDEFINE_KEEP_NVRAM)
|
||||||
|
|
||||||
instance.delete()
|
instance.delete()
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, _("Destroy"))
|
addlogmsg(
|
||||||
|
request.user.username, instance.compute.name, instance.name, _("Destroy")
|
||||||
|
)
|
||||||
return redirect(reverse("instances:index"))
|
return redirect(reverse("instances:index"))
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
|
@ -390,12 +445,26 @@ def migrate(request, pk):
|
||||||
target_host = Compute.objects.get(id=compute_id)
|
target_host = Compute.objects.get(id=compute_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.migrate_instance(target_host, instance, request.user, live, unsafe, xml_del, offline, autoconverge, compress, postcopy)
|
utils.migrate_instance(
|
||||||
|
target_host,
|
||||||
|
instance,
|
||||||
|
request.user,
|
||||||
|
live,
|
||||||
|
unsafe,
|
||||||
|
xml_del,
|
||||||
|
offline,
|
||||||
|
autoconverge,
|
||||||
|
compress,
|
||||||
|
postcopy,
|
||||||
|
)
|
||||||
except libvirtError as err:
|
except libvirtError as err:
|
||||||
messages.error(request, err)
|
messages.error(request, err)
|
||||||
|
|
||||||
migration_method = "live" if live is True else "offline"
|
migration_method = "live" if live is True else "offline"
|
||||||
msg = _("Instance is migrated(%(method)s) to %(hostname)s") % {"hostname": target_host.hostname, "method": migration_method}
|
msg = _("Instance is migrated(%(method)s) to %(hostname)s") % {
|
||||||
|
"hostname": target_host.hostname,
|
||||||
|
"method": migration_method,
|
||||||
|
}
|
||||||
addlogmsg(request.user.username, current_host, instance.name, msg)
|
addlogmsg(request.user.username, current_host, instance.name, msg)
|
||||||
|
|
||||||
return redirect(request.META.get("HTTP_REFERER"))
|
return redirect(request.META.get("HTTP_REFERER"))
|
||||||
|
@ -419,7 +488,9 @@ def set_root_pass(request, pk):
|
||||||
s.close()
|
s.close()
|
||||||
if result["return"] == "success":
|
if result["return"] == "success":
|
||||||
msg = _("Reset root password")
|
msg = _("Reset root password")
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
addlogmsg(
|
||||||
|
request.user.username, instance.compute.name, instance.name, msg
|
||||||
|
)
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
else:
|
else:
|
||||||
messages.error(request, result["message"])
|
messages.error(request, result["message"])
|
||||||
|
@ -434,7 +505,11 @@ def add_public_key(request, pk):
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
sshkeyid = request.POST.get("sshkeyid", "")
|
sshkeyid = request.POST.get("sshkeyid", "")
|
||||||
publickey = UserSSHKey.objects.get(id=sshkeyid)
|
publickey = UserSSHKey.objects.get(id=sshkeyid)
|
||||||
data = {"action": "publickey", "key": publickey.keypublic, "vname": instance.name}
|
data = {
|
||||||
|
"action": "publickey",
|
||||||
|
"key": publickey.keypublic,
|
||||||
|
"vname": instance.name,
|
||||||
|
}
|
||||||
|
|
||||||
if instance.proxy.get_status() == 5:
|
if instance.proxy.get_status() == 5:
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
@ -445,7 +520,9 @@ def add_public_key(request, pk):
|
||||||
if result["return"] == "error":
|
if result["return"] == "error":
|
||||||
msg = result["message"]
|
msg = result["message"]
|
||||||
else:
|
else:
|
||||||
msg = _("Installed new SSH public key %(keyname)s") % {"keyname": publickey.keyname}
|
msg = _("Installed new SSH public key %(keyname)s") % {
|
||||||
|
"keyname": publickey.keyname
|
||||||
|
}
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
||||||
|
|
||||||
if result["return"] == "success":
|
if result["return"] == "success":
|
||||||
|
@ -470,9 +547,13 @@ def resizevm_cpu(request, pk):
|
||||||
new_vcpu = request.POST.get("vcpu", "")
|
new_vcpu = request.POST.get("vcpu", "")
|
||||||
new_cur_vcpu = request.POST.get("cur_vcpu", "")
|
new_cur_vcpu = request.POST.get("cur_vcpu", "")
|
||||||
|
|
||||||
quota_msg = utils.check_user_quota(request.user, 0, int(new_vcpu) - vcpu, 0, 0)
|
quota_msg = utils.check_user_quota(
|
||||||
|
request.user, 0, int(new_vcpu) - vcpu, 0, 0
|
||||||
|
)
|
||||||
if not request.user.is_superuser and quota_msg:
|
if not request.user.is_superuser and quota_msg:
|
||||||
msg = _("User %(quota_msg)s quota reached, cannot resize CPU of '%(instance_name)s'!") % {
|
msg = _(
|
||||||
|
"User %(quota_msg)s quota reached, cannot resize CPU of '%(instance_name)s'!"
|
||||||
|
) % {
|
||||||
"quota_msg": quota_msg,
|
"quota_msg": quota_msg,
|
||||||
"instance_name": instance.name,
|
"instance_name": instance.name,
|
||||||
}
|
}
|
||||||
|
@ -481,8 +562,13 @@ def resizevm_cpu(request, pk):
|
||||||
cur_vcpu = new_cur_vcpu
|
cur_vcpu = new_cur_vcpu
|
||||||
vcpu = new_vcpu
|
vcpu = new_vcpu
|
||||||
instance.proxy.resize_cpu(cur_vcpu, vcpu)
|
instance.proxy.resize_cpu(cur_vcpu, vcpu)
|
||||||
msg = _("CPU is resized: %(old)s to %(new)s") % {"old": cur_vcpu, "new": vcpu}
|
msg = _("CPU is resized: %(old)s to %(new)s") % {
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
"old": cur_vcpu,
|
||||||
|
"new": vcpu,
|
||||||
|
}
|
||||||
|
addlogmsg(
|
||||||
|
request.user.username, instance.compute.name, instance.name, msg
|
||||||
|
)
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
return redirect(reverse("instances:instance", args=[instance.id]) + "#resize")
|
return redirect(reverse("instances:instance", args=[instance.id]) + "#resize")
|
||||||
|
|
||||||
|
@ -507,22 +593,30 @@ def resize_memory(request, pk):
|
||||||
new_cur_memory_custom = request.POST.get("cur_memory_custom", "")
|
new_cur_memory_custom = request.POST.get("cur_memory_custom", "")
|
||||||
if new_cur_memory_custom:
|
if new_cur_memory_custom:
|
||||||
new_cur_memory = new_cur_memory_custom
|
new_cur_memory = new_cur_memory_custom
|
||||||
quota_msg = utils.check_user_quota(request.user, 0, 0, int(new_memory) - memory, 0)
|
quota_msg = utils.check_user_quota(
|
||||||
|
request.user, 0, 0, int(new_memory) - memory, 0
|
||||||
|
)
|
||||||
if not request.user.is_superuser and quota_msg:
|
if not request.user.is_superuser and quota_msg:
|
||||||
msg = _("User %(quota_msg)s quota reached, cannot resize memory of '%(instance_name)s'!") % {
|
msg = _(
|
||||||
|
"User %(quota_msg)s quota reached, cannot resize memory of '%(instance_name)s'!"
|
||||||
|
) % {
|
||||||
"quota_msg": quota_msg,
|
"quota_msg": quota_msg,
|
||||||
"instance_name": instance.name,
|
"instance_name": instance.name,
|
||||||
}
|
}
|
||||||
messages.error(request, msg)
|
messages.error(request, msg)
|
||||||
else:
|
else:
|
||||||
instance.proxy.resize_mem(new_cur_memory, new_memory)
|
instance.proxy.resize_mem(new_cur_memory, new_memory)
|
||||||
msg = _("Memory is resized: current/max: %(old_cur)s/%(old_max)s to %(new_cur)s/%(new_max)s") % {
|
msg = _(
|
||||||
|
"Memory is resized: current/max: %(old_cur)s/%(old_max)s to %(new_cur)s/%(new_max)s"
|
||||||
|
) % {
|
||||||
"old_cur": cur_memory,
|
"old_cur": cur_memory,
|
||||||
"old_max": memory,
|
"old_max": memory,
|
||||||
"new_cur": new_cur_memory,
|
"new_cur": new_cur_memory,
|
||||||
"new_max": new_memory,
|
"new_max": new_memory,
|
||||||
}
|
}
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
addlogmsg(
|
||||||
|
request.user.username, instance.compute.name, instance.name, msg
|
||||||
|
)
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
|
|
||||||
return redirect(reverse("instances:instance", args=[instance.id]) + "#resize")
|
return redirect(reverse("instances:instance", args=[instance.id]) + "#resize")
|
||||||
|
@ -542,15 +636,21 @@ def resize_disk(request, pk):
|
||||||
if request.user.is_superuser or request.user.is_staff or userinstance.is_change:
|
if request.user.is_superuser or request.user.is_staff or userinstance.is_change:
|
||||||
disks_new = list()
|
disks_new = list()
|
||||||
for disk in disks:
|
for disk in disks:
|
||||||
input_disk_size = int(request.POST.get("disk_size_" + disk["dev"], "0")) * 1073741824
|
input_disk_size = (
|
||||||
|
int(request.POST.get("disk_size_" + disk["dev"], "0")) * 1073741824
|
||||||
|
)
|
||||||
if input_disk_size > disk["size"] + (64 << 20):
|
if input_disk_size > disk["size"] + (64 << 20):
|
||||||
disk["size_new"] = input_disk_size
|
disk["size_new"] = input_disk_size
|
||||||
disks_new.append(disk)
|
disks_new.append(disk)
|
||||||
disk_sum = sum([disk["size"] >> 30 for disk in disks_new])
|
disk_sum = sum([disk["size"] >> 30 for disk in disks_new])
|
||||||
disk_new_sum = sum([disk["size_new"] >> 30 for disk in disks_new])
|
disk_new_sum = sum([disk["size_new"] >> 30 for disk in disks_new])
|
||||||
quota_msg = utils.check_user_quota(request.user, 0, 0, 0, disk_new_sum - disk_sum)
|
quota_msg = utils.check_user_quota(
|
||||||
|
request.user, 0, 0, 0, disk_new_sum - disk_sum
|
||||||
|
)
|
||||||
if not request.user.is_superuser and quota_msg:
|
if not request.user.is_superuser and quota_msg:
|
||||||
msg = _("User %(quota_msg)s quota reached, cannot resize disks of '%(instance_name)s'!") % {
|
msg = _(
|
||||||
|
"User %(quota_msg)s quota reached, cannot resize disks of '%(instance_name)s'!"
|
||||||
|
) % {
|
||||||
"quota_msg": quota_msg,
|
"quota_msg": quota_msg,
|
||||||
"instance_name": instance.name,
|
"instance_name": instance.name,
|
||||||
}
|
}
|
||||||
|
@ -558,7 +658,9 @@ def resize_disk(request, pk):
|
||||||
else:
|
else:
|
||||||
instance.proxy.resize_disk(disks_new)
|
instance.proxy.resize_disk(disks_new)
|
||||||
msg = _("Disk is resized: %(dev)s") % {"dev": disk["dev"]}
|
msg = _("Disk is resized: %(dev)s") % {"dev": disk["dev"]}
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
addlogmsg(
|
||||||
|
request.user.username, instance.compute.name, instance.name, msg
|
||||||
|
)
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
|
|
||||||
return redirect(reverse("instances:instance", args=[instance.id]) + "#resize")
|
return redirect(reverse("instances:instance", args=[instance.id]) + "#resize")
|
||||||
|
@ -566,7 +668,9 @@ def resize_disk(request, pk):
|
||||||
|
|
||||||
def add_new_vol(request, pk):
|
def add_new_vol(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
|
|
||||||
if allow_admin_or_not_template:
|
if allow_admin_or_not_template:
|
||||||
media = instance.proxy.get_media_devices()
|
media = instance.proxy.get_media_devices()
|
||||||
|
@ -607,20 +711,34 @@ def add_new_vol(request, pk):
|
||||||
pool_type = conn_pool.get_type()
|
pool_type = conn_pool.get_type()
|
||||||
disk_type = conn_pool.get_volume_type(os.path.basename(source))
|
disk_type = conn_pool.get_volume_type(os.path.basename(source))
|
||||||
|
|
||||||
if pool_type == 'rbd':
|
if pool_type == "rbd":
|
||||||
source_info = conn_pool.get_rbd_source()
|
source_info = conn_pool.get_rbd_source()
|
||||||
else: # add more disk types to handle different pool and disk types
|
else: # add more disk types to handle different pool and disk types
|
||||||
source_info = None
|
source_info = None
|
||||||
|
|
||||||
instance.proxy.attach_disk(target_dev, source, source_info=source_info, pool_type=pool_type, disk_type=disk_type, target_bus=bus, format_type=format, cache_mode=cache)
|
instance.proxy.attach_disk(
|
||||||
msg = _("Attach new disk: %(name)s (%(format)s)") % {"name": name, "format": format}
|
target_dev,
|
||||||
|
source,
|
||||||
|
source_info=source_info,
|
||||||
|
pool_type=pool_type,
|
||||||
|
disk_type=disk_type,
|
||||||
|
target_bus=bus,
|
||||||
|
format_type=format,
|
||||||
|
cache_mode=cache,
|
||||||
|
)
|
||||||
|
msg = _("Attach new disk: %(name)s (%(format)s)") % {
|
||||||
|
"name": name,
|
||||||
|
"format": format,
|
||||||
|
}
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
||||||
return redirect(request.META.get("HTTP_REFERER") + "#disks")
|
return redirect(request.META.get("HTTP_REFERER") + "#disks")
|
||||||
|
|
||||||
|
|
||||||
def add_existing_vol(request, pk):
|
def add_existing_vol(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
if allow_admin_or_not_template:
|
if allow_admin_or_not_template:
|
||||||
storage = request.POST.get("selected_storage", "")
|
storage = request.POST.get("selected_storage", "")
|
||||||
name = request.POST.get("vols", "")
|
name = request.POST.get("vols", "")
|
||||||
|
@ -641,7 +759,7 @@ def add_existing_vol(request, pk):
|
||||||
format_type = conn_create.get_volume_format_type(name)
|
format_type = conn_create.get_volume_format_type(name)
|
||||||
disk_type = conn_create.get_volume_type(name)
|
disk_type = conn_create.get_volume_type(name)
|
||||||
pool_type = conn_create.get_type()
|
pool_type = conn_create.get_type()
|
||||||
if pool_type == 'rbd':
|
if pool_type == "rbd":
|
||||||
source_info = conn_create.get_rbd_source()
|
source_info = conn_create.get_rbd_source()
|
||||||
path = conn_create.get_source_name()
|
path = conn_create.get_source_name()
|
||||||
else:
|
else:
|
||||||
|
@ -651,7 +769,16 @@ def add_existing_vol(request, pk):
|
||||||
target_dev = utils.get_new_disk_dev(media, disks, bus)
|
target_dev = utils.get_new_disk_dev(media, disks, bus)
|
||||||
source = f"{path}/{name}"
|
source = f"{path}/{name}"
|
||||||
|
|
||||||
instance.proxy.attach_disk(target_dev, source, source_info=source_info, pool_type=pool_type, disk_type=disk_type, target_bus=bus, format_type=format_type, cache_mode=cache)
|
instance.proxy.attach_disk(
|
||||||
|
target_dev,
|
||||||
|
source,
|
||||||
|
source_info=source_info,
|
||||||
|
pool_type=pool_type,
|
||||||
|
disk_type=disk_type,
|
||||||
|
target_bus=bus,
|
||||||
|
format_type=format_type,
|
||||||
|
cache_mode=cache,
|
||||||
|
)
|
||||||
msg = _("Attach Existing disk: %(target_dev)s") % {"target_dev": target_dev}
|
msg = _("Attach Existing disk: %(target_dev)s") % {"target_dev": target_dev}
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
||||||
return redirect(request.META.get("HTTP_REFERER") + "#disks")
|
return redirect(request.META.get("HTTP_REFERER") + "#disks")
|
||||||
|
@ -659,7 +786,9 @@ def add_existing_vol(request, pk):
|
||||||
|
|
||||||
def edit_volume(request, pk):
|
def edit_volume(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
if "edit_volume" in request.POST and allow_admin_or_not_template:
|
if "edit_volume" in request.POST and allow_admin_or_not_template:
|
||||||
target_dev = request.POST.get("dev", "")
|
target_dev = request.POST.get("dev", "")
|
||||||
|
|
||||||
|
@ -671,10 +800,16 @@ def edit_volume(request, pk):
|
||||||
new_bus = request.POST.get("vol_bus", bus)
|
new_bus = request.POST.get("vol_bus", bus)
|
||||||
serial = request.POST.get("vol_serial", "")
|
serial = request.POST.get("vol_serial", "")
|
||||||
format = request.POST.get("vol_format", "")
|
format = request.POST.get("vol_format", "")
|
||||||
cache = request.POST.get("vol_cache", app_settings.INSTANCE_VOLUME_DEFAULT_CACHE)
|
cache = request.POST.get(
|
||||||
|
"vol_cache", app_settings.INSTANCE_VOLUME_DEFAULT_CACHE
|
||||||
|
)
|
||||||
io = request.POST.get("vol_io_mode", app_settings.INSTANCE_VOLUME_DEFAULT_IO)
|
io = request.POST.get("vol_io_mode", app_settings.INSTANCE_VOLUME_DEFAULT_IO)
|
||||||
discard = request.POST.get("vol_discard_mode", app_settings.INSTANCE_VOLUME_DEFAULT_DISCARD)
|
discard = request.POST.get(
|
||||||
zeroes = request.POST.get("vol_detect_zeroes", app_settings.INSTANCE_VOLUME_DEFAULT_DETECT_ZEROES)
|
"vol_discard_mode", app_settings.INSTANCE_VOLUME_DEFAULT_DISCARD
|
||||||
|
)
|
||||||
|
zeroes = request.POST.get(
|
||||||
|
"vol_detect_zeroes", app_settings.INSTANCE_VOLUME_DEFAULT_DETECT_ZEROES
|
||||||
|
)
|
||||||
new_target_dev = utils.get_new_disk_dev(instance.media, instance.disks, new_bus)
|
new_target_dev = utils.get_new_disk_dev(instance.media, instance.disks, new_bus)
|
||||||
|
|
||||||
if new_bus != bus:
|
if new_bus != bus:
|
||||||
|
@ -710,7 +845,10 @@ def edit_volume(request, pk):
|
||||||
if not instance.proxy.get_status() == 5:
|
if not instance.proxy.get_status() == 5:
|
||||||
messages.success(
|
messages.success(
|
||||||
request,
|
request,
|
||||||
_("Volume changes are applied. " + "But it will be activated after shutdown"),
|
_(
|
||||||
|
"Volume changes are applied. "
|
||||||
|
+ "But it will be activated after shutdown"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
messages.success(request, _("Volume is changed successfully."))
|
messages.success(request, _("Volume is changed successfully."))
|
||||||
|
@ -722,7 +860,9 @@ def edit_volume(request, pk):
|
||||||
|
|
||||||
def delete_vol(request, pk):
|
def delete_vol(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
if allow_admin_or_not_template:
|
if allow_admin_or_not_template:
|
||||||
storage = request.POST.get("storage", "")
|
storage = request.POST.get("storage", "")
|
||||||
conn_delete = wvmStorage(
|
conn_delete = wvmStorage(
|
||||||
|
@ -746,7 +886,9 @@ def delete_vol(request, pk):
|
||||||
|
|
||||||
def detach_vol(request, pk):
|
def detach_vol(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
|
|
||||||
if allow_admin_or_not_template:
|
if allow_admin_or_not_template:
|
||||||
dev = request.POST.get("dev", "")
|
dev = request.POST.get("dev", "")
|
||||||
|
@ -760,11 +902,20 @@ def detach_vol(request, pk):
|
||||||
|
|
||||||
def add_cdrom(request, pk):
|
def add_cdrom(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
if allow_admin_or_not_template:
|
if allow_admin_or_not_template:
|
||||||
bus = request.POST.get("bus", "ide" if instance.machine == "pc" else "sata")
|
bus = request.POST.get("bus", "ide" if instance.machine == "pc" else "sata")
|
||||||
target = utils.get_new_disk_dev(instance.media, instance.disks, bus)
|
target = utils.get_new_disk_dev(instance.media, instance.disks, bus)
|
||||||
instance.proxy.attach_disk(target, "", disk_device="cdrom", cache_mode="none", target_bus=bus, readonly=True)
|
instance.proxy.attach_disk(
|
||||||
|
target,
|
||||||
|
"",
|
||||||
|
disk_device="cdrom",
|
||||||
|
cache_mode="none",
|
||||||
|
target_bus=bus,
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
msg = _("Add CD-ROM: %(target)s") % {"target": target}
|
msg = _("Add CD-ROM: %(target)s") % {"target": target}
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
||||||
|
|
||||||
|
@ -773,7 +924,9 @@ def add_cdrom(request, pk):
|
||||||
|
|
||||||
def detach_cdrom(request, pk, dev):
|
def detach_cdrom(request, pk, dev):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
|
|
||||||
if allow_admin_or_not_template:
|
if allow_admin_or_not_template:
|
||||||
# dev = request.POST.get('detach_cdrom', '')
|
# dev = request.POST.get('detach_cdrom', '')
|
||||||
|
@ -786,7 +939,9 @@ def detach_cdrom(request, pk, dev):
|
||||||
|
|
||||||
def unmount_iso(request, pk):
|
def unmount_iso(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
if allow_admin_or_not_template:
|
if allow_admin_or_not_template:
|
||||||
image = request.POST.get("path", "")
|
image = request.POST.get("path", "")
|
||||||
dev = request.POST.get("umount_iso", "")
|
dev = request.POST.get("umount_iso", "")
|
||||||
|
@ -799,7 +954,9 @@ def unmount_iso(request, pk):
|
||||||
|
|
||||||
def mount_iso(request, pk):
|
def mount_iso(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
if allow_admin_or_not_template:
|
if allow_admin_or_not_template:
|
||||||
image = request.POST.get("media", "")
|
image = request.POST.get("media", "")
|
||||||
dev = request.POST.get("mount_iso", "")
|
dev = request.POST.get("mount_iso", "")
|
||||||
|
@ -812,9 +969,13 @@ def mount_iso(request, pk):
|
||||||
|
|
||||||
def snapshot(request, pk):
|
def snapshot(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
|
|
||||||
if allow_admin_or_not_template and request.user.has_perm("instances.snapshot_instances"):
|
if allow_admin_or_not_template and request.user.has_perm(
|
||||||
|
"instances.snapshot_instances"
|
||||||
|
):
|
||||||
name = request.POST.get("name", "")
|
name = request.POST.get("name", "")
|
||||||
desc = request.POST.get("description", "")
|
desc = request.POST.get("description", "")
|
||||||
instance.proxy.create_snapshot(name, desc)
|
instance.proxy.create_snapshot(name, desc)
|
||||||
|
@ -825,8 +986,12 @@ def snapshot(request, pk):
|
||||||
|
|
||||||
def delete_snapshot(request, pk):
|
def delete_snapshot(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = (
|
||||||
if allow_admin_or_not_template and request.user.has_perm("instances.snapshot_instances"):
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
|
if allow_admin_or_not_template and request.user.has_perm(
|
||||||
|
"instances.snapshot_instances"
|
||||||
|
):
|
||||||
snap_name = request.POST.get("name", "")
|
snap_name = request.POST.get("name", "")
|
||||||
instance.proxy.snapshot_delete(snap_name)
|
instance.proxy.snapshot_delete(snap_name)
|
||||||
msg = _("Delete snapshot: %(snap)s") % {"snap": snap_name}
|
msg = _("Delete snapshot: %(snap)s") % {"snap": snap_name}
|
||||||
|
@ -836,8 +1001,12 @@ def delete_snapshot(request, pk):
|
||||||
|
|
||||||
def revert_snapshot(request, pk):
|
def revert_snapshot(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = (
|
||||||
if allow_admin_or_not_template and request.user.has_perm("instances.snapshot_instances"):
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
|
if allow_admin_or_not_template and request.user.has_perm(
|
||||||
|
"instances.snapshot_instances"
|
||||||
|
):
|
||||||
snap_name = request.POST.get("name", "")
|
snap_name = request.POST.get("name", "")
|
||||||
instance.proxy.snapshot_revert(snap_name)
|
instance.proxy.snapshot_revert(snap_name)
|
||||||
msg = _("Successful revert snapshot: ")
|
msg = _("Successful revert snapshot: ")
|
||||||
|
@ -865,7 +1034,7 @@ def set_vcpu(request, pk):
|
||||||
@superuser_only
|
@superuser_only
|
||||||
def set_vcpu_hotplug(request, pk):
|
def set_vcpu_hotplug(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
status = True if request.POST.get("vcpu_hotplug", "False") == 'True' else False
|
status = True if request.POST.get("vcpu_hotplug", "False") == "True" else False
|
||||||
msg = _("VCPU Hot-plug is enabled=%(status)s") % {"status": status}
|
msg = _("VCPU Hot-plug is enabled=%(status)s") % {"status": status}
|
||||||
instance.proxy.set_vcpu_hotplug(status)
|
instance.proxy.set_vcpu_hotplug(status)
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
||||||
|
@ -923,7 +1092,10 @@ def set_bootorder(request, pk):
|
||||||
if not instance.proxy.get_status() == 5:
|
if not instance.proxy.get_status() == 5:
|
||||||
messages.success(
|
messages.success(
|
||||||
request,
|
request,
|
||||||
_("Boot menu changes applied. " + "But it will be activated after shutdown"),
|
_(
|
||||||
|
"Boot menu changes applied. "
|
||||||
|
+ "But it will be activated after shutdown"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
messages.success(request, _("Boot order changed successfully."))
|
messages.success(request, _("Boot order changed successfully."))
|
||||||
|
@ -979,7 +1151,7 @@ def change_network(request, pk):
|
||||||
network_data[post] = source
|
network_data[post] = source
|
||||||
network_data[post + "-type"] = source_type
|
network_data[post + "-type"] = source_type
|
||||||
|
|
||||||
if source_type == 'iface':
|
if source_type == "iface":
|
||||||
iface = wvmInterface(
|
iface = wvmInterface(
|
||||||
instance.compute.hostname,
|
instance.compute.hostname,
|
||||||
instance.compute.login,
|
instance.compute.login,
|
||||||
|
@ -1007,14 +1179,14 @@ def add_network(request, pk):
|
||||||
nwfilter = request.POST.get("add-net-nwfilter")
|
nwfilter = request.POST.get("add-net-nwfilter")
|
||||||
(source, source_type) = utils.get_network_tuple(request.POST.get("add-net-network"))
|
(source, source_type) = utils.get_network_tuple(request.POST.get("add-net-network"))
|
||||||
|
|
||||||
if source_type == 'iface':
|
if source_type == "iface":
|
||||||
iface = wvmInterface(
|
iface = wvmInterface(
|
||||||
instance.compute.hostname,
|
instance.compute.hostname,
|
||||||
instance.compute.login,
|
instance.compute.login,
|
||||||
instance.compute.password,
|
instance.compute.password,
|
||||||
instance.compute.type,
|
instance.compute.type,
|
||||||
source,
|
source,
|
||||||
)
|
)
|
||||||
source_type = iface.get_type()
|
source_type = iface.get_type()
|
||||||
|
|
||||||
instance.proxy.add_network(mac, source, source_type, nwfilter=nwfilter)
|
instance.proxy.add_network(mac, source, source_type, nwfilter=nwfilter)
|
||||||
|
@ -1062,7 +1234,9 @@ def set_qos(request, pk):
|
||||||
|
|
||||||
instance.proxy.set_qos(mac, qos_dir, average, peak, burst)
|
instance.proxy.set_qos(mac, qos_dir, average, peak, burst)
|
||||||
if instance.proxy.get_status() == 5:
|
if instance.proxy.get_status() == 5:
|
||||||
messages.success(request, _("%(qos_dir)s QoS is set") % {"qos_dir": qos_dir.capitalize()})
|
messages.success(
|
||||||
|
request, _("%(qos_dir)s QoS is set") % {"qos_dir": qos_dir.capitalize()}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
messages.success(
|
messages.success(
|
||||||
request,
|
request,
|
||||||
|
@ -1084,7 +1258,9 @@ def unset_qos(request, pk):
|
||||||
instance.proxy.unset_qos(mac, qos_dir)
|
instance.proxy.unset_qos(mac, qos_dir)
|
||||||
|
|
||||||
if instance.proxy.get_status() == 5:
|
if instance.proxy.get_status() == 5:
|
||||||
messages.success(request, _("%(qos_dir)s QoS is deleted") % {"qos_dir": qos_dir.capitalize()})
|
messages.success(
|
||||||
|
request, _("%(qos_dir)s QoS is deleted") % {"qos_dir": qos_dir.capitalize()}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
messages.success(
|
messages.success(
|
||||||
request,
|
request,
|
||||||
|
@ -1108,7 +1284,9 @@ def add_owner(request, pk):
|
||||||
check_inst = UserInstance.objects.filter(instance=instance).count()
|
check_inst = UserInstance.objects.filter(instance=instance).count()
|
||||||
|
|
||||||
if check_inst > 0:
|
if check_inst > 0:
|
||||||
messages.error(request, _("Only one owner is allowed and the one already added"))
|
messages.error(
|
||||||
|
request, _("Only one owner is allowed and the one already added")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
add_user_inst = UserInstance(instance=instance, user_id=user_id)
|
add_user_inst = UserInstance(instance=instance, user_id=user_id)
|
||||||
add_user_inst.save()
|
add_user_inst.save()
|
||||||
|
@ -1137,7 +1315,9 @@ def clone(request, pk):
|
||||||
clone_data["name"] = request.POST.get("name", "")
|
clone_data["name"] = request.POST.get("name", "")
|
||||||
|
|
||||||
disk_sum = sum([disk["size"] >> 30 for disk in instance.disks])
|
disk_sum = sum([disk["size"] >> 30 for disk in instance.disks])
|
||||||
quota_msg = utils.check_user_quota(request.user, 1, instance.vcpu, instance.memory, disk_sum)
|
quota_msg = utils.check_user_quota(
|
||||||
|
request.user, 1, instance.vcpu, instance.memory, disk_sum
|
||||||
|
)
|
||||||
check_instance = Instance.objects.filter(name=clone_data["name"])
|
check_instance = Instance.objects.filter(name=clone_data["name"])
|
||||||
|
|
||||||
clone_data["disk_owner_uid"] = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_UID)
|
clone_data["disk_owner_uid"] = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_UID)
|
||||||
|
@ -1156,19 +1336,31 @@ def clone(request, pk):
|
||||||
clone_data[disk_dev] = disk_name
|
clone_data[disk_dev] = disk_name
|
||||||
|
|
||||||
if not request.user.is_superuser and quota_msg:
|
if not request.user.is_superuser and quota_msg:
|
||||||
msg = _("User '%(quota_msg)s' quota reached, cannot create '%(clone_name)s'!") % {
|
msg = _(
|
||||||
|
"User '%(quota_msg)s' quota reached, cannot create '%(clone_name)s'!"
|
||||||
|
) % {
|
||||||
"quota_msg": quota_msg,
|
"quota_msg": quota_msg,
|
||||||
"clone_name": clone_data["name"],
|
"clone_name": clone_data["name"],
|
||||||
}
|
}
|
||||||
messages.error(request, msg)
|
messages.error(request, msg)
|
||||||
elif check_instance:
|
elif check_instance:
|
||||||
msg = _("Instance '%(clone_name)s' already exists!") % {"clone_name": clone_data["name"]}
|
msg = _("Instance '%(clone_name)s' already exists!") % {
|
||||||
|
"clone_name": clone_data["name"]
|
||||||
|
}
|
||||||
messages.error(request, msg)
|
messages.error(request, msg)
|
||||||
elif not re.match(r"^[a-zA-Z0-9-]+$", clone_data["name"]):
|
elif not re.match(r"^[a-zA-Z0-9-]+$", clone_data["name"]):
|
||||||
msg = _("Instance name '%(clone_name)s' contains invalid characters!") % {"clone_name": clone_data["name"]}
|
msg = _("Instance name '%(clone_name)s' contains invalid characters!") % {
|
||||||
|
"clone_name": clone_data["name"]
|
||||||
|
}
|
||||||
messages.error(request, msg)
|
messages.error(request, msg)
|
||||||
elif not re.match(r"^([0-9A-F]{2})(:?[0-9A-F]{2}){5}$", clone_data["clone-net-mac-0"], re.IGNORECASE):
|
elif not re.match(
|
||||||
msg = _("Instance MAC '%(clone_mac)s' invalid format!") % {"clone_mac": clone_data["clone-net-mac-0"]}
|
r"^([0-9A-F]{2})(:?[0-9A-F]{2}){5}$",
|
||||||
|
clone_data["clone-net-mac-0"],
|
||||||
|
re.IGNORECASE,
|
||||||
|
):
|
||||||
|
msg = _("Instance MAC '%(clone_mac)s' invalid format!") % {
|
||||||
|
"clone_mac": clone_data["clone-net-mac-0"]
|
||||||
|
}
|
||||||
messages.error(request, msg)
|
messages.error(request, msg)
|
||||||
else:
|
else:
|
||||||
new_instance = Instance(compute=instance.compute, name=clone_data["name"])
|
new_instance = Instance(compute=instance.compute, name=clone_data["name"])
|
||||||
|
@ -1176,15 +1368,23 @@ def clone(request, pk):
|
||||||
new_uuid = instance.proxy.clone_instance(clone_data)
|
new_uuid = instance.proxy.clone_instance(clone_data)
|
||||||
new_instance.uuid = new_uuid
|
new_instance.uuid = new_uuid
|
||||||
new_instance.save()
|
new_instance.save()
|
||||||
user_instance = UserInstance(instance_id=new_instance.id, user_id=request.user.id, is_delete=True)
|
user_instance = UserInstance(
|
||||||
|
instance_id=new_instance.id, user_id=request.user.id, is_delete=True
|
||||||
|
)
|
||||||
user_instance.save()
|
user_instance.save()
|
||||||
msg = _("Create a clone of '%(instance_name)s'") % {"instance_name": instance.name}
|
msg = _("Create a clone of '%(instance_name)s'") % {
|
||||||
|
"instance_name": instance.name
|
||||||
|
}
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
addlogmsg(request.user.username, instance.compute.name, new_instance.name, msg)
|
addlogmsg(
|
||||||
|
request.user.username, instance.compute.name, new_instance.name, msg
|
||||||
|
)
|
||||||
|
|
||||||
if app_settings.CLONE_INSTANCE_AUTO_MIGRATE == "True":
|
if app_settings.CLONE_INSTANCE_AUTO_MIGRATE == "True":
|
||||||
new_compute = Compute.objects.order_by("?").first()
|
new_compute = Compute.objects.order_by("?").first()
|
||||||
utils.migrate_instance(new_compute, new_instance, request.user, xml_del=True, offline=True)
|
utils.migrate_instance(
|
||||||
|
new_compute, new_instance, request.user, xml_del=True, offline=True
|
||||||
|
)
|
||||||
|
|
||||||
return redirect(reverse("instances:instance", args=[new_instance.id]))
|
return redirect(reverse("instances:instance", args=[new_instance.id]))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -1223,7 +1423,9 @@ def update_console(request, pk):
|
||||||
messages.error(request, msg)
|
messages.error(request, msg)
|
||||||
else:
|
else:
|
||||||
msg = _("Set VNC password")
|
msg = _("Set VNC password")
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
addlogmsg(
|
||||||
|
request.user.username, instance.compute.name, instance.name, msg
|
||||||
|
)
|
||||||
|
|
||||||
if "keymap" in form.changed_data or "clear_keymap" in form.changed_data:
|
if "keymap" in form.changed_data or "clear_keymap" in form.changed_data:
|
||||||
if form.cleaned_data["clear_keymap"]:
|
if form.cleaned_data["clear_keymap"]:
|
||||||
|
@ -1232,17 +1434,23 @@ def update_console(request, pk):
|
||||||
instance.proxy.set_console_keymap(form.cleaned_data["keymap"])
|
instance.proxy.set_console_keymap(form.cleaned_data["keymap"])
|
||||||
|
|
||||||
msg = _("Set VNC keymap")
|
msg = _("Set VNC keymap")
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
addlogmsg(
|
||||||
|
request.user.username, instance.compute.name, instance.name, msg
|
||||||
|
)
|
||||||
|
|
||||||
if "type" in form.changed_data:
|
if "type" in form.changed_data:
|
||||||
instance.proxy.set_console_type(form.cleaned_data["type"])
|
instance.proxy.set_console_type(form.cleaned_data["type"])
|
||||||
msg = _("Set VNC type")
|
msg = _("Set VNC type")
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
addlogmsg(
|
||||||
|
request.user.username, instance.compute.name, instance.name, msg
|
||||||
|
)
|
||||||
|
|
||||||
if "listen_on" in form.changed_data:
|
if "listen_on" in form.changed_data:
|
||||||
instance.proxy.set_console_listener_addr(form.cleaned_data["listen_on"])
|
instance.proxy.set_console_listener_addr(form.cleaned_data["listen_on"])
|
||||||
msg = _("Set VNC listen address")
|
msg = _("Set VNC listen address")
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
addlogmsg(
|
||||||
|
request.user.username, instance.compute.name, instance.name, msg
|
||||||
|
)
|
||||||
|
|
||||||
return redirect(request.META.get("HTTP_REFERER") + "#vncsettings")
|
return redirect(request.META.get("HTTP_REFERER") + "#vncsettings")
|
||||||
|
|
||||||
|
@ -1326,7 +1534,15 @@ def create_instance_select_type(request, compute_id):
|
||||||
all_hypervisors = conn.get_hypervisors_machines()
|
all_hypervisors = conn.get_hypervisors_machines()
|
||||||
|
|
||||||
# Supported hypervisors by webvirtcloud: i686, x86_64(for now)
|
# Supported hypervisors by webvirtcloud: i686, x86_64(for now)
|
||||||
supported_arch = ["x86_64", "i686", "aarch64", "armv7l", "ppc64", "ppc64le", "s390x"]
|
supported_arch = [
|
||||||
|
"x86_64",
|
||||||
|
"i686",
|
||||||
|
"aarch64",
|
||||||
|
"armv7l",
|
||||||
|
"ppc64",
|
||||||
|
"ppc64le",
|
||||||
|
"s390x",
|
||||||
|
]
|
||||||
hypervisors = [hpv for hpv in all_hypervisors.keys() if hpv in supported_arch]
|
hypervisors = [hpv for hpv in all_hypervisors.keys() if hpv in supported_arch]
|
||||||
default_machine = app_settings.INSTANCE_MACHINE_DEFAULT_TYPE
|
default_machine = app_settings.INSTANCE_MACHINE_DEFAULT_TYPE
|
||||||
default_arch = app_settings.INSTANCE_ARCH_DEFAULT_TYPE
|
default_arch = app_settings.INSTANCE_ARCH_DEFAULT_TYPE
|
||||||
|
@ -1371,7 +1587,12 @@ def create_instance(request, compute_id, arch, machine):
|
||||||
appsettings = AppSettings.objects.all()
|
appsettings = AppSettings.objects.all()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = wvmCreate(compute.hostname, compute.login, compute.password, compute.type)
|
conn = wvmCreate(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type
|
||||||
|
)
|
||||||
|
|
||||||
default_firmware = app_settings.INSTANCE_FIRMWARE_DEFAULT_TYPE
|
default_firmware = app_settings.INSTANCE_FIRMWARE_DEFAULT_TYPE
|
||||||
default_cpu_mode = app_settings.INSTANCE_CPU_DEFAULT_MODE
|
default_cpu_mode = app_settings.INSTANCE_CPU_DEFAULT_MODE
|
||||||
|
@ -1397,7 +1618,7 @@ def create_instance(request, compute_id, arch, machine):
|
||||||
storages = sorted(conn.get_storages(only_actives=True))
|
storages = sorted(conn.get_storages(only_actives=True))
|
||||||
default_graphics = app_settings.QEMU_CONSOLE_DEFAULT_TYPE
|
default_graphics = app_settings.QEMU_CONSOLE_DEFAULT_TYPE
|
||||||
default_cdrom = app_settings.INSTANCE_CDROM_ADD
|
default_cdrom = app_settings.INSTANCE_CDROM_ADD
|
||||||
input_device_buses = ['default', 'virtio', 'usb']
|
input_device_buses = ["default", "virtio", "usb"]
|
||||||
default_input_device_bus = app_settings.INSTANCE_INPUT_DEFAULT_DEVICE
|
default_input_device_bus = app_settings.INSTANCE_INPUT_DEFAULT_DEVICE
|
||||||
|
|
||||||
dom_caps = conn.get_dom_capabilities(arch, machine)
|
dom_caps = conn.get_dom_capabilities(arch, machine)
|
||||||
|
@ -1437,13 +1658,21 @@ def create_instance(request, compute_id, arch, machine):
|
||||||
meta_prealloc = True
|
meta_prealloc = True
|
||||||
if instances:
|
if instances:
|
||||||
if data["name"] in instances:
|
if data["name"] in instances:
|
||||||
raise libvirtError(_("A virtual machine with this name already exists"))
|
raise libvirtError(
|
||||||
|
_("A virtual machine with this name already exists")
|
||||||
|
)
|
||||||
if Instance.objects.filter(name__exact=data["name"]):
|
if Instance.objects.filter(name__exact=data["name"]):
|
||||||
raise libvirtError(_("There is an instance with same name. Remove it and try again!"))
|
raise libvirtError(
|
||||||
|
_(
|
||||||
|
"There is an instance with same name. Remove it and try again!"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if data["hdd_size"]:
|
if data["hdd_size"]:
|
||||||
if not data["mac"]:
|
if not data["mac"]:
|
||||||
raise libvirtError(_("No Virtual Machine MAC has been entered"))
|
raise libvirtError(
|
||||||
|
_("No Virtual Machine MAC has been entered")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
path = conn.create_volume(
|
path = conn.create_volume(
|
||||||
data["storage"],
|
data["storage"],
|
||||||
|
@ -1471,10 +1700,14 @@ def create_instance(request, compute_id, arch, machine):
|
||||||
|
|
||||||
elif data["template"]:
|
elif data["template"]:
|
||||||
templ_path = conn.get_volume_path(data["template"])
|
templ_path = conn.get_volume_path(data["template"])
|
||||||
dest_vol = conn.get_volume_path(data["name"] + ".img", data["storage"])
|
dest_vol = conn.get_volume_path(
|
||||||
|
data["name"] + ".img", data["storage"]
|
||||||
|
)
|
||||||
if dest_vol:
|
if dest_vol:
|
||||||
raise libvirtError(
|
raise libvirtError(
|
||||||
_("Image has already exist. Please check volumes or change instance name")
|
_(
|
||||||
|
"Image has already exist. Please check volumes or change instance name"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
clone_path = conn.clone_from_template(
|
clone_path = conn.clone_from_template(
|
||||||
|
@ -1501,15 +1734,21 @@ def create_instance(request, compute_id, arch, machine):
|
||||||
is_disk_created = True
|
is_disk_created = True
|
||||||
else:
|
else:
|
||||||
if not data["images"]:
|
if not data["images"]:
|
||||||
raise libvirtError(_("First you need to create or select an image"))
|
raise libvirtError(
|
||||||
|
_("First you need to create or select an image")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
for idx, vol in enumerate(data["images"].split(",")):
|
for idx, vol in enumerate(data["images"].split(",")):
|
||||||
path = conn.get_volume_path(vol)
|
path = conn.get_volume_path(vol)
|
||||||
volume = dict()
|
volume = dict()
|
||||||
volume["path"] = path
|
volume["path"] = path
|
||||||
volume["type"] = conn.get_volume_format_type(path)
|
volume["type"] = conn.get_volume_format_type(path)
|
||||||
volume["device"] = request.POST.get("device" + str(idx), "")
|
volume["device"] = request.POST.get(
|
||||||
volume["bus"] = request.POST.get("bus" + str(idx), "")
|
"device" + str(idx), ""
|
||||||
|
)
|
||||||
|
volume["bus"] = request.POST.get(
|
||||||
|
"bus" + str(idx), ""
|
||||||
|
)
|
||||||
if volume["bus"] == "scsi":
|
if volume["bus"] == "scsi":
|
||||||
volume["scsi_model"] = default_scsi_disk_model
|
volume["scsi_model"] = default_scsi_disk_model
|
||||||
volume["cache_mode"] = data["cache_mode"]
|
volume["cache_mode"] = data["cache_mode"]
|
||||||
|
@ -1560,12 +1799,21 @@ def create_instance(request, compute_id, arch, machine):
|
||||||
add_cdrom=data["add_cdrom"],
|
add_cdrom=data["add_cdrom"],
|
||||||
add_input=data["add_input"],
|
add_input=data["add_input"],
|
||||||
)
|
)
|
||||||
create_instance = Instance(compute_id=compute_id, name=data["name"], uuid=uuid)
|
create_instance = Instance(
|
||||||
|
compute_id=compute_id, name=data["name"], uuid=uuid
|
||||||
|
)
|
||||||
create_instance.save()
|
create_instance.save()
|
||||||
msg = _("Instance is created")
|
msg = _("Instance is created")
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
addlogmsg(request.user.username, create_instance.compute.name, create_instance.name, msg)
|
addlogmsg(
|
||||||
return redirect(reverse("instances:instance", args=[create_instance.id]))
|
request.user.username,
|
||||||
|
create_instance.compute.name,
|
||||||
|
create_instance.name,
|
||||||
|
msg,
|
||||||
|
)
|
||||||
|
return redirect(
|
||||||
|
reverse("instances:instance", args=[create_instance.id])
|
||||||
|
)
|
||||||
except libvirtError as lib_err:
|
except libvirtError as lib_err:
|
||||||
if data["hdd_size"] or len(volume_list) > 0:
|
if data["hdd_size"] or len(volume_list) > 0:
|
||||||
if is_disk_created:
|
if is_disk_created:
|
||||||
|
|
|
@ -4,10 +4,14 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
class Interfaces(models.Model):
|
class Interfaces(models.Model):
|
||||||
name = models.CharField(_('name'), max_length=20, error_messages={'required': _('No interface name has been entered')})
|
name = models.CharField(
|
||||||
type = models.CharField(_('status'), max_length=12)
|
_("name"),
|
||||||
state = models.CharField(_('device'), max_length=100)
|
max_length=20,
|
||||||
mac = models.CharField(_('forward'), max_length=24)
|
error_messages={"required": _("No interface name has been entered")},
|
||||||
|
)
|
||||||
|
type = models.CharField(_("status"), max_length=12)
|
||||||
|
state = models.CharField(_("device"), max_length=100)
|
||||||
|
mac = models.CharField(_("forward"), max_length=24)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
managed = False
|
managed = False
|
||||||
|
|
|
@ -21,7 +21,12 @@ def interfaces(request, compute_id):
|
||||||
compute = get_object_or_404(Compute, pk=compute_id)
|
compute = get_object_or_404(Compute, pk=compute_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = wvmInterfaces(compute.hostname, compute.login, compute.password, compute.type)
|
conn = wvmInterfaces(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type
|
||||||
|
)
|
||||||
ifaces = conn.get_ifaces()
|
ifaces = conn.get_ifaces()
|
||||||
try:
|
try:
|
||||||
netdevs = conn.get_net_devices()
|
netdevs = conn.get_net_devices()
|
||||||
|
@ -29,7 +34,13 @@ def interfaces(request, compute_id):
|
||||||
netdevs = ["eth0", "eth1"]
|
netdevs = ["eth0", "eth1"]
|
||||||
|
|
||||||
for iface in ifaces:
|
for iface in ifaces:
|
||||||
interf = wvmInterface(compute.hostname, compute.login, compute.password, compute.type, iface)
|
interf = wvmInterface(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type,
|
||||||
|
iface
|
||||||
|
)
|
||||||
ifaces_all.append(interf.get_details())
|
ifaces_all.append(interf.get_details())
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
|
@ -75,7 +86,13 @@ def interface(request, compute_id, iface):
|
||||||
compute = get_object_or_404(Compute, pk=compute_id)
|
compute = get_object_or_404(Compute, pk=compute_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = wvmInterface(compute.hostname, compute.login, compute.password, compute.type, iface)
|
conn = wvmInterface(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type,
|
||||||
|
iface
|
||||||
|
)
|
||||||
start_mode = conn.get_start_mode()
|
start_mode = conn.get_start_mode()
|
||||||
state = conn.is_active()
|
state = conn.is_active()
|
||||||
mac = conn.get_mac()
|
mac = conn.get_mac()
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from networks.models import Networks
|
from networks.models import Networks
|
||||||
|
|
||||||
|
|
||||||
class NetworksSerializer(serializers.ModelSerializer):
|
class NetworksSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Networks
|
model = Networks
|
||||||
fields = ['name', 'status', 'device', 'forward']
|
fields = ["name", "status", "device", "forward"]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,10 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class AddNetPool(forms.Form):
|
class AddNetPool(forms.Form):
|
||||||
name = forms.CharField(error_messages={"required": _("No pool name has been entered")}, max_length=20)
|
name = forms.CharField(
|
||||||
|
error_messages={"required": _("No pool name has been entered")},
|
||||||
|
max_length=20
|
||||||
|
)
|
||||||
subnet = forms.CharField(
|
subnet = forms.CharField(
|
||||||
error_messages={"required": _("No IPv4 subnet has been entered")},
|
error_messages={"required": _("No IPv4 subnet has been entered")},
|
||||||
max_length=20,
|
max_length=20,
|
||||||
|
@ -27,27 +30,39 @@ class AddNetPool(forms.Form):
|
||||||
name = self.cleaned_data["name"]
|
name = self.cleaned_data["name"]
|
||||||
have_symbol = re.match(r"^[a-zA-Z0-9\.\_\-]+$", name)
|
have_symbol = re.match(r"^[a-zA-Z0-9\.\_\-]+$", name)
|
||||||
if not have_symbol:
|
if not have_symbol:
|
||||||
raise forms.ValidationError(_("The pool name must not contain any special characters"))
|
raise forms.ValidationError(
|
||||||
|
_("The pool name must not contain any special characters")
|
||||||
|
)
|
||||||
elif len(name) > 20:
|
elif len(name) > 20:
|
||||||
raise forms.ValidationError(_("The pool name must not exceed 20 characters"))
|
raise forms.ValidationError(
|
||||||
|
_("The pool name must not exceed 20 characters")
|
||||||
|
)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def clean_subnet(self):
|
def clean_subnet(self):
|
||||||
subnet = self.cleaned_data["subnet"]
|
subnet = self.cleaned_data["subnet"]
|
||||||
have_symbol = re.match("^[0-9./]+$", subnet if subnet else ".")
|
have_symbol = re.match("^[0-9./]+$", subnet if subnet else ".")
|
||||||
if not have_symbol:
|
if not have_symbol:
|
||||||
raise forms.ValidationError(_("The IPv4 subnet must not contain any special characters"))
|
raise forms.ValidationError(
|
||||||
|
_("The IPv4 subnet must not contain any special characters")
|
||||||
|
)
|
||||||
elif len(subnet) > 20:
|
elif len(subnet) > 20:
|
||||||
raise forms.ValidationError(_("The IPv4 subnet must not exceed 20 characters"))
|
raise forms.ValidationError(
|
||||||
|
_("The IPv4 subnet must not exceed 20 characters")
|
||||||
|
)
|
||||||
return subnet
|
return subnet
|
||||||
|
|
||||||
def clean_subnet6(self):
|
def clean_subnet6(self):
|
||||||
subnet = self.cleaned_data["subnet6"]
|
subnet = self.cleaned_data["subnet6"]
|
||||||
have_symbol = re.match("^[0-9a-fA-F:/]+$", subnet if subnet else ":")
|
have_symbol = re.match("^[0-9a-fA-F:/]+$", subnet if subnet else ":")
|
||||||
if not have_symbol:
|
if not have_symbol:
|
||||||
raise forms.ValidationError(_("The IPv6 subnet must not contain any special characters"))
|
raise forms.ValidationError(
|
||||||
|
_("The IPv6 subnet must not contain any special characters")
|
||||||
|
)
|
||||||
elif len(subnet) > 42:
|
elif len(subnet) > 42:
|
||||||
raise forms.ValidationError(_("The IPv6 subnet must not exceed 42 characters"))
|
raise forms.ValidationError(
|
||||||
|
_("The IPv6 subnet must not exceed 42 characters")
|
||||||
|
)
|
||||||
return subnet
|
return subnet
|
||||||
|
|
||||||
def clean_bridge_name(self):
|
def clean_bridge_name(self):
|
||||||
|
@ -55,7 +70,11 @@ class AddNetPool(forms.Form):
|
||||||
if self.cleaned_data["forward"] in ["bridge", "macvtap"]:
|
if self.cleaned_data["forward"] in ["bridge", "macvtap"]:
|
||||||
have_symbol = re.match(r"^[a-zA-Z0-9\.\_\:\-]+$", bridge_name)
|
have_symbol = re.match(r"^[a-zA-Z0-9\.\_\:\-]+$", bridge_name)
|
||||||
if not have_symbol:
|
if not have_symbol:
|
||||||
raise forms.ValidationError(_("The pool bridge name must not contain any special characters"))
|
raise forms.ValidationError(
|
||||||
|
_("The pool bridge name must not contain any special characters")
|
||||||
|
)
|
||||||
elif len(bridge_name) > 20:
|
elif len(bridge_name) > 20:
|
||||||
raise forms.ValidationError(_("The pool bridge name must not exceed 20 characters"))
|
raise forms.ValidationError(
|
||||||
|
_("The pool bridge name must not exceed 20 characters")
|
||||||
|
)
|
||||||
return bridge_name
|
return bridge_name
|
||||||
|
|
|
@ -4,10 +4,14 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
class Networks(models.Model):
|
class Networks(models.Model):
|
||||||
name = models.CharField(_('name'), max_length=20, error_messages={'required': _('No network name has been entered')})
|
name = models.CharField(
|
||||||
status = models.CharField(_('status'), max_length=12)
|
_("name"),
|
||||||
device = models.CharField(_('device'), max_length=100)
|
max_length=20,
|
||||||
forward = models.CharField(_('forward'), max_length=24)
|
error_messages={"required": _("No network name has been entered")},
|
||||||
|
)
|
||||||
|
status = models.CharField(_("status"), max_length=12)
|
||||||
|
device = models.CharField(_("device"), max_length=100)
|
||||||
|
forward = models.CharField(_("forward"), max_length=24)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
managed = False
|
managed = False
|
||||||
|
|
|
@ -43,18 +43,26 @@ def networks(request, compute_id):
|
||||||
msg = _("Network pool name already in use")
|
msg = _("Network pool name already in use")
|
||||||
messages.error(request, msg)
|
messages.error(request, msg)
|
||||||
errors = True
|
errors = True
|
||||||
if data["forward"] in ["bridge", "macvtap"] and data["bridge_name"] == "":
|
if (
|
||||||
|
data["forward"] in ["bridge", "macvtap"]
|
||||||
|
and data["bridge_name"] == ""
|
||||||
|
):
|
||||||
messages.error(request, _("Please enter bridge/dev name"))
|
messages.error(request, _("Please enter bridge/dev name"))
|
||||||
errors = True
|
errors = True
|
||||||
if data["subnet"]:
|
if data["subnet"]:
|
||||||
ipv4 = True
|
ipv4 = True
|
||||||
gateway4, netmask4, dhcp4 = network_size(data["subnet"], data["dhcp4"])
|
gateway4, netmask4, dhcp4 = network_size(
|
||||||
|
data["subnet"], data["dhcp4"]
|
||||||
|
)
|
||||||
if data["subnet6"]:
|
if data["subnet6"]:
|
||||||
ipv6 = True
|
ipv6 = True
|
||||||
gateway6, prefix6, dhcp6 = network_size(data["subnet6"], data["dhcp6"])
|
gateway6, prefix6, dhcp6 = network_size(
|
||||||
|
data["subnet6"], data["dhcp6"]
|
||||||
|
)
|
||||||
if prefix6 != "64":
|
if prefix6 != "64":
|
||||||
messages.error(
|
messages.error(
|
||||||
request, _("For libvirt, the IPv6 network prefix must be /64")
|
request,
|
||||||
|
_("For libvirt, the IPv6 network prefix must be /64"),
|
||||||
)
|
)
|
||||||
errors = True
|
errors = True
|
||||||
if not errors:
|
if not errors:
|
||||||
|
@ -177,7 +185,11 @@ def network(request, compute_id, pool):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ret_val = conn.modify_fixed_address(name, address, mac_duid, family)
|
ret_val = conn.modify_fixed_address(name, address, mac_duid, family)
|
||||||
messages.success(request, _("Fixed address operation completed for %(family)s") % {"family": family.upper()})
|
messages.success(
|
||||||
|
request,
|
||||||
|
_("Fixed address operation completed for %(family)s")
|
||||||
|
% {"family": family.upper()},
|
||||||
|
)
|
||||||
return HttpResponseRedirect(request.get_full_path())
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
except libvirtError as lib_err:
|
except libvirtError as lib_err:
|
||||||
messages.error(request, lib_err)
|
messages.error(request, lib_err)
|
||||||
|
@ -187,7 +199,10 @@ def network(request, compute_id, pool):
|
||||||
ip = request.POST.get("address", "")
|
ip = request.POST.get("address", "")
|
||||||
family = request.POST.get("family", "ipv4")
|
family = request.POST.get("family", "ipv4")
|
||||||
conn.delete_fixed_address(ip, family)
|
conn.delete_fixed_address(ip, family)
|
||||||
messages.success(request, _("%(family)s Fixed Address is Deleted.") % {"family": family.upper()})
|
messages.success(
|
||||||
|
request,
|
||||||
|
_("%(family)s Fixed Address is Deleted.") % {"family": family.upper()},
|
||||||
|
)
|
||||||
return HttpResponseRedirect(request.get_full_path())
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
if "modify_dhcp_range" in request.POST:
|
if "modify_dhcp_range" in request.POST:
|
||||||
range_start = request.POST.get("range_start", "")
|
range_start = request.POST.get("range_start", "")
|
||||||
|
@ -195,7 +210,10 @@ def network(request, compute_id, pool):
|
||||||
family = request.POST.get("family", "ipv4")
|
family = request.POST.get("family", "ipv4")
|
||||||
try:
|
try:
|
||||||
conn.modify_dhcp_range(range_start, range_end, family)
|
conn.modify_dhcp_range(range_start, range_end, family)
|
||||||
messages.success(request, _("%(family)s DHCP Range is Changed.") % {"family": family.upper()})
|
messages.success(
|
||||||
|
request,
|
||||||
|
_("%(family)s DHCP Range is Changed.") % {"family": family.upper()},
|
||||||
|
)
|
||||||
return HttpResponseRedirect(request.get_full_path())
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
except libvirtError as lib_err:
|
except libvirtError as lib_err:
|
||||||
messages.error(request, lib_err)
|
messages.error(request, lib_err)
|
||||||
|
@ -225,10 +243,16 @@ def network(request, compute_id, pool):
|
||||||
if conn.is_active():
|
if conn.is_active():
|
||||||
messages.success(
|
messages.success(
|
||||||
request,
|
request,
|
||||||
_("%(qos_dir)s QoS is updated. Network XML is changed. Stop and start network to activate new config") % {"qos_dir": qos_dir.capitalize()}
|
_(
|
||||||
|
"%(qos_dir)s QoS is updated. Network XML is changed. Stop and start network to activate new config"
|
||||||
|
)
|
||||||
|
% {"qos_dir": qos_dir.capitalize()},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
messages.success(request, _("%(qos_dir)s QoS is set") % {"qos_dir": qos_dir.capitalize()})
|
messages.success(
|
||||||
|
request,
|
||||||
|
_("%(qos_dir)s QoS is set") % {"qos_dir": qos_dir.capitalize()},
|
||||||
|
)
|
||||||
except libvirtError as lib_err:
|
except libvirtError as lib_err:
|
||||||
messages.error(request, lib_err)
|
messages.error(request, lib_err)
|
||||||
return HttpResponseRedirect(request.get_full_path())
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
@ -239,11 +263,17 @@ def network(request, compute_id, pool):
|
||||||
if conn.is_active():
|
if conn.is_active():
|
||||||
messages.success(
|
messages.success(
|
||||||
request,
|
request,
|
||||||
_("%(qos_dir)s QoS is deleted. Network XML is changed. \
|
_(
|
||||||
Stop and start network to activate new config") % {"qos_dir": qos_dir.capitalize()}
|
"%(qos_dir)s QoS is deleted. Network XML is changed. \
|
||||||
|
Stop and start network to activate new config"
|
||||||
|
)
|
||||||
|
% {"qos_dir": qos_dir.capitalize()},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
messages.success(request, _("%(qos_dir)s QoS is deleted") % {"qos_dir": qos_dir.capitalize()})
|
messages.success(
|
||||||
|
request,
|
||||||
|
_("%(qos_dir)s QoS is deleted") % {"qos_dir": qos_dir.capitalize()},
|
||||||
|
)
|
||||||
return HttpResponseRedirect(request.get_full_path())
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,12 @@ def nwfilters(request, compute_id):
|
||||||
compute = get_object_or_404(Compute, pk=compute_id)
|
compute = get_object_or_404(Compute, pk=compute_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = wvmNWFilters(compute.hostname, compute.login, compute.password, compute.type)
|
conn = wvmNWFilters(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type
|
||||||
|
)
|
||||||
|
|
||||||
for nwf in conn.get_nwfilters():
|
for nwf in conn.get_nwfilters():
|
||||||
nwfilters_all.append(conn.get_nwfilter_info(nwf))
|
nwfilters_all.append(conn.get_nwfilter_info(nwf))
|
||||||
|
@ -41,19 +46,27 @@ def nwfilters(request, compute_id):
|
||||||
|
|
||||||
for nwf in nwfilters_all:
|
for nwf in nwfilters_all:
|
||||||
if name == nwf["name"]:
|
if name == nwf["name"]:
|
||||||
error_msg = _("A network filter with this name already exists")
|
error_msg = _(
|
||||||
|
"A network filter with this name already exists"
|
||||||
|
)
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
if uuid == nwf["uuid"]:
|
if uuid == nwf["uuid"]:
|
||||||
error_msg = _("A network filter with this UUID already exists")
|
error_msg = _(
|
||||||
|
"A network filter with this UUID already exists"
|
||||||
|
)
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
msg = _("%(filter)s network filter is created") % {"filter": name}
|
msg = _("%(filter)s network filter is created") % {
|
||||||
|
"filter": name
|
||||||
|
}
|
||||||
conn.create_nwfilter(xml)
|
conn.create_nwfilter(xml)
|
||||||
addlogmsg(request.user.username, compute.hostname, "", msg)
|
addlogmsg(request.user.username, compute.hostname, "", msg)
|
||||||
except libvirtError as lib_err:
|
except libvirtError as lib_err:
|
||||||
messages.error(request, lib_err)
|
messages.error(request, lib_err)
|
||||||
addlogmsg(request.user.username, compute.hostname, "", lib_err)
|
addlogmsg(
|
||||||
|
request.user.username, compute.hostname, "", lib_err
|
||||||
|
)
|
||||||
|
|
||||||
if "del_nwfilter" in request.POST:
|
if "del_nwfilter" in request.POST:
|
||||||
name = request.POST.get("nwfiltername", "")
|
name = request.POST.get("nwfiltername", "")
|
||||||
|
@ -63,18 +76,27 @@ def nwfilters(request, compute_id):
|
||||||
nwfilter_info = conn.get_nwfilter_info(name)
|
nwfilter_info = conn.get_nwfilter_info(name)
|
||||||
|
|
||||||
is_conn = wvmInstances(
|
is_conn = wvmInstances(
|
||||||
compute.hostname, compute.login, compute.password, compute.type
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type
|
||||||
)
|
)
|
||||||
instances = is_conn.get_instances()
|
instances = is_conn.get_instances()
|
||||||
for inst in instances:
|
for inst in instances:
|
||||||
i_conn = wvmInstance(
|
i_conn = wvmInstance(
|
||||||
compute.hostname, compute.login, compute.password, compute.type, inst
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type,
|
||||||
|
inst,
|
||||||
)
|
)
|
||||||
dom_filterrefs = i_conn.get_filterrefs()
|
dom_filterrefs = i_conn.get_filterrefs()
|
||||||
|
|
||||||
if name in dom_filterrefs:
|
if name in dom_filterrefs:
|
||||||
in_use = True
|
in_use = True
|
||||||
msg = _("NWFilter is in use by %(instance)s. Cannot be deleted.") % {"instance": inst}
|
msg = _(
|
||||||
|
"NWFilter is in use by %(instance)s. Cannot be deleted."
|
||||||
|
) % {"instance": inst}
|
||||||
messages.error(request, msg)
|
messages.error(request, msg)
|
||||||
addlogmsg(request.user.username, compute.hostname, "", msg)
|
addlogmsg(request.user.username, compute.hostname, "", msg)
|
||||||
i_conn.close()
|
i_conn.close()
|
||||||
|
@ -93,7 +115,10 @@ def nwfilters(request, compute_id):
|
||||||
conn.clone_nwfilter(name, cln_name)
|
conn.clone_nwfilter(name, cln_name)
|
||||||
nwfilters_all.append(conn.get_nwfilter_info(cln_name))
|
nwfilters_all.append(conn.get_nwfilter_info(cln_name))
|
||||||
|
|
||||||
msg = _("Cloning NWFilter %(name)s as %(clone)s") % {"name":name, "clone": cln_name}
|
msg = _("Cloning NWFilter %(name)s as %(clone)s") % {
|
||||||
|
"name": name,
|
||||||
|
"clone": cln_name,
|
||||||
|
}
|
||||||
addlogmsg(request.user.username, compute.hostname, "", msg)
|
addlogmsg(request.user.username, compute.hostname, "", msg)
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
|
@ -126,9 +151,18 @@ def nwfilter(request, compute_id, nwfltr):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
nwfilter = wvmNWFilter(
|
nwfilter = wvmNWFilter(
|
||||||
compute.hostname, compute.login, compute.password, compute.type, nwfltr
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type,
|
||||||
|
nwfltr
|
||||||
|
)
|
||||||
|
conn = wvmNWFilters(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type
|
||||||
)
|
)
|
||||||
conn = wvmNWFilters(compute.hostname, compute.login, compute.password, compute.type)
|
|
||||||
|
|
||||||
for nwf in conn.get_nwfilters():
|
for nwf in conn.get_nwfilters():
|
||||||
nwfilters_all.append(conn.get_nwfilter_info(nwf))
|
nwfilters_all.append(conn.get_nwfilter_info(nwf))
|
||||||
|
|
|
@ -5,9 +5,15 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class AddStgPool(forms.Form):
|
class AddStgPool(forms.Form):
|
||||||
name = forms.CharField(error_messages={'required': _('No pool name has been entered')}, max_length=20)
|
name = forms.CharField(
|
||||||
|
error_messages={"required": _("No pool name has been entered")}, max_length=20
|
||||||
|
)
|
||||||
stg_type = forms.CharField(max_length=10)
|
stg_type = forms.CharField(max_length=10)
|
||||||
target = forms.CharField(error_messages={'required': _('No path has been entered')}, max_length=100, required=False)
|
target = forms.CharField(
|
||||||
|
error_messages={"required": _("No path has been entered")},
|
||||||
|
max_length=100,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
source = forms.CharField(max_length=100, required=False)
|
source = forms.CharField(max_length=100, required=False)
|
||||||
ceph_user = forms.CharField(required=False)
|
ceph_user = forms.CharField(required=False)
|
||||||
ceph_host = forms.CharField(required=False)
|
ceph_host = forms.CharField(required=False)
|
||||||
|
@ -17,49 +23,62 @@ class AddStgPool(forms.Form):
|
||||||
source_format = forms.CharField(required=False)
|
source_format = forms.CharField(required=False)
|
||||||
|
|
||||||
def clean_name(self):
|
def clean_name(self):
|
||||||
name = self.cleaned_data['name']
|
name = self.cleaned_data["name"]
|
||||||
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
|
have_symbol = re.match("^[a-zA-Z0-9._-]+$", name)
|
||||||
if not have_symbol:
|
if not have_symbol:
|
||||||
raise forms.ValidationError(_('The pool name must not contain any special characters'))
|
raise forms.ValidationError(
|
||||||
|
_("The pool name must not contain any special characters")
|
||||||
|
)
|
||||||
elif len(name) > 20:
|
elif len(name) > 20:
|
||||||
raise forms.ValidationError(_('The pool name must not exceed 20 characters'))
|
raise forms.ValidationError(
|
||||||
|
_("The pool name must not exceed 20 characters")
|
||||||
|
)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def clean_target(self):
|
def clean_target(self):
|
||||||
storage_type = self.cleaned_data['stg_type']
|
storage_type = self.cleaned_data["stg_type"]
|
||||||
target = self.cleaned_data['target']
|
target = self.cleaned_data["target"]
|
||||||
have_symbol = re.match('^[a-zA-Z0-9/]+$', target)
|
have_symbol = re.match("^[a-zA-Z0-9/]+$", target)
|
||||||
if storage_type == 'dir' or storage_type == 'netfs':
|
if storage_type == "dir" or storage_type == "netfs":
|
||||||
if not have_symbol:
|
if not have_symbol:
|
||||||
raise forms.ValidationError(_('The target must not contain any special characters'))
|
raise forms.ValidationError(
|
||||||
if storage_type == 'dir' or storage_type == 'netfs':
|
_("The target must not contain any special characters")
|
||||||
|
)
|
||||||
|
if storage_type == "dir" or storage_type == "netfs":
|
||||||
if not target:
|
if not target:
|
||||||
raise forms.ValidationError(_('No path has been entered'))
|
raise forms.ValidationError(_("No path has been entered"))
|
||||||
return target
|
return target
|
||||||
|
|
||||||
def clean_source(self):
|
def clean_source(self):
|
||||||
storage_type = self.cleaned_data['stg_type']
|
storage_type = self.cleaned_data["stg_type"]
|
||||||
source = self.cleaned_data['source']
|
source = self.cleaned_data["source"]
|
||||||
have_symbol = re.match('^[a-zA-Z0-9\/]+$', source)
|
have_symbol = re.match("^[a-zA-Z0-9\/]+$", source)
|
||||||
if storage_type == 'logical' or storage_type == 'netfs':
|
if storage_type == "logical" or storage_type == "netfs":
|
||||||
if not source:
|
if not source:
|
||||||
raise forms.ValidationError(_('No device or path has been entered'))
|
raise forms.ValidationError(_("No device or path has been entered"))
|
||||||
if not have_symbol:
|
if not have_symbol:
|
||||||
raise forms.ValidationError(_('The disk source must not contain any special characters'))
|
raise forms.ValidationError(
|
||||||
|
_("The disk source must not contain any special characters")
|
||||||
|
)
|
||||||
return source
|
return source
|
||||||
|
|
||||||
|
|
||||||
class CreateVolumeForm(forms.Form):
|
class CreateVolumeForm(forms.Form):
|
||||||
name = forms.CharField(max_length=120)
|
name = forms.CharField(max_length=120)
|
||||||
format = forms.ChoiceField(required=True, choices=(('qcow2', 'qcow2 (recommended)'), ('qcow', 'qcow'), ('raw', 'raw')))
|
format = forms.ChoiceField(
|
||||||
|
required=True,
|
||||||
|
choices=(("qcow2", "qcow2 (recommended)"), ("qcow", "qcow"), ("raw", "raw")),
|
||||||
|
)
|
||||||
size = forms.IntegerField()
|
size = forms.IntegerField()
|
||||||
meta_prealloc = forms.BooleanField(required=False)
|
meta_prealloc = forms.BooleanField(required=False)
|
||||||
|
|
||||||
def clean_name(self):
|
def clean_name(self):
|
||||||
name = self.cleaned_data['name']
|
name = self.cleaned_data["name"]
|
||||||
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
|
have_symbol = re.match("^[a-zA-Z0-9._-]+$", name)
|
||||||
if not have_symbol:
|
if not have_symbol:
|
||||||
raise forms.ValidationError(_('The image name must not contain any special characters'))
|
raise forms.ValidationError(
|
||||||
|
_("The image name must not contain any special characters")
|
||||||
|
)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,14 +86,21 @@ class CloneImage(forms.Form):
|
||||||
name = forms.CharField(max_length=120)
|
name = forms.CharField(max_length=120)
|
||||||
image = forms.CharField(max_length=120)
|
image = forms.CharField(max_length=120)
|
||||||
convert = forms.BooleanField(required=False)
|
convert = forms.BooleanField(required=False)
|
||||||
format = forms.ChoiceField(required=False, choices=(('qcow2', 'qcow2 (recommended)'), ('qcow', 'qcow'), ('raw', 'raw')))
|
format = forms.ChoiceField(
|
||||||
|
required=False,
|
||||||
|
choices=(("qcow2", "qcow2 (recommended)"), ("qcow", "qcow"), ("raw", "raw")),
|
||||||
|
)
|
||||||
meta_prealloc = forms.BooleanField(required=False)
|
meta_prealloc = forms.BooleanField(required=False)
|
||||||
|
|
||||||
def clean_name(self):
|
def clean_name(self):
|
||||||
name = self.cleaned_data['name']
|
name = self.cleaned_data["name"]
|
||||||
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
|
have_symbol = re.match("^[a-zA-Z0-9._-]+$", name)
|
||||||
if not have_symbol:
|
if not have_symbol:
|
||||||
raise forms.ValidationError(_('The image name must not contain any special characters'))
|
raise forms.ValidationError(
|
||||||
|
_("The image name must not contain any special characters")
|
||||||
|
)
|
||||||
elif len(name) > 120:
|
elif len(name) > 120:
|
||||||
raise forms.ValidationError(_('The image name must not exceed 120 characters'))
|
raise forms.ValidationError(
|
||||||
|
_("The image name must not exceed 120 characters")
|
||||||
|
)
|
||||||
return name
|
return name
|
||||||
|
|
|
@ -3,45 +3,55 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
class Storages(models.Model):
|
class Storages(models.Model):
|
||||||
name = models.CharField(_('name'), max_length=20, error_messages={'required': _('No pool name has been entered')})
|
name = models.CharField(
|
||||||
status = models.IntegerField(_('status'))
|
_("name"),
|
||||||
type = models.CharField(_('type'), max_length=100)
|
max_length=20,
|
||||||
size = models.IntegerField(_('size'))
|
error_messages={"required": _("No pool name has been entered")},
|
||||||
volumes = models.IntegerField(_('volumes'))
|
)
|
||||||
|
status = models.IntegerField(_("status"))
|
||||||
|
type = models.CharField(_("type"), max_length=100)
|
||||||
|
size = models.IntegerField(_("size"))
|
||||||
|
volumes = models.IntegerField(_("volumes"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
managed = False
|
managed = False
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.name}'
|
return f"{self.name}"
|
||||||
|
|
||||||
|
|
||||||
class Volume(models.Model):
|
class Volume(models.Model):
|
||||||
name = models.CharField(_('name'), max_length=128)
|
name = models.CharField(_("name"), max_length=128)
|
||||||
type = models.CharField(_('format'), max_length=12, choices=(('qcow2', 'qcow2 (recommended)'), ('qcow', 'qcow'), ('raw', 'raw')))
|
type = models.CharField(
|
||||||
allocation = models.IntegerField(_('allocation'))
|
_("format"),
|
||||||
size = models.IntegerField(_('size'))
|
max_length=12,
|
||||||
|
choices=(("qcow2", "qcow2 (recommended)"), ("qcow", "qcow"), ("raw", "raw")),
|
||||||
|
)
|
||||||
|
allocation = models.IntegerField(_("allocation"))
|
||||||
|
size = models.IntegerField(_("size"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
managed = False
|
managed = False
|
||||||
verbose_name_plural = "Volumes"
|
verbose_name_plural = "Volumes"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.name}'
|
return f"{self.name}"
|
||||||
|
|
||||||
|
|
||||||
class Storage(models.Model):
|
class Storage(models.Model):
|
||||||
state = models.IntegerField(_('state'))
|
state = models.IntegerField(_("state"))
|
||||||
size = models.IntegerField(_('size'))
|
size = models.IntegerField(_("size"))
|
||||||
free = models.IntegerField(_('free'))
|
free = models.IntegerField(_("free"))
|
||||||
status = models.CharField(_('status'), max_length=128)
|
status = models.CharField(_("status"), max_length=128)
|
||||||
path = models.CharField(_('path'), max_length=128)
|
path = models.CharField(_("path"), max_length=128)
|
||||||
type = models.CharField(_('type'), max_length=128)
|
type = models.CharField(_("type"), max_length=128)
|
||||||
autostart = models.BooleanField(_('autostart'))
|
autostart = models.BooleanField(_("autostart"))
|
||||||
volumes = models.ForeignKey(Volume, related_name="storage_volumes", on_delete=models.DO_NOTHING)
|
volumes = models.ForeignKey(
|
||||||
|
Volume, related_name="storage_volumes", on_delete=models.DO_NOTHING
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
managed = False
|
managed = False
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.path}'
|
return f"{self.path}"
|
||||||
|
|
|
@ -27,7 +27,12 @@ def storages(request, compute_id):
|
||||||
errors = False
|
errors = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = wvmStorages(compute.hostname, compute.login, compute.password, compute.type)
|
conn = wvmStorages(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type
|
||||||
|
)
|
||||||
storages = conn.get_storages_info()
|
storages = conn.get_storages_info()
|
||||||
secrets = conn.get_secrets()
|
secrets = conn.get_secrets()
|
||||||
|
|
||||||
|
@ -45,7 +50,11 @@ def storages(request, compute_id):
|
||||||
msg = _("You need create secret for pool")
|
msg = _("You need create secret for pool")
|
||||||
messages.error(request, msg)
|
messages.error(request, msg)
|
||||||
errors = True
|
errors = True
|
||||||
if not data["ceph_pool"] and not data["ceph_host"] and not data["ceph_user"]:
|
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")
|
msg = _("You need input all fields for creating ceph pool")
|
||||||
messages.error(request, msg)
|
messages.error(request, msg)
|
||||||
errors = True
|
errors = True
|
||||||
|
@ -69,8 +78,15 @@ def storages(request, compute_id):
|
||||||
data["target"],
|
data["target"],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
conn.create_storage(data["stg_type"], data["name"], data["source"], data["target"])
|
conn.create_storage(
|
||||||
return HttpResponseRedirect(reverse("storage", args=[compute_id, data["name"]]))
|
data["stg_type"],
|
||||||
|
data["name"],
|
||||||
|
data["source"],
|
||||||
|
data["target"],
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
reverse("storage", args=[compute_id, data["name"]])
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
for msg_err in form.errors.values():
|
for msg_err in form.errors.values():
|
||||||
messages.error(request, msg_err.as_text())
|
messages.error(request, msg_err.as_text())
|
||||||
|
@ -94,19 +110,26 @@ def storage(request, compute_id, pool):
|
||||||
target = os.path.normpath(os.path.join(path, str(f_name)))
|
target = os.path.normpath(os.path.join(path, str(f_name)))
|
||||||
if not target.startswith(path):
|
if not target.startswith(path):
|
||||||
raise Exception(_("Security Issues with file uploading"))
|
raise Exception(_("Security Issues with file uploading"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(target, "wb+") as f:
|
with open(target, "wb+") as f:
|
||||||
for chunk in f_name.chunks():
|
for chunk in f_name.chunks():
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
messages.error(request, _("File not found. Check the path variable and filename"))
|
messages.error(
|
||||||
|
request, _("File not found. Check the path variable and filename")
|
||||||
|
)
|
||||||
|
|
||||||
compute = get_object_or_404(Compute, pk=compute_id)
|
compute = get_object_or_404(Compute, pk=compute_id)
|
||||||
meta_prealloc = False
|
meta_prealloc = False
|
||||||
form = CreateVolumeForm()
|
form = CreateVolumeForm()
|
||||||
|
|
||||||
conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, pool)
|
conn = wvmStorage(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type, pool
|
||||||
|
)
|
||||||
|
|
||||||
storages = conn.get_storages()
|
storages = conn.get_storages()
|
||||||
state = conn.is_active()
|
state = conn.is_active()
|
||||||
|
@ -147,7 +170,9 @@ def storage(request, compute_id, pool):
|
||||||
volname = request.POST.get("volname", "")
|
volname = request.POST.get("volname", "")
|
||||||
vol = conn.get_volume(volname)
|
vol = conn.get_volume(volname)
|
||||||
vol.delete(0)
|
vol.delete(0)
|
||||||
messages.success(request, _("Volume: %(vol)s is deleted.") % {"vol": volname})
|
messages.success(
|
||||||
|
request, _("Volume: %(vol)s is deleted.") % {"vol": volname}
|
||||||
|
)
|
||||||
return redirect(reverse("storage", args=[compute.id, pool]))
|
return redirect(reverse("storage", args=[compute.id, pool]))
|
||||||
# return HttpResponseRedirect(request.get_full_path())
|
# return HttpResponseRedirect(request.get_full_path())
|
||||||
if "iso_upload" in request.POST:
|
if "iso_upload" in request.POST:
|
||||||
|
@ -156,7 +181,10 @@ def storage(request, compute_id, pool):
|
||||||
messages.error(request, error_msg)
|
messages.error(request, error_msg)
|
||||||
else:
|
else:
|
||||||
handle_uploaded_file(path, request.FILES["file"])
|
handle_uploaded_file(path, request.FILES["file"])
|
||||||
messages.success(request, _("ISO: %(file)s is uploaded.") % {"file": request.FILES["file"]})
|
messages.success(
|
||||||
|
request,
|
||||||
|
_("ISO: %(file)s is uploaded.") % {"file": request.FILES["file"]},
|
||||||
|
)
|
||||||
return HttpResponseRedirect(request.get_full_path())
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
if "cln_volume" in request.POST:
|
if "cln_volume" in request.POST:
|
||||||
form = CloneImage(request.POST)
|
form = CloneImage(request.POST)
|
||||||
|
@ -174,10 +202,13 @@ def storage(request, compute_id, pool):
|
||||||
else:
|
else:
|
||||||
format = None
|
format = None
|
||||||
try:
|
try:
|
||||||
name = conn.clone_volume(data["image"], data["name"], format, meta_prealloc)
|
name = conn.clone_volume(
|
||||||
|
data["image"], data["name"], format, meta_prealloc
|
||||||
|
)
|
||||||
messages.success(
|
messages.success(
|
||||||
request,
|
request,
|
||||||
_("%(image)s image cloned as %(name)s successfully") % {"image": data["image"], "name": name},
|
_("%(image)s image cloned as %(name)s successfully")
|
||||||
|
% {"image": data["image"], "name": name},
|
||||||
)
|
)
|
||||||
return HttpResponseRedirect(request.get_full_path())
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
except libvirtError as lib_err:
|
except libvirtError as lib_err:
|
||||||
|
@ -202,7 +233,13 @@ def create_volume(request, compute_id, pool):
|
||||||
compute = get_object_or_404(Compute, pk=compute_id)
|
compute = get_object_or_404(Compute, pk=compute_id)
|
||||||
meta_prealloc = False
|
meta_prealloc = False
|
||||||
|
|
||||||
conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, pool)
|
conn = wvmStorage(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type,
|
||||||
|
pool
|
||||||
|
)
|
||||||
|
|
||||||
storages = conn.get_storages()
|
storages = conn.get_storages()
|
||||||
|
|
||||||
|
@ -223,7 +260,9 @@ def create_volume(request, compute_id, pool):
|
||||||
disk_owner_uid,
|
disk_owner_uid,
|
||||||
disk_owner_gid,
|
disk_owner_gid,
|
||||||
)
|
)
|
||||||
messages.success(request, _("Image file %(name)s is created successfully") % {"name": name})
|
messages.success(
|
||||||
|
request, _("Image file %(name)s is created successfully") % {"name": name}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
for msg_err in form.errors.values():
|
for msg_err in form.errors.values():
|
||||||
messages.error(request, msg_err.as_text())
|
messages.error(request, msg_err.as_text())
|
||||||
|
@ -241,7 +280,13 @@ def get_volumes(request, compute_id, pool):
|
||||||
data = {}
|
data = {}
|
||||||
compute = get_object_or_404(Compute, pk=compute_id)
|
compute = get_object_or_404(Compute, pk=compute_id)
|
||||||
try:
|
try:
|
||||||
conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, pool)
|
conn = wvmStorage(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type,
|
||||||
|
pool
|
||||||
|
)
|
||||||
conn.refresh()
|
conn.refresh()
|
||||||
data["vols"] = sorted(conn.get_volumes())
|
data["vols"] = sorted(conn.get_volumes())
|
||||||
except libvirtError:
|
except libvirtError:
|
||||||
|
|
|
@ -2,7 +2,10 @@ from django import forms
|
||||||
|
|
||||||
|
|
||||||
class AddSecret(forms.Form):
|
class AddSecret(forms.Form):
|
||||||
ephemeral = forms.ChoiceField(required=True, choices=(('no', 'no'), ('yes', 'yes')))
|
ephemeral = forms.ChoiceField(required=True, choices=(("no", "no"), ("yes", "yes")))
|
||||||
private = 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')))
|
usage_type = forms.ChoiceField(
|
||||||
|
required=True,
|
||||||
|
choices=(("ceph", "ceph"), ("volume", "volume"), ("iscsi", "iscsi")),
|
||||||
|
)
|
||||||
data = forms.CharField(max_length=100, required=True)
|
data = forms.CharField(max_length=100, required=True)
|
||||||
|
|
|
@ -35,7 +35,12 @@ def secrets(request, compute_id):
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = wvmSecrets(compute.hostname, compute.login, compute.password, compute.type)
|
conn = wvmSecrets(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type
|
||||||
|
)
|
||||||
secrets = conn.get_secrets()
|
secrets = conn.get_secrets()
|
||||||
|
|
||||||
for uuid in secrets:
|
for uuid in secrets:
|
||||||
|
|
|
@ -42,8 +42,7 @@ class wvmHostDetails(wvmConnect):
|
||||||
total = sum(self.wvm.getCPUStats(-1, 0).values())
|
total = sum(self.wvm.getCPUStats(-1, 0).values())
|
||||||
diff_idle = idle - prev_idle
|
diff_idle = idle - prev_idle
|
||||||
diff_total = total - prev_total
|
diff_total = total - prev_total
|
||||||
diff_usage = (1000 * (diff_total - diff_idle) /
|
diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10
|
||||||
diff_total + 5) / 10
|
|
||||||
prev_total = total
|
prev_total = total
|
||||||
prev_idle = idle
|
prev_idle = idle
|
||||||
if num == 0:
|
if num == 0:
|
||||||
|
@ -61,6 +60,8 @@ class wvmHostDetails(wvmConnect):
|
||||||
info.append(self.wvm.getInfo()[0]) # architecture
|
info.append(self.wvm.getInfo()[0]) # architecture
|
||||||
info.append(self.wvm.getInfo()[1] * 1048576) # memory
|
info.append(self.wvm.getInfo()[1] * 1048576) # memory
|
||||||
info.append(self.wvm.getInfo()[2]) # cpu core count
|
info.append(self.wvm.getInfo()[2]) # cpu core count
|
||||||
info.append(get_xml_path(self.wvm.getSysinfo(0), func=cpu_version)) # cpu version
|
info.append(
|
||||||
|
get_xml_path(self.wvm.getSysinfo(0), func=cpu_version)
|
||||||
|
) # cpu version
|
||||||
info.append(self.wvm.getURI()) # uri
|
info.append(self.wvm.getURI()) # uri
|
||||||
return info
|
return info
|
||||||
|
|
|
@ -21,10 +21,7 @@ try:
|
||||||
VIR_MIGRATE_POSTCOPY,
|
VIR_MIGRATE_POSTCOPY,
|
||||||
)
|
)
|
||||||
from libvirt import VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT
|
from libvirt import VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT
|
||||||
from libvirt_qemu import (
|
from libvirt_qemu import qemuAgentCommand, VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT
|
||||||
qemuAgentCommand,
|
|
||||||
VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT
|
|
||||||
)
|
|
||||||
except:
|
except:
|
||||||
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE
|
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE
|
||||||
|
|
||||||
|
@ -94,7 +91,18 @@ class wvmInstances(wvmConnect):
|
||||||
dom = self.get_instance(name)
|
dom = self.get_instance(name)
|
||||||
dom.resume()
|
dom.resume()
|
||||||
|
|
||||||
def moveto(self, conn, name, live, unsafe, undefine, offline, autoconverge=False, compress=False, postcopy=False):
|
def moveto(
|
||||||
|
self,
|
||||||
|
conn,
|
||||||
|
name,
|
||||||
|
live,
|
||||||
|
unsafe,
|
||||||
|
undefine,
|
||||||
|
offline,
|
||||||
|
autoconverge=False,
|
||||||
|
compress=False,
|
||||||
|
postcopy=False,
|
||||||
|
):
|
||||||
flags = VIR_MIGRATE_PERSIST_DEST
|
flags = VIR_MIGRATE_PERSIST_DEST
|
||||||
if live and conn.get_status() != 5:
|
if live and conn.get_status() != 5:
|
||||||
flags |= VIR_MIGRATE_LIVE
|
flags |= VIR_MIGRATE_LIVE
|
||||||
|
@ -117,29 +125,39 @@ class wvmInstances(wvmConnect):
|
||||||
dom_emulator = conn.get_dom_emulator()
|
dom_emulator = conn.get_dom_emulator()
|
||||||
|
|
||||||
if dom_emulator != self.get_emulator(dom_arch):
|
if dom_emulator != self.get_emulator(dom_arch):
|
||||||
raise libvirtError("Destination host emulator is different. Cannot be migrated")
|
raise libvirtError(
|
||||||
|
"Destination host emulator is different. Cannot be migrated"
|
||||||
|
)
|
||||||
|
|
||||||
dom.migrate(self.wvm, flags, None, None, 0)
|
dom.migrate(self.wvm, flags, None, None, 0)
|
||||||
|
|
||||||
def graphics_type(self, name):
|
def graphics_type(self, name):
|
||||||
inst = self.get_instance(name)
|
inst = self.get_instance(name)
|
||||||
console_type = util.get_xml_path(inst.XMLDesc(0), "/domain/devices/graphics/@type")
|
console_type = util.get_xml_path(
|
||||||
|
inst.XMLDesc(0), "/domain/devices/graphics/@type"
|
||||||
|
)
|
||||||
if console_type is None:
|
if console_type is None:
|
||||||
return "None"
|
return "None"
|
||||||
return console_type
|
return console_type
|
||||||
|
|
||||||
def graphics_listen(self, name):
|
def graphics_listen(self, name):
|
||||||
inst = self.get_instance(name)
|
inst = self.get_instance(name)
|
||||||
listener_addr = util.get_xml_path(inst.XMLDesc(0), "/domain/devices/graphics/@listen")
|
listener_addr = util.get_xml_path(
|
||||||
|
inst.XMLDesc(0), "/domain/devices/graphics/@listen"
|
||||||
|
)
|
||||||
if listener_addr is None:
|
if listener_addr is None:
|
||||||
listener_addr = util.get_xml_path(inst.XMLDesc(0), "/domain/devices/graphics/listen/@address")
|
listener_addr = util.get_xml_path(
|
||||||
|
inst.XMLDesc(0), "/domain/devices/graphics/listen/@address"
|
||||||
|
)
|
||||||
if listener_addr is None:
|
if listener_addr is None:
|
||||||
return "None"
|
return "None"
|
||||||
return listener_addr
|
return listener_addr
|
||||||
|
|
||||||
def graphics_port(self, name):
|
def graphics_port(self, name):
|
||||||
inst = self.get_instance(name)
|
inst = self.get_instance(name)
|
||||||
console_port = util.get_xml_path(inst.XMLDesc(0), "/domain/devices/graphics/@port")
|
console_port = util.get_xml_path(
|
||||||
|
inst.XMLDesc(0), "/domain/devices/graphics/@port"
|
||||||
|
)
|
||||||
if console_port is None:
|
if console_port is None:
|
||||||
return "None"
|
return "None"
|
||||||
return console_port
|
return console_port
|
||||||
|
@ -153,7 +171,9 @@ class wvmInstances(wvmConnect):
|
||||||
|
|
||||||
def graphics_passwd(self, name):
|
def graphics_passwd(self, name):
|
||||||
inst = self.get_instance(name)
|
inst = self.get_instance(name)
|
||||||
password = util.get_xml_path(inst.XMLDesc(VIR_DOMAIN_XML_SECURE), "/domain/devices/graphics/@passwd")
|
password = util.get_xml_path(
|
||||||
|
inst.XMLDesc(VIR_DOMAIN_XML_SECURE), "/domain/devices/graphics/@passwd"
|
||||||
|
)
|
||||||
if password is None:
|
if password is None:
|
||||||
return "None"
|
return "None"
|
||||||
return password
|
return password
|
||||||
|
@ -167,27 +187,30 @@ class wvmInstance(wvmConnect):
|
||||||
|
|
||||||
def osinfo(self):
|
def osinfo(self):
|
||||||
info_results = qemuAgentCommand(
|
info_results = qemuAgentCommand(
|
||||||
self.instance,
|
self.instance,
|
||||||
'{"execute":"guest-get-osinfo"}',
|
'{"execute":"guest-get-osinfo"}',
|
||||||
VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT, 0
|
VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT,
|
||||||
)
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
timezone_results = qemuAgentCommand(
|
timezone_results = qemuAgentCommand(
|
||||||
self.instance,
|
self.instance,
|
||||||
'{"execute":"guest-get-timezone"}',
|
'{"execute":"guest-get-timezone"}',
|
||||||
VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT, 0
|
VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT,
|
||||||
)
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
hostname_results = qemuAgentCommand(
|
hostname_results = qemuAgentCommand(
|
||||||
self.instance,
|
self.instance,
|
||||||
'{"execute":"guest-get-host-name"}',
|
'{"execute":"guest-get-host-name"}',
|
||||||
VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT, 0
|
VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT,
|
||||||
)
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
info_results = json.loads(info_results).get('return')
|
info_results = json.loads(info_results).get("return")
|
||||||
|
|
||||||
timezone_results = json.loads(timezone_results).get('return')
|
timezone_results = json.loads(timezone_results).get("return")
|
||||||
hostname_results = json.loads(hostname_results).get('return')
|
hostname_results = json.loads(hostname_results).get("return")
|
||||||
|
|
||||||
info_results.update(timezone_results)
|
info_results.update(timezone_results)
|
||||||
info_results.update(hostname_results)
|
info_results.update(hostname_results)
|
||||||
|
@ -283,7 +306,11 @@ class wvmInstance(wvmConnect):
|
||||||
enabled = vcpu.get("enabled")
|
enabled = vcpu.get("enabled")
|
||||||
hotplug = vcpu.get("hotpluggable")
|
hotplug = vcpu.get("hotpluggable")
|
||||||
order = vcpu.get("order")
|
order = vcpu.get("order")
|
||||||
vcpus[vcpu_id] = {"enabled": enabled, "hotpluggable": hotplug, "order": order}
|
vcpus[vcpu_id] = {
|
||||||
|
"enabled": enabled,
|
||||||
|
"hotpluggable": hotplug,
|
||||||
|
"order": order,
|
||||||
|
}
|
||||||
|
|
||||||
return vcpus
|
return vcpus
|
||||||
|
|
||||||
|
@ -382,7 +409,9 @@ class wvmInstance(wvmConnect):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.is_agent_ready():
|
if self.is_agent_ready():
|
||||||
self._ip_cache["qemuga"] = self._get_interface_addresses(VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT)
|
self._ip_cache["qemuga"] = self._get_interface_addresses(
|
||||||
|
VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT
|
||||||
|
)
|
||||||
|
|
||||||
arp_flag = 3 # libvirt."VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP"
|
arp_flag = 3 # libvirt."VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP"
|
||||||
self._ip_cache["arp"] = self._get_interface_addresses(arp_flag)
|
self._ip_cache["arp"] = self._get_interface_addresses(arp_flag)
|
||||||
|
@ -395,9 +424,17 @@ class wvmInstance(wvmConnect):
|
||||||
interface_type = net.xpath("@type")[0]
|
interface_type = net.xpath("@type")[0]
|
||||||
mac_inst = net.xpath("mac/@address")[0]
|
mac_inst = net.xpath("mac/@address")[0]
|
||||||
nic_inst = net.xpath("source/@network|source/@bridge|source/@dev")[0]
|
nic_inst = net.xpath("source/@network|source/@bridge|source/@dev")[0]
|
||||||
target_inst = "" if not net.xpath("target/@dev") else net.xpath("target/@dev")[0]
|
target_inst = (
|
||||||
link_state = "up" if not net.xpath("link") else net.xpath("link/@state")[0]
|
"" if not net.xpath("target/@dev") else net.xpath("target/@dev")[0]
|
||||||
filterref_inst = "" if not net.xpath("filterref/@filter") else net.xpath("filterref/@filter")[0]
|
)
|
||||||
|
link_state = (
|
||||||
|
"up" if not net.xpath("link") else net.xpath("link/@state")[0]
|
||||||
|
)
|
||||||
|
filterref_inst = (
|
||||||
|
""
|
||||||
|
if not net.xpath("filterref/@filter")
|
||||||
|
else net.xpath("filterref/@filter")[0]
|
||||||
|
)
|
||||||
model_type = net.xpath("model/@type")[0]
|
model_type = net.xpath("model/@type")[0]
|
||||||
if net.xpath("bandwidth/inbound"):
|
if net.xpath("bandwidth/inbound"):
|
||||||
in_attr = net.xpath("bandwidth/inbound")[0]
|
in_attr = net.xpath("bandwidth/inbound")[0]
|
||||||
|
@ -451,7 +488,9 @@ class wvmInstance(wvmConnect):
|
||||||
dev = disk.xpath("target/@dev")[0]
|
dev = disk.xpath("target/@dev")[0]
|
||||||
bus = disk.xpath("target/@bus")[0]
|
bus = disk.xpath("target/@bus")[0]
|
||||||
try:
|
try:
|
||||||
src_file = disk.xpath("source/@file|source/@dev|source/@name")[0]
|
src_file = disk.xpath(
|
||||||
|
"source/@file|source/@dev|source/@name"
|
||||||
|
)[0]
|
||||||
except Exception:
|
except Exception:
|
||||||
v = disk.xpath("source/@volume")[0]
|
v = disk.xpath("source/@volume")[0]
|
||||||
s_name = disk.xpath("source/@pool")[0]
|
s_name = disk.xpath("source/@pool")[0]
|
||||||
|
@ -480,7 +519,11 @@ class wvmInstance(wvmConnect):
|
||||||
|
|
||||||
readonly = True if disk.xpath("readonly") else False
|
readonly = True if disk.xpath("readonly") else False
|
||||||
shareable = True if disk.xpath("shareable") else False
|
shareable = True if disk.xpath("shareable") else False
|
||||||
serial = disk.xpath("serial")[0].text if disk.xpath("serial") else None
|
serial = (
|
||||||
|
disk.xpath("serial")[0].text
|
||||||
|
if disk.xpath("serial")
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
vol = self.get_volume_by_path(src_file)
|
vol = self.get_volume_by_path(src_file)
|
||||||
|
@ -541,7 +584,15 @@ class wvmInstance(wvmConnect):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
result.append({"dev": dev, "image": volume, "storage": storage, "path": src_file, "bus": bus})
|
result.append(
|
||||||
|
{
|
||||||
|
"dev": dev,
|
||||||
|
"image": volume,
|
||||||
|
"storage": storage,
|
||||||
|
"path": src_file,
|
||||||
|
"bus": bus,
|
||||||
|
}
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
return util.get_xml_path(self._XMLDesc(0), func=disks)
|
return util.get_xml_path(self._XMLDesc(0), func=disks)
|
||||||
|
@ -567,7 +618,9 @@ class wvmInstance(wvmConnect):
|
||||||
elif flag == -1: # Remove
|
elif flag == -1: # Remove
|
||||||
os.remove(menu)
|
os.remove(menu)
|
||||||
else:
|
else:
|
||||||
raise Exception("Unknown boot menu option, please choose one of 0:disable, 1:enable, -1:remove")
|
raise Exception(
|
||||||
|
"Unknown boot menu option, please choose one of 0:disable, 1:enable, -1:remove"
|
||||||
|
)
|
||||||
|
|
||||||
xmldom = ElementTree.tostring(tree).decode()
|
xmldom = ElementTree.tostring(tree).decode()
|
||||||
self._defineXML(xmldom)
|
self._defineXML(xmldom)
|
||||||
|
@ -614,7 +667,11 @@ class wvmInstance(wvmConnect):
|
||||||
elif dev_type == "usb":
|
elif dev_type == "usb":
|
||||||
pass
|
pass
|
||||||
|
|
||||||
boot_order[int(idx) - 1] = {"type": dev_type, "dev": dev_device, "target": dev_target}
|
boot_order[int(idx) - 1] = {
|
||||||
|
"type": dev_type,
|
||||||
|
"dev": dev_device,
|
||||||
|
"target": dev_target,
|
||||||
|
}
|
||||||
|
|
||||||
return boot_order
|
return boot_order
|
||||||
|
|
||||||
|
@ -716,7 +773,7 @@ class wvmInstance(wvmConnect):
|
||||||
self,
|
self,
|
||||||
target_dev,
|
target_dev,
|
||||||
source,
|
source,
|
||||||
source_info = None,
|
source_info=None,
|
||||||
pool_type="dir",
|
pool_type="dir",
|
||||||
target_bus="ide",
|
target_bus="ide",
|
||||||
disk_type="file",
|
disk_type="file",
|
||||||
|
@ -733,7 +790,11 @@ class wvmInstance(wvmConnect):
|
||||||
):
|
):
|
||||||
|
|
||||||
additionals = ""
|
additionals = ""
|
||||||
if cache_mode is not None and cache_mode != "default" and disk_device != "cdrom":
|
if (
|
||||||
|
cache_mode is not None
|
||||||
|
and cache_mode != "default"
|
||||||
|
and disk_device != "cdrom"
|
||||||
|
):
|
||||||
additionals += f"cache='{cache_mode}' "
|
additionals += f"cache='{cache_mode}' "
|
||||||
if io_mode is not None and io_mode != "default":
|
if io_mode is not None and io_mode != "default":
|
||||||
additionals += f"io='{io_mode}' "
|
additionals += f"io='{io_mode}' "
|
||||||
|
@ -746,31 +807,33 @@ class wvmInstance(wvmConnect):
|
||||||
if disk_device == "cdrom":
|
if disk_device == "cdrom":
|
||||||
xml_disk += f"<driver name='{driver_name}' type='{format_type}'/>"
|
xml_disk += f"<driver name='{driver_name}' type='{format_type}'/>"
|
||||||
elif disk_device == "disk":
|
elif disk_device == "disk":
|
||||||
xml_disk += f"<driver name='{driver_name}' type='{format_type}' {additionals}/>"
|
xml_disk += (
|
||||||
|
f"<driver name='{driver_name}' type='{format_type}' {additionals}/>"
|
||||||
|
)
|
||||||
|
|
||||||
if disk_type == 'file':
|
if disk_type == "file":
|
||||||
xml_disk += f"<source file='{source}'/>"
|
xml_disk += f"<source file='{source}'/>"
|
||||||
elif disk_type == 'network':
|
elif disk_type == "network":
|
||||||
if pool_type == 'rbd':
|
if pool_type == "rbd":
|
||||||
auth_type = source_info.get('auth_type')
|
auth_type = source_info.get("auth_type")
|
||||||
auth_user = source_info.get('auth_user')
|
auth_user = source_info.get("auth_user")
|
||||||
auth_uuid = source_info.get("auth_uuid")
|
auth_uuid = source_info.get("auth_uuid")
|
||||||
xml_disk += f"""<auth username='{auth_user}'>
|
xml_disk += f"""<auth username='{auth_user}'>
|
||||||
<secret type='{auth_type}' uuid='{auth_uuid}'/>
|
<secret type='{auth_type}' uuid='{auth_uuid}'/>
|
||||||
</auth>"""
|
</auth>"""
|
||||||
xml_disk += f"""<source protocol='{pool_type}' name='{source}'>"""
|
xml_disk += f"""<source protocol='{pool_type}' name='{source}'>"""
|
||||||
for host in source_info.get("hosts"):
|
for host in source_info.get("hosts"):
|
||||||
if host.get('hostport'):
|
if host.get("hostport"):
|
||||||
xml_disk += f"""<host name="{host.get('hostname')}" port='{host.get('hostport')}'/>"""
|
xml_disk += f"""<host name="{host.get('hostname')}" port='{host.get('hostport')}'/>"""
|
||||||
else:
|
else:
|
||||||
xml_disk += f"""<host name="{host.get('hostname')}"/>"""
|
xml_disk += f"""<host name="{host.get('hostname')}"/>"""
|
||||||
xml_disk +="""</source>"""
|
xml_disk += """</source>"""
|
||||||
else:
|
else:
|
||||||
raise Exception("Not implemented disk type")
|
raise Exception("Not implemented disk type")
|
||||||
else:
|
else:
|
||||||
raise Exception("Not implemented disk type")
|
raise Exception("Not implemented disk type")
|
||||||
|
|
||||||
xml_disk +=f"<target dev='{target_dev}' bus='{target_bus}'/>"
|
xml_disk += f"<target dev='{target_dev}' bus='{target_bus}'/>"
|
||||||
if readonly or disk_device == "cdrom":
|
if readonly or disk_device == "cdrom":
|
||||||
xml_disk += """<readonly/>"""
|
xml_disk += """<readonly/>"""
|
||||||
if shareable:
|
if shareable:
|
||||||
|
@ -787,7 +850,9 @@ class wvmInstance(wvmConnect):
|
||||||
def detach_disk(self, target_dev):
|
def detach_disk(self, target_dev):
|
||||||
tree = etree.fromstring(self._XMLDesc(0))
|
tree = etree.fromstring(self._XMLDesc(0))
|
||||||
|
|
||||||
disk_el = tree.xpath("./devices/disk/target[@dev='{}']".format(target_dev))[0].getparent()
|
disk_el = tree.xpath("./devices/disk/target[@dev='{}']".format(target_dev))[
|
||||||
|
0
|
||||||
|
].getparent()
|
||||||
xml_disk = etree.tostring(disk_el).decode()
|
xml_disk = etree.tostring(disk_el).decode()
|
||||||
devices = tree.find("devices")
|
devices = tree.find("devices")
|
||||||
devices.remove(disk_el)
|
devices.remove(disk_el)
|
||||||
|
@ -813,7 +878,9 @@ class wvmInstance(wvmConnect):
|
||||||
detect_zeroes_mode,
|
detect_zeroes_mode,
|
||||||
):
|
):
|
||||||
tree = etree.fromstring(self._XMLDesc(0))
|
tree = etree.fromstring(self._XMLDesc(0))
|
||||||
disk_el = tree.xpath("./devices/disk/target[@dev='{}']".format(target_dev))[0].getparent()
|
disk_el = tree.xpath("./devices/disk/target[@dev='{}']".format(target_dev))[
|
||||||
|
0
|
||||||
|
].getparent()
|
||||||
old_disk_type = disk_el.get("type")
|
old_disk_type = disk_el.get("type")
|
||||||
old_disk_device = disk_el.get("device")
|
old_disk_device = disk_el.get("device")
|
||||||
old_driver_name = disk_el.xpath("driver/@name")[0]
|
old_driver_name = disk_el.xpath("driver/@name")[0]
|
||||||
|
@ -833,7 +900,9 @@ class wvmInstance(wvmConnect):
|
||||||
if old_disk_device == "cdrom":
|
if old_disk_device == "cdrom":
|
||||||
xml_disk += f"<driver name='{old_driver_name}' type='{format}'/>"
|
xml_disk += f"<driver name='{old_driver_name}' type='{format}'/>"
|
||||||
elif old_disk_device == "disk":
|
elif old_disk_device == "disk":
|
||||||
xml_disk += f"<driver name='{old_driver_name}' type='{format}' {additionals}/>"
|
xml_disk += (
|
||||||
|
f"<driver name='{old_driver_name}' type='{format}' {additionals}/>"
|
||||||
|
)
|
||||||
|
|
||||||
xml_disk += f"""<source file='{source}'/>
|
xml_disk += f"""<source file='{source}'/>
|
||||||
<target dev='{target_dev}' bus='{target_bus}'/>"""
|
<target dev='{target_dev}' bus='{target_bus}'/>"""
|
||||||
|
@ -855,7 +924,7 @@ class wvmInstance(wvmConnect):
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
cpu_use_now = self.instance.info()[4]
|
cpu_use_now = self.instance.info()[4]
|
||||||
diff_usage = cpu_use_now - cpu_use_ago
|
diff_usage = cpu_use_now - cpu_use_ago
|
||||||
cpu_usage["cpu"] = 100 * diff_usage / (1 * nbcore * 10 ** 9)
|
cpu_usage["cpu"] = 100 * diff_usage / (1 * nbcore * 10**9)
|
||||||
else:
|
else:
|
||||||
cpu_usage["cpu"] = 0
|
cpu_usage["cpu"] = 0
|
||||||
return cpu_usage
|
return cpu_usage
|
||||||
|
@ -864,7 +933,7 @@ class wvmInstance(wvmConnect):
|
||||||
self.instance.setVcpu(str(cpu_id), enabled)
|
self.instance.setVcpu(str(cpu_id), enabled)
|
||||||
|
|
||||||
def set_vcpu_hotplug(self, status, vcpus_hotplug=0):
|
def set_vcpu_hotplug(self, status, vcpus_hotplug=0):
|
||||||
""" vcpus_hotplug = 0 make all vpus hotpluggable """
|
"""vcpus_hotplug = 0 make all vpus hotpluggable"""
|
||||||
vcpus_hotplug = int(self.get_vcpu()) if vcpus_hotplug == 0 else vcpus_hotplug
|
vcpus_hotplug = int(self.get_vcpu()) if vcpus_hotplug == 0 else vcpus_hotplug
|
||||||
if self.get_status() == 5: # shutoff
|
if self.get_status() == 5: # shutoff
|
||||||
if status:
|
if status:
|
||||||
|
@ -887,7 +956,9 @@ class wvmInstance(wvmConnect):
|
||||||
parent.remove(vcpu)
|
parent.remove(vcpu)
|
||||||
self._defineXML(etree.tostring(tree).decode())
|
self._defineXML(etree.tostring(tree).decode())
|
||||||
else:
|
else:
|
||||||
raise libvirtError("Please shutdown the instance then try to enable vCPU hotplug")
|
raise libvirtError(
|
||||||
|
"Please shutdown the instance then try to enable vCPU hotplug"
|
||||||
|
)
|
||||||
|
|
||||||
def mem_usage(self):
|
def mem_usage(self):
|
||||||
mem_usage = {}
|
mem_usage = {}
|
||||||
|
@ -983,9 +1054,13 @@ class wvmInstance(wvmConnect):
|
||||||
return telnet_port
|
return telnet_port
|
||||||
|
|
||||||
def get_console_listener_addr(self):
|
def get_console_listener_addr(self):
|
||||||
listener_addr = util.get_xml_path(self._XMLDesc(0), "/domain/devices/graphics/@listen")
|
listener_addr = util.get_xml_path(
|
||||||
|
self._XMLDesc(0), "/domain/devices/graphics/@listen"
|
||||||
|
)
|
||||||
if listener_addr is None:
|
if listener_addr is None:
|
||||||
listener_addr = util.get_xml_path(self._XMLDesc(0), "/domain/devices/graphics/listen/@address")
|
listener_addr = util.get_xml_path(
|
||||||
|
self._XMLDesc(0), "/domain/devices/graphics/listen/@address"
|
||||||
|
)
|
||||||
if listener_addr is None:
|
if listener_addr is None:
|
||||||
return "127.0.0.1"
|
return "127.0.0.1"
|
||||||
return listener_addr
|
return listener_addr
|
||||||
|
@ -1021,9 +1096,13 @@ class wvmInstance(wvmConnect):
|
||||||
return socket
|
return socket
|
||||||
|
|
||||||
def get_console_type(self):
|
def get_console_type(self):
|
||||||
console_type = util.get_xml_path(self._XMLDesc(0), "/domain/devices/graphics/@type")
|
console_type = util.get_xml_path(
|
||||||
|
self._XMLDesc(0), "/domain/devices/graphics/@type"
|
||||||
|
)
|
||||||
if console_type is None:
|
if console_type is None:
|
||||||
console_type = util.get_xml_path(self._XMLDesc(0), "/domain/devices/console/@type")
|
console_type = util.get_xml_path(
|
||||||
|
self._XMLDesc(0), "/domain/devices/console/@type"
|
||||||
|
)
|
||||||
return console_type
|
return console_type
|
||||||
|
|
||||||
def set_console_type(self, console_type):
|
def set_console_type(self, console_type):
|
||||||
|
@ -1046,18 +1125,24 @@ class wvmInstance(wvmConnect):
|
||||||
def get_console_port(self, console_type=None):
|
def get_console_port(self, console_type=None):
|
||||||
if console_type is None:
|
if console_type is None:
|
||||||
console_type = self.get_console_type()
|
console_type = self.get_console_type()
|
||||||
port = util.get_xml_path(self._XMLDesc(0), "/domain/devices/graphics[@type='%s']/@port" % console_type)
|
port = util.get_xml_path(
|
||||||
|
self._XMLDesc(0),
|
||||||
|
"/domain/devices/graphics[@type='%s']/@port" % console_type,
|
||||||
|
)
|
||||||
return port
|
return port
|
||||||
|
|
||||||
def get_console_websocket_port(self):
|
def get_console_websocket_port(self):
|
||||||
console_type = self.get_console_type()
|
console_type = self.get_console_type()
|
||||||
websocket_port = util.get_xml_path(
|
websocket_port = util.get_xml_path(
|
||||||
self._XMLDesc(0), "/domain/devices/graphics[@type='%s']/@websocket" % console_type
|
self._XMLDesc(0),
|
||||||
|
"/domain/devices/graphics[@type='%s']/@websocket" % console_type,
|
||||||
)
|
)
|
||||||
return websocket_port
|
return websocket_port
|
||||||
|
|
||||||
def get_console_passwd(self):
|
def get_console_passwd(self):
|
||||||
return util.get_xml_path(self._XMLDesc(VIR_DOMAIN_XML_SECURE), "/domain/devices/graphics/@passwd")
|
return util.get_xml_path(
|
||||||
|
self._XMLDesc(VIR_DOMAIN_XML_SECURE), "/domain/devices/graphics/@passwd"
|
||||||
|
)
|
||||||
|
|
||||||
def set_console_passwd(self, passwd):
|
def set_console_passwd(self, passwd):
|
||||||
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
|
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
|
||||||
|
@ -1100,10 +1185,15 @@ class wvmInstance(wvmConnect):
|
||||||
self._defineXML(newxml)
|
self._defineXML(newxml)
|
||||||
|
|
||||||
def get_console_keymap(self):
|
def get_console_keymap(self):
|
||||||
return util.get_xml_path(self._XMLDesc(VIR_DOMAIN_XML_SECURE), "/domain/devices/graphics/@keymap") or ""
|
return (
|
||||||
|
util.get_xml_path(
|
||||||
|
self._XMLDesc(VIR_DOMAIN_XML_SECURE), "/domain/devices/graphics/@keymap"
|
||||||
|
)
|
||||||
|
or ""
|
||||||
|
)
|
||||||
|
|
||||||
def get_video_model(self):
|
def get_video_model(self):
|
||||||
""" :return only primary video card"""
|
""":return only primary video card"""
|
||||||
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
|
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
|
||||||
tree = etree.fromstring(xml)
|
tree = etree.fromstring(xml)
|
||||||
video_models = tree.xpath("/domain/devices/video/model")
|
video_models = tree.xpath("/domain/devices/video/model")
|
||||||
|
@ -1112,7 +1202,7 @@ class wvmInstance(wvmConnect):
|
||||||
return model.get("type")
|
return model.get("type")
|
||||||
|
|
||||||
def set_video_model(self, model):
|
def set_video_model(self, model):
|
||||||
""" Changes only primary video card"""
|
"""Changes only primary video card"""
|
||||||
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
|
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
|
||||||
tree = etree.fromstring(xml)
|
tree = etree.fromstring(xml)
|
||||||
video_models = tree.xpath("/domain/devices/video/model")
|
video_models = tree.xpath("/domain/devices/video/model")
|
||||||
|
@ -1208,7 +1298,7 @@ class wvmInstance(wvmConnect):
|
||||||
self.instance.snapshotCreateXML(xml, flag)
|
self.instance.snapshotCreateXML(xml, flag)
|
||||||
|
|
||||||
def create_snapshot(self, name, desc=None):
|
def create_snapshot(self, name, desc=None):
|
||||||
state = 'shutoff' if self.get_status()==5 else 'running'
|
state = "shutoff" if self.get_status() == 5 else "running"
|
||||||
xml = """<domainsnapshot>
|
xml = """<domainsnapshot>
|
||||||
<name>%s</name>
|
<name>%s</name>
|
||||||
<description>%s</description>
|
<description>%s</description>
|
||||||
|
@ -1227,11 +1317,17 @@ class wvmInstance(wvmConnect):
|
||||||
self.recover_snapshot_xml()
|
self.recover_snapshot_xml()
|
||||||
|
|
||||||
def change_snapshot_xml(self):
|
def change_snapshot_xml(self):
|
||||||
xml_temp = self._XMLDesc(VIR_DOMAIN_XML_SECURE).replace("<loader readonly='yes' type='pflash'>","<loader readonly='yes' type='rom'>")
|
xml_temp = self._XMLDesc(VIR_DOMAIN_XML_SECURE).replace(
|
||||||
|
"<loader readonly='yes' type='pflash'>",
|
||||||
|
"<loader readonly='yes' type='rom'>",
|
||||||
|
)
|
||||||
self._defineXML(xml_temp)
|
self._defineXML(xml_temp)
|
||||||
|
|
||||||
def recover_snapshot_xml(self):
|
def recover_snapshot_xml(self):
|
||||||
xml_temp = self._XMLDesc(VIR_DOMAIN_XML_SECURE).replace("<loader readonly='yes' type='rom'>","<loader readonly='yes' type='pflash'>")
|
xml_temp = self._XMLDesc(VIR_DOMAIN_XML_SECURE).replace(
|
||||||
|
"<loader readonly='yes' type='rom'>",
|
||||||
|
"<loader readonly='yes' type='pflash'>",
|
||||||
|
)
|
||||||
self._defineXML(xml_temp)
|
self._defineXML(xml_temp)
|
||||||
|
|
||||||
def get_snapshot(self):
|
def get_snapshot(self):
|
||||||
|
@ -1239,8 +1335,12 @@ class wvmInstance(wvmConnect):
|
||||||
snapshot_list = self.instance.snapshotListNames(0)
|
snapshot_list = self.instance.snapshotListNames(0)
|
||||||
for snapshot in snapshot_list:
|
for snapshot in snapshot_list:
|
||||||
snap = self.instance.snapshotLookupByName(snapshot, 0)
|
snap = self.instance.snapshotLookupByName(snapshot, 0)
|
||||||
snap_description = util.get_xml_path(snap.getXMLDesc(0), "/domainsnapshot/description")
|
snap_description = util.get_xml_path(
|
||||||
snap_time_create = util.get_xml_path(snap.getXMLDesc(0), "/domainsnapshot/creationTime")
|
snap.getXMLDesc(0), "/domainsnapshot/description"
|
||||||
|
)
|
||||||
|
snap_time_create = util.get_xml_path(
|
||||||
|
snap.getXMLDesc(0), "/domainsnapshot/creationTime"
|
||||||
|
)
|
||||||
snapshots.append(
|
snapshots.append(
|
||||||
{
|
{
|
||||||
"date": datetime.fromtimestamp(int(snap_time_create)),
|
"date": datetime.fromtimestamp(int(snap_time_create)),
|
||||||
|
@ -1338,7 +1438,9 @@ class wvmInstance(wvmConnect):
|
||||||
elm.set("file", clone_path)
|
elm.set("file", clone_path)
|
||||||
|
|
||||||
vol = self.get_volume_by_path(source_file)
|
vol = self.get_volume_by_path(source_file)
|
||||||
vol_format = util.get_xml_path(vol.XMLDesc(0), "/volume/target/format/@type")
|
vol_format = util.get_xml_path(
|
||||||
|
vol.XMLDesc(0), "/volume/target/format/@type"
|
||||||
|
)
|
||||||
|
|
||||||
if vol_format == "qcow2" and meta_prealloc:
|
if vol_format == "qcow2" and meta_prealloc:
|
||||||
meta_prealloc = True
|
meta_prealloc = True
|
||||||
|
@ -1373,7 +1475,9 @@ class wvmInstance(wvmConnect):
|
||||||
elm.set("name", clone_name)
|
elm.set("name", clone_name)
|
||||||
|
|
||||||
vol = self.get_volume_by_path(source_name)
|
vol = self.get_volume_by_path(source_name)
|
||||||
vol_format = util.get_xml_path(vol.XMLDesc(0), "/volume/target/format/@type")
|
vol_format = util.get_xml_path(
|
||||||
|
vol.XMLDesc(0), "/volume/target/format/@type"
|
||||||
|
)
|
||||||
|
|
||||||
vol_clone_xml = f"""
|
vol_clone_xml = f"""
|
||||||
<volume type='network'>
|
<volume type='network'>
|
||||||
|
@ -1417,15 +1521,22 @@ class wvmInstance(wvmConnect):
|
||||||
bridge_name = None
|
bridge_name = None
|
||||||
return bridge_name
|
return bridge_name
|
||||||
|
|
||||||
def add_network(self, mac_address, source, source_type="net", model="virtio", nwfilter=None):
|
def add_network(
|
||||||
|
self,
|
||||||
|
mac_address,
|
||||||
|
source,
|
||||||
|
source_type="net",
|
||||||
|
model="virtio",
|
||||||
|
nwfilter=None
|
||||||
|
):
|
||||||
|
|
||||||
if source_type == "net":
|
if source_type == "net":
|
||||||
interface_type = "network"
|
interface_type = "network"
|
||||||
elif source_type == "bridge":
|
elif source_type == "bridge":
|
||||||
interface_type = "bridge"
|
interface_type = "bridge"
|
||||||
else:
|
else:
|
||||||
interface_type = "direct"
|
interface_type = "direct"
|
||||||
|
|
||||||
# network modes not handled: default is bridge
|
# network modes not handled: default is bridge
|
||||||
|
|
||||||
xml_iface = f"""
|
xml_iface = f"""
|
||||||
|
@ -1471,12 +1582,18 @@ class wvmInstance(wvmConnect):
|
||||||
net_source_type = network_data.get("net-source-0-type")
|
net_source_type = network_data.get("net-source-0-type")
|
||||||
net_filter = network_data.get("net-nwfilter-0")
|
net_filter = network_data.get("net-nwfilter-0")
|
||||||
net_model = network_data.get("net-model-0")
|
net_model = network_data.get("net-model-0")
|
||||||
|
|
||||||
# Remove interface first, but keep network interface XML definition
|
# Remove interface first, but keep network interface XML definition
|
||||||
# If there is an error happened while adding changed one, then add removed one to back.
|
# If there is an error happened while adding changed one, then add removed one to back.
|
||||||
status = self.delete_network(net_mac)
|
status = self.delete_network(net_mac)
|
||||||
try:
|
try:
|
||||||
self.add_network(net_mac, net_source, net_source_type, net_model, net_filter)
|
self.add_network(
|
||||||
|
net_mac,
|
||||||
|
net_source,
|
||||||
|
net_source_type,
|
||||||
|
net_model,
|
||||||
|
net_filter
|
||||||
|
)
|
||||||
except libvirtError:
|
except libvirtError:
|
||||||
if status is not None:
|
if status is not None:
|
||||||
if self.get_status() == 1:
|
if self.get_status() == 1:
|
||||||
|
@ -1485,12 +1602,11 @@ class wvmInstance(wvmConnect):
|
||||||
if self.get_status() == 5:
|
if self.get_status() == 5:
|
||||||
self.instance.attachDeviceFlags(status, VIR_DOMAIN_AFFECT_CONFIG)
|
self.instance.attachDeviceFlags(status, VIR_DOMAIN_AFFECT_CONFIG)
|
||||||
|
|
||||||
|
|
||||||
def change_network_oldway(self, network_data):
|
def change_network_oldway(self, network_data):
|
||||||
'''
|
"""
|
||||||
change network firsh version...
|
change network firsh version...
|
||||||
will be removed if new one works as expected for all scenarios
|
will be removed if new one works as expected for all scenarios
|
||||||
'''
|
"""
|
||||||
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
|
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
|
||||||
tree = ElementTree.fromstring(xml)
|
tree = ElementTree.fromstring(xml)
|
||||||
for num, interface in enumerate(tree.findall("devices/interface")):
|
for num, interface in enumerate(tree.findall("devices/interface")):
|
||||||
|
@ -1512,9 +1628,13 @@ class wvmInstance(wvmConnect):
|
||||||
elif net_source_type == "iface":
|
elif net_source_type == "iface":
|
||||||
source.set("dev", net_source)
|
source.set("dev", net_source)
|
||||||
else:
|
else:
|
||||||
raise libvirtError("Unknown network type: {}".format(net_source_type))
|
raise libvirtError(
|
||||||
|
"Unknown network type: {}".format(net_source_type)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise libvirtError("Unknown network type: {}".format(interface.get("type")))
|
raise libvirtError(
|
||||||
|
"Unknown network type: {}".format(interface.get("type"))
|
||||||
|
)
|
||||||
|
|
||||||
source = interface.find("model")
|
source = interface.find("model")
|
||||||
if net_model != "default":
|
if net_model != "default":
|
||||||
|
@ -1615,7 +1735,12 @@ class wvmInstance(wvmConnect):
|
||||||
out_peak = out_qos.get("peak")
|
out_peak = out_qos.get("peak")
|
||||||
out_burst = out_qos.get("burst")
|
out_burst = out_qos.get("burst")
|
||||||
bound_list.append(
|
bound_list.append(
|
||||||
{"direction": "outbound", "average": out_av, "peak": out_peak, "burst": out_burst}
|
{
|
||||||
|
"direction": "outbound",
|
||||||
|
"average": out_av,
|
||||||
|
"peak": out_peak,
|
||||||
|
"burst": out_burst,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
qos_values[mac[0]] = bound_list
|
qos_values[mac[0]] = bound_list
|
||||||
return qos_values
|
return qos_values
|
||||||
|
@ -1652,9 +1777,13 @@ class wvmInstance(wvmConnect):
|
||||||
|
|
||||||
def unset_qos(self, mac, direction):
|
def unset_qos(self, mac, direction):
|
||||||
tree = etree.fromstring(self._XMLDesc(0))
|
tree = etree.fromstring(self._XMLDesc(0))
|
||||||
for direct in tree.xpath("/domain/devices/interface/bandwidth/{}".format(direction)):
|
for direct in tree.xpath(
|
||||||
|
"/domain/devices/interface/bandwidth/{}".format(direction)
|
||||||
|
):
|
||||||
band_el = direct.getparent()
|
band_el = direct.getparent()
|
||||||
interface_el = band_el.getparent() # parent bandwidth,its parent is interface
|
interface_el = (
|
||||||
|
band_el.getparent()
|
||||||
|
) # parent bandwidth,its parent is interface
|
||||||
parent_mac = interface_el.xpath("mac/@address")
|
parent_mac = interface_el.xpath("mac/@address")
|
||||||
if parent_mac[0] == mac:
|
if parent_mac[0] == mac:
|
||||||
band_el.remove(direct)
|
band_el.remove(direct)
|
||||||
|
@ -1675,7 +1804,9 @@ class wvmInstance(wvmConnect):
|
||||||
|
|
||||||
def remove_guest_agent(self):
|
def remove_guest_agent(self):
|
||||||
tree = etree.fromstring(self._XMLDesc(0))
|
tree = etree.fromstring(self._XMLDesc(0))
|
||||||
for target in tree.xpath("/domain/devices/channel[@type='unix']/target[@name='org.qemu.guest_agent.0']"):
|
for target in tree.xpath(
|
||||||
|
"/domain/devices/channel[@type='unix']/target[@name='org.qemu.guest_agent.0']"
|
||||||
|
):
|
||||||
parent = target.getparent()
|
parent = target.getparent()
|
||||||
channel_xml = etree.tostring(parent).decode()
|
channel_xml = etree.tostring(parent).decode()
|
||||||
if self.get_status() == 1:
|
if self.get_status() == 1:
|
||||||
|
|
|
@ -88,7 +88,8 @@ class wvmNWFilter(wvmConnect):
|
||||||
tree = ElementTree.fromstring(self._XMLDesc(0))
|
tree = ElementTree.fromstring(self._XMLDesc(0))
|
||||||
|
|
||||||
rule_tree = tree.findall(
|
rule_tree = tree.findall(
|
||||||
"./rule[@action='%s'][@direction='%s'][@priority='%s']" % (action, direction, priority)
|
"./rule[@action='%s'][@direction='%s'][@priority='%s']"
|
||||||
|
% (action, direction, priority)
|
||||||
)
|
)
|
||||||
if rule_tree:
|
if rule_tree:
|
||||||
tree.remove(rule_tree[0])
|
tree.remove(rule_tree[0])
|
||||||
|
@ -111,7 +112,8 @@ class wvmNWFilter(wvmConnect):
|
||||||
rule_priority = rule.get("priority")
|
rule_priority = rule.get("priority")
|
||||||
rule_directives = rule.find("./")
|
rule_directives = rule.find("./")
|
||||||
rule_tree = tree.findall(
|
rule_tree = tree.findall(
|
||||||
"./rule[@action='%s'][@direction='%s'][@priority='%s']" % (rule_action, rule_direction, rule_priority)
|
"./rule[@action='%s'][@direction='%s'][@priority='%s']"
|
||||||
|
% (rule_action, rule_direction, rule_priority)
|
||||||
)
|
)
|
||||||
|
|
||||||
if rule_tree:
|
if rule_tree:
|
||||||
|
|
|
@ -15,7 +15,13 @@ class wvmStorages(wvmConnect):
|
||||||
stg_vol = len(stg.listVolumes()) if stg_status else None
|
stg_vol = len(stg.listVolumes()) if stg_status else None
|
||||||
stg_size = stg.info()[1]
|
stg_size = stg.info()[1]
|
||||||
storages.append(
|
storages.append(
|
||||||
{"name": pool, "status": stg_status, "type": stg_type, "volumes": stg_vol, "size": stg_size}
|
{
|
||||||
|
"name": pool,
|
||||||
|
"status": stg_status,
|
||||||
|
"type": stg_type,
|
||||||
|
"volumes": stg_vol,
|
||||||
|
"size": stg_size,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
return storages
|
return storages
|
||||||
|
|
||||||
|
@ -47,7 +53,9 @@ class wvmStorages(wvmConnect):
|
||||||
|
|
||||||
return stg
|
return stg
|
||||||
|
|
||||||
def create_storage_ceph(self, stg_type, name, ceph_pool, ceph_host, ceph_user, secret):
|
def create_storage_ceph(
|
||||||
|
self, stg_type, name, ceph_pool, ceph_host, ceph_user, secret
|
||||||
|
):
|
||||||
xml = f"""
|
xml = f"""
|
||||||
<pool type='{stg_type}'>
|
<pool type='{stg_type}'>
|
||||||
<name>{name}</name>
|
<name>{name}</name>
|
||||||
|
@ -65,7 +73,9 @@ class wvmStorages(wvmConnect):
|
||||||
stg.create(0)
|
stg.create(0)
|
||||||
stg.setAutostart(1)
|
stg.setAutostart(1)
|
||||||
|
|
||||||
def create_storage_netfs(self, stg_type, name, netfs_host, source, source_format, target):
|
def create_storage_netfs(
|
||||||
|
self, stg_type, name, netfs_host, source, source_format, target
|
||||||
|
):
|
||||||
xml = f"""
|
xml = f"""
|
||||||
<pool type='{stg_type}'>
|
<pool type='{stg_type}'>
|
||||||
<name>{name}</name>
|
<name>{name}</name>
|
||||||
|
@ -96,7 +106,12 @@ class wvmStorage(wvmConnect):
|
||||||
return self.pool.name()
|
return self.pool.name()
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
status = ["Not running", "Initializing pool, not available", "Running normally", "Running degraded"]
|
status = [
|
||||||
|
"Not running",
|
||||||
|
"Initializing pool, not available",
|
||||||
|
"Running normally",
|
||||||
|
"Running degraded",
|
||||||
|
]
|
||||||
try:
|
try:
|
||||||
return status[self.pool.info()[0]]
|
return status[self.pool.info()[0]]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -161,27 +176,28 @@ class wvmStorage(wvmConnect):
|
||||||
hosts_array = []
|
hosts_array = []
|
||||||
|
|
||||||
for host in doc.xpath("/pool/source/host"):
|
for host in doc.xpath("/pool/source/host"):
|
||||||
name = host.get('name')
|
name = host.get("name")
|
||||||
if name:
|
if name:
|
||||||
port = host.get('port')
|
port = host.get("port")
|
||||||
if port:
|
if port:
|
||||||
hosts_array.append({"hostname": name, "hostport": port})
|
hosts_array.append({"hostname": name, "hostport": port})
|
||||||
else:
|
else:
|
||||||
hosts_array.append({"hostname": name})
|
hosts_array.append({"hostname": name})
|
||||||
|
|
||||||
name = doc.get('name')
|
name = doc.get("name")
|
||||||
auth = doc.xpath("/pool/source/auth")
|
auth = doc.xpath("/pool/source/auth")
|
||||||
auth_type = auth[0].get("type")
|
auth_type = auth[0].get("type")
|
||||||
auth_user = auth[0].get("username")
|
auth_user = auth[0].get("username")
|
||||||
auth_uuid = auth[0].xpath("secret/@uuid")[0]
|
auth_uuid = auth[0].xpath("secret/@uuid")[0]
|
||||||
|
|
||||||
return({
|
return {
|
||||||
"name": name,
|
"name": name,
|
||||||
"auth_type": auth_type,
|
"auth_type": auth_type,
|
||||||
"auth_user": auth_user,
|
"auth_user": auth_user,
|
||||||
"auth_uuid": auth_uuid,
|
"auth_uuid": auth_uuid,
|
||||||
"hosts": hosts_array
|
"hosts": hosts_array,
|
||||||
})
|
}
|
||||||
|
|
||||||
return util.get_xml_path(self._XMLDesc(0), func=hosts)
|
return util.get_xml_path(self._XMLDesc(0), func=hosts)
|
||||||
|
|
||||||
def get_pretty_allocation(self):
|
def get_pretty_allocation(self):
|
||||||
|
@ -231,30 +247,49 @@ class wvmStorage(wvmConnect):
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
vols = self.get_volumes()
|
vols = self.get_volumes()
|
||||||
return [{"name": volname,
|
return [
|
||||||
"size": self.get_volume_size(volname),
|
{
|
||||||
"allocation": self.get_volume_allocation(volname),
|
|
||||||
"type": self.get_volume_format_type(volname)} for volname in vols]
|
|
||||||
|
|
||||||
def get_volume_details(self, volname):
|
|
||||||
with contextlib.suppress(Exception):
|
|
||||||
self.refresh()
|
|
||||||
return {
|
|
||||||
"name": volname,
|
"name": volname,
|
||||||
"size": self.get_volume_size(volname),
|
"size": self.get_volume_size(volname),
|
||||||
"allocation": self.get_volume_allocation(volname),
|
"allocation": self.get_volume_allocation(volname),
|
||||||
"type": self.get_volume_format_type(volname),
|
"type": self.get_volume_format_type(volname),
|
||||||
|
}
|
||||||
|
for volname in vols
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_volume_details(self, volname):
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
self.refresh()
|
||||||
|
return {
|
||||||
|
"name": volname,
|
||||||
|
"size": self.get_volume_size(volname),
|
||||||
|
"allocation": self.get_volume_allocation(volname),
|
||||||
|
"type": self.get_volume_format_type(volname),
|
||||||
}
|
}
|
||||||
|
|
||||||
def update_volumes(self):
|
def update_volumes(self):
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
self.refresh()
|
self.refresh()
|
||||||
vols = self.get_volumes()
|
vols = self.get_volumes()
|
||||||
return [{"name": volname, "size": self.get_volume_size(volname),
|
return [
|
||||||
"allocation": self.get_volume_allocation(volname),
|
{
|
||||||
"type": self.get_volume_format_type(volname)} for volname in vols]
|
"name": volname,
|
||||||
|
"size": self.get_volume_size(volname),
|
||||||
|
"allocation": self.get_volume_allocation(volname),
|
||||||
|
"type": self.get_volume_format_type(volname),
|
||||||
|
}
|
||||||
|
for volname in vols
|
||||||
|
]
|
||||||
|
|
||||||
def create_volume(self, name, size, vol_fmt="qcow2", metadata=False, disk_owner_uid=0, disk_owner_gid=0):
|
def create_volume(
|
||||||
|
self,
|
||||||
|
name,
|
||||||
|
size,
|
||||||
|
vol_fmt="qcow2",
|
||||||
|
metadata=False,
|
||||||
|
disk_owner_uid=0,
|
||||||
|
disk_owner_gid=0,
|
||||||
|
):
|
||||||
size = int(size) * 1073741824
|
size = int(size) * 1073741824
|
||||||
storage_type = self.get_type()
|
storage_type = self.get_type()
|
||||||
alloc = size
|
alloc = size
|
||||||
|
|
Loading…
Reference in a new issue