1
0
Fork 0
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:
catborise 2022-11-02 08:54:35 +03:00
parent ea409ca863
commit 217e106c8b
55 changed files with 2510 additions and 1454 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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"),
)

View file

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

View file

@ -2,4 +2,4 @@ from django.apps import AppConfig
class AdminConfig(AppConfig): class AdminConfig(AppConfig):
name = 'admin' name = "admin"

View file

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

View file

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

View file

@ -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"),
] ]

View file

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

View file

@ -2,4 +2,4 @@ from django.apps import AppConfig
class AppsettingsConfig(AppConfig): class AppsettingsConfig(AppConfig):
name = 'appsettings' name = "appsettings"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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",
),
]
),
),
] ]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"),
] ]

View file

@ -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()}"

View file

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

View file

@ -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",
]

View file

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

View file

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

View file

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

View file

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

View file

@ -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"),
] ]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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