1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2024-11-01 03:54:15 +00:00

Merge pull request #552 from catborise/master

format black code style
This commit is contained in:
catborise 2022-11-02 15:59:31 +03:00 committed by GitHub
commit b43d7b0a8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 2560 additions and 1474 deletions

View file

@ -3,48 +3,59 @@ from django.db.models.signals import post_migrate
def apply_change_password(sender, **kwargs):
'''
"""
Apply new change_password permission for all users
Depending on settings SHOW_PROFILE_EDIT_PASSWORD
'''
"""
from django.conf import settings
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')
print('\033[1m* \033[92mApplying permission can_change_password for all users\033[0m')
if hasattr(settings, "SHOW_PROFILE_EDIT_PASSWORD"):
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()
permission = Permission.objects.get(codename='change_password')
permission = Permission.objects.get(codename="change_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:
user.user_permissions.add(permission)
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:
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):
'''
"""
Create initial admin user
'''
"""
from accounts.models import UserAttributes
from django.contrib.auth.models import User
plan = kwargs.get('plan', [])
plan = kwargs.get("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:
print('\033[1m* \033[92mCreating default admin user\033[0m')
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()
print("\033[1m* \033[92mCreating default admin user\033[0m")
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()
break
class AccountsConfig(AppConfig):
name = 'accounts'
verbose_name = 'Accounts'
name = "accounts"
verbose_name = "Accounts"
def ready(self):
post_migrate.connect(create_admin, sender=self)

View file

@ -12,52 +12,52 @@ class UserInstanceForm(ModelForm):
super(UserInstanceForm, self).__init__(*args, **kwargs)
# 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:
self.fields['user'].disabled = True
self.fields['instance'].disabled = True
self.fields["user"].disabled = True
self.fields["instance"].disabled = True
def clean_instance(self):
instance = self.cleaned_data['instance']
if app_settings.ALLOW_INSTANCE_MULTIPLE_OWNER == 'False':
instance = self.cleaned_data["instance"]
if app_settings.ALLOW_INSTANCE_MULTIPLE_OWNER == "False":
exists = UserInstance.objects.filter(instance=instance)
if exists:
raise ValidationError(_('Instance owned by another user'))
raise ValidationError(_("Instance owned by another user"))
return instance
class Meta:
model = UserInstance
fields = '__all__'
fields = "__all__"
class ProfileForm(ModelForm):
class Meta:
model = get_user_model()
fields = ('first_name', 'last_name', 'email')
fields = ("first_name", "last_name", "email")
class UserSSHKeyForm(ModelForm):
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)
super().__init__(*args, **kwargs)
def clean_keyname(self):
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"))
return self.cleaned_data['keyname']
return self.cleaned_data["keyname"]
def clean_keypublic(self):
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"))
if not validate_ssh_key(self.cleaned_data['keypublic']):
raise ValidationError(_('Invalid key'))
return self.cleaned_data['keypublic']
if not validate_ssh_key(self.cleaned_data["keypublic"]):
raise ValidationError(_("Invalid key"))
return self.cleaned_data["keypublic"]
def save(self, commit=True):
ssh_key = super().save(commit=False)
@ -68,8 +68,8 @@ class UserSSHKeyForm(ModelForm):
class Meta:
model = UserSSHKey
fields = ('keyname', 'keypublic')
fields = ("keyname", "keypublic")
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):
def get_queryset(self):
return super().get_queryset().select_related('instance', 'user')
return super().get_queryset().select_related("instance", "user")
class UserInstance(models.Model):
@ -20,16 +20,19 @@ class UserInstance(models.Model):
objects = UserInstanceManager()
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:
unique_together = ['user', 'instance']
unique_together = ["user", "instance"]
class UserSSHKey(models.Model):
user = models.ForeignKey(User, on_delete=models.DO_NOTHING)
keyname = models.CharField(_('key name'), max_length=25)
keypublic = models.CharField(_('public key'), max_length=500)
keyname = models.CharField(_("key name"), max_length=25)
keypublic = models.CharField(_("public key"), max_length=500)
def __str__(self):
return self.keyname
@ -38,26 +41,26 @@ class UserSSHKey(models.Model):
class UserAttributes(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
can_clone_instances = models.BooleanField(default=True)
max_instances = models.IntegerField(_('max instances'),
default=2,
help_text=_("-1 for unlimited. Any integer value"),
validators=[
MinValueValidator(-1),
])
max_instances = models.IntegerField(
_("max instances"),
default=2,
help_text=_("-1 for unlimited. Any integer value"),
validators=[MinValueValidator(-1)],
)
max_cpus = models.IntegerField(
_('max CPUs'),
_("max CPUs"),
default=2,
help_text=_("-1 for unlimited. Any integer value"),
validators=[MinValueValidator(-1)],
)
max_memory = models.IntegerField(
_('max memory'),
_("max memory"),
default=2048,
help_text=_("-1 for unlimited. Any integer value"),
validators=[MinValueValidator(-1)],
)
max_disk_size = models.IntegerField(
_('max disk size'),
_("max disk size"),
default=20,
help_text=_("-1 for unlimited. Any integer value"),
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
"""
class Meta:
default_permissions = ()
permissions = (('change_password', _('Can change password')), )
permissions = (("change_password", _("Can change password")),)
managed = False

View file

@ -4,4 +4,6 @@
Scan this QR code to get OTP for account '{{ user }}'
{% endblocktrans %}
<br>
{% qr_from_text totp_url %}
{% qr_from_text totp_url %}
<p class="small">{% trans 'Some e-mail clients does not render SVG, also generating PNG.' %}</p>
{% qr_from_text totp_url size="s" image_format="png" error_correction="M" %}

View file

@ -23,15 +23,15 @@ class AccountsTestCase(TestCase):
# Add users for testing purposes
User = get_user_model()
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
cls.compute = Compute(
name='test-compute',
hostname='localhost',
login='',
password='',
details='local',
name="test-compute",
hostname="localhost",
login="",
password="",
details="local",
type=4,
)
cls.compute.save()
@ -45,17 +45,17 @@ class AccountsTestCase(TestCase):
# Add disks for testing
cls.connection.create_volume(
'default',
'test-volume',
"default",
"test-volume",
1,
'qcow2',
"qcow2",
False,
0,
0,
)
# 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()
# Create testing vm from XML
@ -71,80 +71,90 @@ class AccountsTestCase(TestCase):
super().tearDownClass()
def setUp(self):
self.client.login(username='admin', password='admin')
permission = Permission.objects.get(codename='change_password')
self.client.login(username="admin", password="admin")
permission = Permission.objects.get(codename="change_password")
self.test_user.user_permissions.add(permission)
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.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"
def test_profile(self):
response = self.client.get(reverse('accounts:profile'))
response = self.client.get(reverse("accounts:profile"))
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)
def test_account_with_otp(self):
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)
def test_login_logout(self):
client = Client()
response = client.post(reverse("accounts:login"), {"username": "test", "password": "test"})
self.assertRedirects(response, reverse('accounts:profile'))
response = client.post(
reverse("accounts:login"), {"username": "test", "password": "test"}
)
self.assertRedirects(response, reverse("accounts:profile"))
response = client.get(reverse('accounts:logout'))
self.assertRedirects(response, reverse('accounts:login'))
response = client.get(reverse("accounts:logout"))
self.assertRedirects(response, reverse("accounts:login"))
def test_change_password(self):
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)
response = self.client.post(
reverse('accounts:change_password'),
reverse("accounts:change_password"),
{
'old_password': 'wrongpass',
'new_password1': 'newpw',
'new_password2': 'newpw',
"old_password": "wrongpass",
"new_password1": "newpw",
"new_password2": "newpw",
},
)
self.assertEqual(response.status_code, 200)
response = self.client.post(
reverse('accounts:change_password'),
reverse("accounts:change_password"),
{
'old_password': 'test',
'new_password1': 'newpw',
'new_password2': 'newpw',
"old_password": "test",
"new_password1": "newpw",
"new_password2": "newpw",
},
)
self.assertRedirects(response, reverse('accounts:profile'))
self.assertRedirects(response, reverse("accounts:profile"))
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)
def test_user_instance_create_update_delete(self):
# 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)
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,
'instance': self.instance.id,
'is_change': False,
'is_delete': False,
'is_vnc': False,
"user": self.test_user.id,
"instance": self.instance.id,
"is_change": False,
"is_delete": 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)
self.assertEqual(user_instance.user, self.test_user)
@ -154,20 +164,24 @@ class AccountsTestCase(TestCase):
self.assertEqual(user_instance.is_vnc, False)
# 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)
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,
'instance': self.instance.id,
'is_change': True,
'is_delete': True,
'is_vnc': True,
"user": self.test_user.id,
"instance": self.instance.id,
"is_change": True,
"is_delete": 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)
self.assertEqual(user_instance.user, self.test_user)
@ -177,91 +191,118 @@ class AccountsTestCase(TestCase):
self.assertEqual(user_instance.is_vnc, True)
# 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)
response = self.client.post(reverse('accounts:user_instance_delete', args=[user_instance.id]))
self.assertRedirects(response, reverse('accounts:account', args=[self.test_user.id]))
response = self.client.post(
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
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(
reverse('accounts:user_instance_delete', args=[user_instance.id]) + '?next=' + reverse('index'))
self.assertRedirects(response, reverse('index'))
reverse("accounts:user_instance_delete", args=[user_instance.id])
+ "?next="
+ reverse("index")
)
self.assertRedirects(response, reverse("index"))
def test_update_user_profile(self):
self.client.force_login(self.test_user)
user = get_user_model().objects.get(username='test')
self.assertEqual(user.first_name, '')
self.assertEqual(user.last_name, '')
self.assertEqual(user.email, '')
user = get_user_model().objects.get(username="test")
self.assertEqual(user.first_name, "")
self.assertEqual(user.last_name, "")
self.assertEqual(user.email, "")
response = self.client.post(reverse('accounts:profile'), {
'first_name': 'first name',
'last_name': 'last name',
'email': 'email@mail.mail',
})
self.assertRedirects(response, reverse('accounts:profile'))
response = self.client.post(
reverse("accounts:profile"),
{
"first_name": "first name",
"last_name": "last name",
"email": "email@mail.mail",
},
)
self.assertRedirects(response, reverse("accounts:profile"))
user = get_user_model().objects.get(username='test')
self.assertEqual(user.first_name, 'first name')
self.assertEqual(user.last_name, 'last name')
self.assertEqual(user.email, 'email@mail.mail')
user = get_user_model().objects.get(username="test")
self.assertEqual(user.first_name, "first name")
self.assertEqual(user.last_name, "last name")
self.assertEqual(user.email, "email@mail.mail")
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)
response = self.client.post(reverse('accounts:ssh_key_create'), {
'keyname': 'keyname',
'keypublic': self.rsa_key,
})
self.assertRedirects(response, reverse('accounts:profile'))
response = self.client.post(
reverse("accounts:ssh_key_create"),
{
"keyname": "keyname",
"keypublic": self.rsa_key,
},
)
self.assertRedirects(response, reverse("accounts:profile"))
key = UserSSHKey.objects.get(pk=1)
self.assertEqual(key.keyname, 'keyname')
self.assertEqual(key.keyname, "keyname")
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)
response = self.client.post(reverse('accounts:ssh_key_delete', args=[1]))
self.assertRedirects(response, reverse('accounts:profile'))
response = self.client.post(reverse("accounts:ssh_key_delete", args=[1]))
self.assertRedirects(response, reverse("accounts:profile"))
def test_validate_ssh_key(self):
self.assertFalse(validate_ssh_key(''))
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 AAA test@test'))
self.assertFalse(validate_ssh_key(""))
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 AAA test@test"))
# validate ecdsa key
self.assertTrue(validate_ssh_key(self.ecdsa_key))
def test_forms(self):
# 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 = 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())
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())
app_settings.ALLOW_INSTANCE_MULTIPLE_OWNER = 'False'
form = UserInstanceForm({
'user': self.admin_user.id,
'instance': self.instance.id,
'is_change': False,
'is_delete': False,
'is_vnc': False,
})
app_settings.ALLOW_INSTANCE_MULTIPLE_OWNER = "False"
form = UserInstanceForm(
{
"user": self.admin_user.id,
"instance": self.instance.id,
"is_change": False,
"is_delete": False,
"is_vnc": False,
}
)
form.save()
form = UserInstanceForm({
'user': self.test_user.id,
'instance': self.instance.id,
'is_change': False,
'is_delete': False,
'is_vnc': False,
})
form = UserInstanceForm(
{
"user": self.test_user.id,
"instance": self.instance.id,
"is_change": False,
"is_delete": False,
"is_vnc": False,
}
)
self.assertFalse(form.is_valid())

View file

@ -5,29 +5,50 @@ from django_otp.forms import OTPAuthenticationForm
from . import views
app_name = 'accounts'
app_name = "accounts"
urlpatterns = [
path('logout/', LogoutView.as_view(template_name='logout.html'), name='logout'),
path('profile/', views.profile, name='profile'),
path('profile/<int:user_id>/', views.account, name='account'),
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('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'),
path("logout/", LogoutView.as_view(template_name="logout.html"), name="logout"),
path("profile/", views.profile, name="profile"),
path("profile/<int:user_id>/", views.account, name="account"),
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(
"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:
urlpatterns += [
path(
'login/',
LoginView.as_view(template_name='accounts/otp_login.html', authentication_form=OTPAuthenticationForm),
name='login',
"login/",
LoginView.as_view(
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:
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)
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 render(
@ -191,7 +194,13 @@ def admin_email_otp(request, user_id):
device = get_user_totp_device(user)
if user.email != "":
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:
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)

View file

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

View file

@ -7,7 +7,7 @@ class Permission(P):
"""
def __str__(self):
return f'{self.content_type.app_label}: {self.name}'
return f"{self.content_type.app_label}: {self.name}"
class Meta:
proxy = True

View file

@ -8,62 +8,66 @@ from accounts.models import UserAttributes
class AdminTestCase(TestCase):
def setUp(self):
self.client.login(username='admin', password='admin')
self.client.login(username="admin", password="admin")
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)
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)
response = self.client.post(reverse('admin:group_create'), {'name': 'Test Group'})
self.assertRedirects(response, reverse('admin:group_list'))
response = self.client.post(
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)
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)
response = self.client.post(reverse('admin:group_update', args=[1]), {'name': 'Updated Group Test'})
self.assertRedirects(response, reverse('admin:group_list'))
response = self.client.post(
reverse("admin:group_update", args=[1]), {"name": "Updated Group Test"}
)
self.assertRedirects(response, reverse("admin:group_list"))
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)
response = self.client.post(reverse('admin:group_delete', args=[1]))
self.assertRedirects(response, reverse('admin:group_list'))
response = self.client.post(reverse("admin:group_delete", args=[1]))
self.assertRedirects(response, reverse("admin:group_list"))
with self.assertRaises(ObjectDoesNotExist):
Group.objects.get(id=1)
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)
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)
response = self.client.post(
reverse('admin:user_create'),
reverse("admin:user_create"),
{
'username': 'test',
'password': 'test',
'max_instances': 1,
'max_cpus': 1,
'max_memory': 1024,
'max_disk_size': 4,
"username": "test",
"password": "test",
"max_instances": 1,
"max_cpus": 1,
"max_memory": 1024,
"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)
ua: UserAttributes = UserAttributes.objects.get(id=2)
@ -73,23 +77,23 @@ class AdminTestCase(TestCase):
self.assertEqual(ua.max_memory, 1024)
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)
response = self.client.post(
reverse('admin:user_update', args=[2]),
reverse("admin:user_update", args=[2]),
{
'username': 'utest',
'max_instances': 2,
'max_cpus': 2,
'max_memory': 2048,
'max_disk_size': 8,
"username": "utest",
"max_instances": 2,
"max_cpus": 2,
"max_memory": 2048,
"max_disk_size": 8,
},
)
self.assertRedirects(response, reverse('admin:user_list'))
self.assertRedirects(response, reverse("admin:user_list"))
user = User.objects.get(id=2)
self.assertEqual(user.username, 'utest')
self.assertEqual(user.username, "utest")
ua: UserAttributes = UserAttributes.objects.get(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_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)
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)
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)
response = self.client.post(reverse('admin:user_delete', args=[2]))
self.assertRedirects(response, reverse('admin:user_list'))
response = self.client.post(reverse("admin:user_delete", args=[2]))
self.assertRedirects(response, reverse("admin:user_list"))
with self.assertRaises(ObjectDoesNotExist):
User.objects.get(id=2)
def test_logs(self):
response = self.client.get(reverse('admin:logs'))
response = self.client.get(reverse("admin:logs"))
self.assertEqual(response.status_code, 200)

View file

@ -3,16 +3,16 @@ from django.urls import path
from . import views
urlpatterns = [
path('groups/', views.group_list, name='group_list'),
path('groups/create/', views.group_create, name='group_create'),
path('groups/<int:pk>/update/', views.group_update, name='group_update'),
path('groups/<int:pk>/delete/', views.group_delete, name='group_delete'),
path('users/', views.user_list, name='user_list'),
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/', views.user_update, name='user_update'),
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>/unblock/', views.user_unblock, name='user_unblock'),
path('logs/', views.logs, name='logs'),
path("groups/", views.group_list, name="group_list"),
path("groups/create/", views.group_create, name="group_create"),
path("groups/<int:pk>/update/", views.group_update, name="group_update"),
path("groups/<int:pk>/delete/", views.group_delete, name="group_delete"),
path("users/", views.user_list, name="user_list"),
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/", views.user_update, name="user_update"),
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>/unblock/", views.user_unblock, name="user_unblock"),
path("logs/", views.logs, name="logs"),
]

View file

@ -107,7 +107,11 @@ def user_create(request):
return render(
request,
"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)
attributes = UserAttributes.objects.get(user=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():
user_form.save()
attributes_form.save()
@ -126,7 +132,11 @@ def user_update(request, pk):
return render(
request,
"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():
user = form.save()
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")
else:
messages.error(request, _("Wrong Data Provided"))

View file

@ -2,4 +2,4 @@ from django.apps import 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):
def choices_as_list(self):
return self.choices.split(',')
return self.choices.split(",")
name = models.CharField(_('name'), max_length=25, null=False)
key = models.CharField(_('key'), db_index=True, max_length=50, unique=True)
value = models.CharField(_('value'), max_length=25)
choices = models.CharField(_('choices'), max_length=70)
description = models.CharField(_('description'), max_length=100, null=True)
name = models.CharField(_("name"), max_length=25, null=False)
key = models.CharField(_("key"), db_index=True, max_length=50, unique=True)
value = models.CharField(_("value"), max_length=25)
choices = models.CharField(_("choices"), max_length=70)
description = models.CharField(_("description"), max_length=100, null=True)

View file

@ -27,7 +27,9 @@ def appsettings(request):
addlogmsg(request.user.username, "-", "", err)
# 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 "SASS_DIR" in request.POST:
@ -35,7 +37,9 @@ def appsettings(request):
sass_dir.value = request.POST.get("SASS_DIR", "")
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)
except Exception as err:
msg = err
@ -47,15 +51,17 @@ def appsettings(request):
if "BOOTSTRAP_THEME" in request.POST:
theme = request.POST.get("BOOTSTRAP_THEME", "")
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_bootswatch = f"@import '{sass_dir.value}/wvc-theme/{theme}/bootswatch';"
scss_bootswatch = (
f"@import '{sass_dir.value}/wvc-theme/{theme}/bootswatch';"
)
try:
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(
string=scss_var + "\n" + scss_boot + "\n" + scss_bootswatch,
@ -82,7 +88,10 @@ def appsettings(request):
setting.value = request.POST.get(setting.key, "")
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)
except Exception as err:
msg = err

View file

@ -1,4 +1,3 @@
from rest_framework import serializers
from computes.models import Compute
from vrtManager.connection import (
@ -10,18 +9,17 @@ from vrtManager.connection import (
class ComputeSerializer(serializers.ModelSerializer):
# Use <input type="password"> for the input.
password = serializers.CharField(style={'input_type': 'password'})
# Use <input type="password"> for the input.
password = serializers.CharField(style={"input_type": "password"})
# Use a radio input instead of a select input.
conn_types = (
(CONN_SSH, 'SSH'),
(CONN_TCP, 'TCP'),
(CONN_TLS, 'TLS'),
(CONN_SOCKET, 'SOCK'),
(CONN_SSH, "SSH"),
(CONN_TCP, "TCP"),
(CONN_TLS, "TLS"),
(CONN_SOCKET, "SOCK"),
)
type = serializers.ChoiceField(choices=conn_types)
class Meta:
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.
"""
queryset = Compute.objects.all().order_by('name')
queryset = Compute.objects.all().order_by("name")
serializer_class = ComputeSerializer
permission_classes = [permissions.IsAuthenticated]
class ComputeArchitecturesView(viewsets.ViewSet):
def list(self, request, compute_pk=None):
"""
Return a list of supported host architectures.
@ -43,7 +43,6 @@ class ComputeArchitecturesView(viewsets.ViewSet):
class ComputeMachinesView(viewsets.ViewSet):
def list(self, request, compute_pk=None, archs_pk=None):
"""
Return a list of supported host architectures.

View file

@ -13,8 +13,8 @@ class TcpComputeForm(forms.ModelForm):
class Meta:
model = Compute
widgets = {'password': forms.PasswordInput()}
fields = '__all__'
widgets = {"password": forms.PasswordInput()}
fields = "__all__"
class SshComputeForm(forms.ModelForm):
@ -23,7 +23,7 @@ class SshComputeForm(forms.ModelForm):
class Meta:
model = Compute
exclude = ['password']
exclude = ["password"]
class TlsComputeForm(forms.ModelForm):
@ -32,14 +32,14 @@ class TlsComputeForm(forms.ModelForm):
class Meta:
model = Compute
widgets = {'password': forms.PasswordInput()}
fields = '__all__'
widgets = {"password": forms.PasswordInput()}
fields = "__all__"
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)
class Meta:
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):
name = CharField(_('name'), max_length=64, unique=True)
hostname = CharField(_('hostname'), max_length=64)
login = CharField(_('login'), max_length=20)
password = CharField(_('password'), max_length=14, blank=True, null=True)
details = CharField(_('details'), max_length=64, null=True, blank=True)
name = CharField(_("name"), max_length=64, unique=True)
hostname = CharField(_("hostname"), max_length=64)
login = CharField(_("login"), max_length=20)
password = CharField(_("password"), max_length=14, blank=True, null=True)
details = CharField(_("details"), max_length=64, null=True, blank=True)
type = IntegerField()
@cached_property
@ -55,7 +55,7 @@ class Compute(Model):
@cached_property
def ram_usage(self):
return self.proxy.get_memory_usage()['percent']
return self.proxy.get_memory_usage()["percent"]
def __str__(self):
return self.name

View file

@ -7,115 +7,121 @@ from .models import Compute
class ComputesTestCase(TestCase):
def setUp(self):
self.client.login(username='admin', password='admin')
self.client.login(username="admin", password="admin")
Compute(
name='local',
hostname='localhost',
login='',
password='',
details='local',
name="local",
hostname="localhost",
login="",
password="",
details="local",
type=4,
).save()
def test_index(self):
response = self.client.get(reverse('computes'))
response = self.client.get(reverse("computes"))
self.assertEqual(response.status_code, 200)
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)
response = self.client.post(
reverse('add_socket_host'),
reverse("add_socket_host"),
{
'name': 'l1',
'details': 'Created',
'hostname': 'localhost',
'type': 4,
"name": "l1",
"details": "Created",
"hostname": "localhost",
"type": 4,
},
)
self.assertRedirects(response, reverse('computes'))
self.assertRedirects(response, reverse("computes"))
compute = Compute.objects.get(pk=2)
self.assertEqual(compute.name, 'l1')
self.assertEqual(compute.details, 'Created')
self.assertEqual(compute.name, "l1")
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)
response = self.client.post(
reverse('compute_update', args=[2]),
reverse("compute_update", args=[2]),
{
'name': 'l2',
'details': 'Updated',
'hostname': 'localhost',
'type': 4,
"name": "l2",
"details": "Updated",
"hostname": "localhost",
"type": 4,
},
)
self.assertRedirects(response, reverse('computes'))
self.assertRedirects(response, reverse("computes"))
compute = Compute.objects.get(pk=2)
self.assertEqual(compute.name, 'l2')
self.assertEqual(compute.details, 'Updated')
self.assertEqual(compute.name, "l2")
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)
response = self.client.post(reverse('compute_delete', args=[2]))
self.assertRedirects(response, reverse('computes'))
response = self.client.post(reverse("compute_delete", args=[2]))
self.assertRedirects(response, reverse("computes"))
with self.assertRaises(ObjectDoesNotExist):
Compute.objects.get(id=2)
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)
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)
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)
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)
def test_storage(self):
pass
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)
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)
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)
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)
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)
# TODO: add test for single interface
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)
# TODO: add test for single nwfilter
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)
# def test_create_instance_select_type(self):
@ -125,19 +131,29 @@ class ComputesTestCase(TestCase):
# TODO: create_instance
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)
def test_compute_disk_buses(self):
response = self.client.get(
reverse('buses', kwargs={
'compute_id': 1,
'arch': 'x86_64',
'machine': 'pc',
'disk': 'disk',
}))
reverse(
"buses",
kwargs={
"compute_id": 1,
"arch": "x86_64",
"machine": "pc",
"disk": "disk",
},
)
)
self.assertEqual(response.status_code, 200)
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)

View file

@ -9,36 +9,71 @@ from storages.views import create_volume, get_volumes, storage, storages
from . import forms, views
urlpatterns = [
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("", views.computes, name="computes"),
path(
'<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'),
])),
"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(
"<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(uuid__in=domain_uuids).delete()
# 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:
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.utils.translation import gettext_lazy as _
have_symbol = re.compile('[^a-zA-Z0-9._-]+')
wrong_ip = re.compile('^0.|^255.')
wrong_name = re.compile('[^a-zA-Z0-9._-]+')
have_symbol = re.compile("[^a-zA-Z0-9._-]+")
wrong_ip = re.compile("^0.|^255.")
wrong_name = re.compile("[^a-zA-Z0-9._-]+")
def validate_hostname(value):
@ -13,12 +13,14 @@ def validate_hostname(value):
wip = wrong_ip.match(value)
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:
raise ValidationError(_('Wrong IP address'))
raise ValidationError(_("Wrong IP address"))
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:
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 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 instances.models import Instance
from vrtManager.connection import (
@ -39,7 +44,8 @@ def computes(request):
def overview(request, compute_id):
compute = get_object_or_404(Compute, pk=compute_id)
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(
@ -48,7 +54,14 @@ def overview(request, compute_id):
compute.password,
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()
mem_usage = conn.get_memory_usage()
emulator = conn.get_emulator(host_arch)
@ -64,9 +77,15 @@ def instances(request, compute_id):
compute = get_object_or_404(Compute, pk=compute_id)
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

View file

@ -1,12 +1,12 @@
Django==3.2.15
Django==3.2.16
django_bootstrap5==22.1
django-icons==22.1
django-login-required-middleware==0.8
django-login-required-middleware==0.9.0
django-otp==1.1.3
django-qr-code==2.3.0
django-qr-code==3.1.1
gunicorn==20.1.0
libsass==0.21.0
libvirt-python==8.6.0
libvirt-python==8.8.0
lxml==4.9.1
qrcode==7.3.1
rwlock==0.0.7
@ -16,7 +16,7 @@ ldap3==2.9.1
python-engineio==4.3.4
python-socketio==5.7.1
eventlet==0.33.1
djangorestframework==3.13.1
djangorestframework==3.14.0
drf-nested-routers==0.93.4
drf-yasg==1.21.3
drf-yasg==1.21.4
markdown==3.4.1

View file

@ -6,9 +6,9 @@ import logging
import django
DIR_PATH = os.path.dirname(os.path.abspath(__file__))
ROOT_PATH = os.path.abspath(os.path.join(DIR_PATH, '..', ''))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webvirtcloud.settings')
CERT = DIR_PATH + '/cert.pem'
ROOT_PATH = os.path.abspath(os.path.join(DIR_PATH, "..", ""))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webvirtcloud.settings")
CERT = DIR_PATH + "/cert.pem"
if ROOT_PATH not in sys.path:
sys.path.append(ROOT_PATH)
@ -17,7 +17,8 @@ django.setup()
import re
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 webvirtcloud.settings import WS_PORT, WS_HOST, WS_CERT
from vrtManager.connection import CONN_SSH, CONN_SOCKET
@ -26,40 +27,40 @@ from optparse import OptionParser
parser = OptionParser()
parser.add_option("-v",
"--verbose",
dest="verbose",
action="store_true",
help="Verbose mode",
default=False)
parser.add_option(
"-v",
"--verbose",
dest="verbose",
action="store_true",
help="Verbose mode",
default=False,
)
parser.add_option("-d",
"--debug",
dest="debug",
action="store_true",
help="Debug mode",
default=False)
parser.add_option(
"-d", "--debug", dest="debug", action="store_true", help="Debug mode", default=False
)
parser.add_option("-H",
"--host",
dest="host",
action="store",
help="Listen host",
default=WS_HOST)
parser.add_option(
"-H", "--host", dest="host", action="store", help="Listen host", default=WS_HOST
)
parser.add_option("-p",
"--port",
dest="port",
action="store",
help="Listen port",
default=WS_PORT or 6080)
parser.add_option(
"-p",
"--port",
dest="port",
action="store",
help="Listen port",
default=WS_PORT or 6080,
)
parser.add_option("-c",
"--cert",
dest="cert",
action="store",
help="Certificate file path",
default=WS_CERT or CERT)
parser.add_option(
"-c",
"--cert",
dest="cert",
action="store",
help="Certificate file path",
default=WS_CERT or CERT,
)
(options, args) = parser.parse_args()
@ -74,6 +75,7 @@ else:
try:
from websockify import WebSocketProxy
try:
from websockify import ProxyRequestHandler
except ImportError:
@ -84,7 +86,7 @@ except ImportError:
try:
from novnc.wsproxy import WebSocketProxy
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)
else:
USE_HANDLER = False
@ -95,18 +97,20 @@ def get_connection_infos(token):
from vrtManager.instance import wvmInstance
try:
temptoken = token.split('-', 1)
temptoken = token.split("-", 1)
host = int(temptoken[0])
uuid = temptoken[1]
instance = Instance.objects.get(compute_id=host, uuid=uuid)
conn = wvmInstance(instance.compute.hostname,
instance.compute.login,
instance.compute.password,
instance.compute.type,
instance.name)
if instance.compute.hostname.count(':'):
connhost = instance.compute.hostname.split(':')[0]
connport = instance.compute.hostname.split(':')[1]
conn = wvmInstance(
instance.compute.hostname,
instance.compute.login,
instance.compute.password,
instance.compute.type,
instance.name,
)
if instance.compute.hostname.count(":"):
connhost = instance.compute.hostname.split(":")[0]
connport = instance.compute.hostname.split(":")[1]
else:
connhost = instance.compute.hostname
connport = 22
@ -117,10 +121,18 @@ def get_connection_infos(token):
console_socket = conn.get_console_socket()
except Exception as e:
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
return (connhost, connport, connuser, conntype, console_host,
console_port, console_socket)
return (
connhost,
connport,
connuser,
conntype,
console_host,
console_port,
console_socket,
)
class CompatibilityMixIn(object):
@ -128,25 +140,31 @@ class CompatibilityMixIn(object):
# NoVNC uses it's own convention that forward token
# from the request to a cookie header, we should check
# also for this behavior
hcookie = self.headers.get('cookie')
hcookie = self.headers.get("cookie")
if hcookie:
cookie = Cookie.SimpleCookie()
for hcookie_part in hcookie.split(';'):
for hcookie_part in hcookie.split(";"):
hcookie_part = hcookie_part.lstrip()
try:
cookie.load(hcookie_part)
except Cookie.CookieError:
# NOTE(stgleb): Do not print out cookie content
# for security reasons.
self.msg('Found malformed cookie')
self.msg("Found malformed cookie")
else:
if 'token' in cookie:
token = cookie['token'].value
if "token" in cookie:
token = cookie["token"].value
(connhost, connport, connuser, conntype, console_host, console_port,
console_socket) = get_connection_infos(token)
(
connhost,
connport,
connuser,
conntype,
console_host,
console_port,
console_socket,
) = get_connection_infos(token)
cnx_debug_msg = "Connection infos :\n"
cnx_debug_msg += "- connhost : '%s'\n" % connhost
@ -160,14 +178,16 @@ class CompatibilityMixIn(object):
if console_socket and conntype == CONN_SOCKET:
# 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.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
if conntype != CONN_SSH:
self.msg("Need a tunnel to access console but can't mount " +
"one because it's not a SSH host")
self.msg(
"Need a tunnel to access console but can't mount "
+ "one because it's not a SSH host"
)
raise Exception(self.msg)
try:
# generate a string with all placeholders to avoid TypeErrors
@ -175,10 +195,25 @@ class CompatibilityMixIn(object):
# https://github.com/retspen/webvirtmgr/pull/497
error_msg = "Try to open tunnel on %s@%s:%s on console %s:%s "
error_msg += "(or socket %s)"
self.msg(error_msg % (connuser, connhost, connport,
console_host, console_port, console_socket))
tunnel = SSHTunnels(connhost, connuser, connport,
console_host, console_port, console_socket)
self.msg(
error_msg
% (
connuser,
connhost,
connport,
console_host,
console_port,
console_socket,
)
)
tunnel = SSHTunnels(
connhost,
connuser,
connport,
console_host,
console_port,
console_socket,
)
fd = tunnel.open_new()
tunnel.unlock()
tsock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
@ -202,15 +237,18 @@ class CompatibilityMixIn(object):
except Exception:
if tunnel:
self.vmsg(
"%s:%s (via %s@%s:%s) : Websocket client or Target closed" %
(console_host, console_port, connuser, connhost, connport))
"%s:%s (via %s@%s:%s) : Websocket client or Target closed"
% (console_host, console_port, connuser, connhost, connport)
)
if tsock:
tsock.shutdown(socket.SHUT_RDWR)
tsock.close()
tunnel.close_all()
raise
if USE_HANDLER:
class NovaProxyRequestHandler(ProxyRequestHandler, CompatibilityMixIn):
def msg(self, *args, **kwargs):
self.log_message(*args, **kwargs)
@ -228,9 +266,10 @@ if USE_HANDLER:
socket_factory = self.server.socket
self._new_client(daemon, socket_factory)
else:
class NovaWebSocketProxy(WebSocketProxy, CompatibilityMixIn):
else:
class NovaWebSocketProxy(WebSocketProxy, CompatibilityMixIn):
def new_client(self):
"""
Called after a new WebSocket connection has been established.
@ -241,39 +280,44 @@ else:
self._new_client(daemon, socket_factory)
if __name__ == '__main__':
if __name__ == "__main__":
if USE_HANDLER:
# Create the WebSocketProxy with NovaProxyRequestHandler handler
server = WebSocketProxy(RequestHandlerClass=NovaProxyRequestHandler,
listen_host=options.host,
listen_port=options.port,
source_is_ipv6=False,
verbose=options.verbose,
cert=options.cert,
key=None,
ssl_only=False,
daemon=False,
record=False,
web=False,
traffic=False,
target_host='ignore',
target_port='ignore',
wrap_mode='exit',
wrap_cmd=None)
server = WebSocketProxy(
RequestHandlerClass=NovaProxyRequestHandler,
listen_host=options.host,
listen_port=options.port,
source_is_ipv6=False,
verbose=options.verbose,
cert=options.cert,
key=None,
ssl_only=False,
daemon=False,
record=False,
web=False,
traffic=False,
target_host="ignore",
target_port="ignore",
wrap_mode="exit",
wrap_cmd=None,
)
else:
# Create the NovaWebSockets proxy
server = NovaWebSocketProxy(listen_host=options.host,
listen_port=options.port,
source_is_ipv6=False,
verbose=options.verbose,
cert=options.cert,
key=None,
ssl_only=False,
daemon=False,
record=False,
web=False,
target_host='ignore',
target_port='ignore',
wrap_mode='exit',
wrap_cmd=None)
server = NovaWebSocketProxy(
listen_host=options.host,
listen_port=options.port,
source_is_ipv6=False,
verbose=options.verbose,
cert=options.cert,
key=None,
ssl_only=False,
daemon=False,
record=False,
web=False,
target_host="ignore",
target_port="ignore",
wrap_mode="exit",
wrap_cmd=None,
)
server.start_server()

View file

@ -5,9 +5,9 @@ import logging
import django
DIR_PATH = os.path.dirname(os.path.abspath(__file__))
ROOT_PATH = os.path.abspath(os.path.join(DIR_PATH, '..', ''))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webvirtcloud.settings')
CERT = DIR_PATH + '/cert.pem'
ROOT_PATH = os.path.abspath(os.path.join(DIR_PATH, "..", ""))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webvirtcloud.settings")
CERT = DIR_PATH + "/cert.pem"
if ROOT_PATH not in sys.path:
sys.path.append(ROOT_PATH)
@ -32,7 +32,7 @@ import tty
import termios
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 webvirtcloud.settings import SOCKETIO_PORT, SOCKETIO_HOST
from vrtManager.connection import CONN_SSH, CONN_SOCKET
@ -40,33 +40,36 @@ from optparse import OptionParser
parser = OptionParser()
parser.add_option("-v",
"--verbose",
dest="verbose",
action="store_true",
help="Verbose mode",
default=False)
parser.add_option(
"-v",
"--verbose",
dest="verbose",
action="store_true",
help="Verbose mode",
default=False,
)
parser.add_option("-d",
"--debug",
dest="debug",
action="store_true",
help="Debug mode",
default=False)
parser.add_option(
"-d", "--debug", dest="debug", action="store_true", help="Debug mode", default=False
)
parser.add_option("-H",
"--host",
dest="host",
action="store",
help="Listen host",
default=SOCKETIO_HOST)
parser.add_option(
"-H",
"--host",
dest="host",
action="store",
help="Listen host",
default=SOCKETIO_HOST,
)
parser.add_option("-p",
"--port",
dest="port",
action="store",
help="Listen port",
default=SOCKETIO_PORT or 6081)
parser.add_option(
"-p",
"--port",
dest="port",
action="store",
help="Listen port",
default=SOCKETIO_PORT or 6081,
)
(options, args) = parser.parse_args()
@ -85,26 +88,31 @@ sio = socketio.Server(async_mode=async_mode, cors_allowed_origins=[])
fd = None
child_pid = None
def get_connection_infos(token):
from instances.models import Instance
from vrtManager.instance import wvmInstance
try:
temptoken = token.split('-', 1)
temptoken = token.split("-", 1)
host = int(temptoken[0])
uuid = temptoken[1]
instance = Instance.objects.get(compute_id=host, uuid=uuid)
conn = wvmInstance(instance.compute.hostname,
instance.compute.login,
instance.compute.password,
instance.compute.type,
instance.name)
conn = wvmInstance(
instance.compute.hostname,
instance.compute.login,
instance.compute.password,
instance.compute.type,
instance.name,
)
except Exception as e:
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
return (instance, conn)
def set_winsize(fd, row, col, xpix=0, ypix=0):
winsize = struct.pack("HHHH", row, col, xpix, ypix)
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
@ -123,41 +131,44 @@ def read_and_forward_pty_output():
sio.emit("pty_output", {"output": output})
else:
return
@sio.event
def resize(sid, message):
def resize(sid, message):
global fd
if fd:
set_winsize(fd, message["rows"], message["cols"])
@sio.event
def pty_input(sid, message):
global fd
if fd:
os.write(fd, message["input"].encode())
@sio.event
def disconnect_request(sid):
sio.disconnect(sid)
@sio.event
def connect(sid, environ):
global fd
global child_pid
hcookie = environ.get('HTTP_COOKIE')
hcookie = environ.get("HTTP_COOKIE")
if hcookie:
cookie = Cookie.SimpleCookie()
for hcookie_part in hcookie.split(';'):
for hcookie_part in hcookie.split(";"):
hcookie_part = hcookie_part.lstrip()
try:
cookie.load(hcookie_part)
except Cookie.CookieError:
logging.warn('Found malformed cookie')
logging.warn("Found malformed cookie")
else:
if 'token' in cookie:
token = cookie['token'].value
if "token" in cookie:
token = cookie["token"].value
if child_pid:
# already started child process, don't start another
@ -170,14 +181,15 @@ def connect(sid, environ):
if child_pid == 0:
(instance, conn) = get_connection_infos(token)
uuid = conn.get_uuid()
uuid = conn.get_uuid()
uri = conn.wvm.getURI()
subprocess.run(['conf/daemon/consolecallback', uri, uuid])
subprocess.run(["conf/daemon/consolecallback", uri, uuid])
else:
# this is the parent process fork.
sio.start_background_task(target=read_and_forward_pty_output)
@sio.event
def disconnect(sid):
@ -185,13 +197,15 @@ def disconnect(sid):
global child_pid
# kill pty process
os.kill(child_pid,signal.SIGKILL)
os.kill(child_pid, signal.SIGKILL)
os.wait()
# reset the variables
fd = None
child_pid = None
app = socketio.WSGIApp(sio)
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):
while True:
lock_cb, cb, args, = self._queue.get()
(
lock_cb,
cb,
args,
) = self._queue.get()
lock_cb()
cb(*args)
def schedule(self, lock_cb, cb, *args):
if not self._thread:
self._thread = threading.Thread(name="Tunnel thread",
target=self._handle_queue,
args=())
self._thread = threading.Thread(
name="Tunnel thread", target=self._handle_queue, args=()
)
self._thread.daemon = True
if not self._thread.is_alive():
self._thread.start()
@ -63,8 +67,11 @@ class _Tunnel(object):
return
self._closed = True
log.debug("Close tunnel PID=%s ERRFD=%s",
self._pid, self._errfd and self._errfd.fileno() or None)
log.debug(
"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
# when it's garbage collected.
@ -110,8 +117,7 @@ class _Tunnel(object):
self._errfd = errfds[0]
self._errfd.setblocking(0)
log.debug("Opened tunnel PID=%d ERRFD=%d",
pid, self._errfd.fileno())
log.debug("Opened tunnel PID=%d ERRFD=%d", pid, self._errfd.fileno())
self._pid = pid
@ -124,7 +130,7 @@ def _make_ssh_command(connhost, connuser, connport, gaddr, gport, gsocket):
argv += ["-p", str(connport)]
if connuser:
argv += ['-l', connuser]
argv += ["-l", connuser]
argv += [connhost]
@ -151,8 +157,8 @@ def _make_ssh_command(connhost, connuser, connport, gaddr, gport, gsocket):
"""else"""
""" CMD="nc %(nc_params)s";"""
"""fi;"""
"""eval "$CMD";""" %
{'nc_params': nc_params})
"""eval "$CMD";""" % {"nc_params": nc_params}
)
argv.append("sh -c")
argv.append("'%s'" % nc_cmd)
@ -166,7 +172,8 @@ class SSHTunnels(object):
def __init__(self, connhost, connuser, connport, gaddr, gport, gsocket):
self._tunnels = []
self._sshcommand = _make_ssh_command(
connhost, connuser, connport, gaddr, gport, gsocket)
connhost, connuser, connport, gaddr, gport, gsocket
)
self._locked = False
def open_new(self):

View file

@ -17,7 +17,7 @@ from webvirtcloud.settings import (
WS_PUBLIC_PORT,
SOCKETIO_PUBLIC_HOST,
SOCKETIO_PUBLIC_PORT,
SOCKETIO_PUBLIC_PATH
SOCKETIO_PUBLIC_PATH,
)
@ -31,32 +31,39 @@ def console(request):
if request.method == "GET":
token = request.GET.get("token", "")
view_type = request.GET.get("view", "lite")
view_only = request.GET.get(
"view_only", app_settings.CONSOLE_VIEW_ONLY.lower())
view_only = request.GET.get("view_only", app_settings.CONSOLE_VIEW_ONLY.lower())
scale = request.GET.get("scale", app_settings.CONSOLE_SCALE.lower())
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", app_settings.CONSOLE_CLIP_VIEWPORT.lower())
"clip_viewport", app_settings.CONSOLE_CLIP_VIEWPORT.lower()
)
try:
temptoken = token.split("-", 1)
host = int(temptoken[0])
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:
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)
except UserInstance.DoesNotExist:
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)
else:
instance = Instance.objects.get(compute_id=host, uuid=uuid)
conn = wvmInstance(
instance.compute.hostname,
instance.compute.login,
@ -83,7 +90,9 @@ def console(request):
console_page = "console-" + console_type + "-" + view_type + ".html"
response = render(request, console_page, locals())
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_path = SOCKETIO_PUBLIC_PATH if SOCKETIO_PUBLIC_PATH else "/"
@ -93,9 +102,13 @@ def console(request):
response = render(request, "console-xterm.html", locals())
else:
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:
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.set_cookie("token", token)

View file

@ -3,8 +3,14 @@ from django.urls import path
from . import views
urlpatterns = [
path('openstack/', views.os_index, name='ds_openstack_index'),
path('openstack/<version>/meta_data.json', 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'),
path("openstack/", views.os_index, name="ds_openstack_index"),
path(
"openstack/<version>/meta_data.json",
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)
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)
url = f"{conn.get_console_type()}://{fqdn}:{conn.get_console_port()}"

View file

@ -1,7 +1,8 @@
-r ../conf/requirements.txt
coverage==6.4.4
django-debug-toolbar==3.6.0
coverage==6.5.0
django-debug-toolbar==3.7.0
pycodestyle==2.9.1
pyflakes==2.5.0
pylint==2.14.5
pylint==2.15.3
yapf==0.32.0
black

View file

@ -4,90 +4,88 @@ from instances.models import Flavor, Instance, MigrateInstance, CreateInstance
class InstanceSerializer(serializers.ModelSerializer):
class Meta:
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 Meta:
model = Instance
fields = [
'id',
'compute',
'status',
'uuid',
'name',
'title',
'description',
'is_template',
'created',
'drbd',
'arch',
'machine',
'vcpu',
'memory',
'firmware',
'nvram',
'bootmenu',
'boot_order',
'disks',
'media',
'media_iso',
'snapshots',
'networks',
'console_type',
'console_port',
'console_keymap',
'console_listener_address',
'video_model',
'guest_agent_ready',
'autostart']
"id",
"compute",
"status",
"uuid",
"name",
"title",
"description",
"is_template",
"created",
"drbd",
"arch",
"machine",
"vcpu",
"memory",
"firmware",
"nvram",
"bootmenu",
"boot_order",
"disks",
"media",
"media_iso",
"snapshots",
"networks",
"console_type",
"console_port",
"console_keymap",
"console_listener_address",
"video_model",
"guest_agent_ready",
"autostart",
]
class FlavorSerializer(serializers.ModelSerializer):
class Meta:
model = Flavor
fields = ['label', 'memory', 'vcpu', 'disk']
fields = ["label", "memory", "vcpu", "disk"]
class CreateInstanceSerializer(serializers.ModelSerializer):
firmware_choices = (
('', 'BIOS'),
#('UEFI', 'UEFI'),
("", "BIOS"),
# ('UEFI', 'UEFI'),
)
firmware = serializers.ChoiceField(choices = firmware_choices)
graphics = serializers.CharField(initial='vnc')
video = serializers.CharField(initial='vga')
storage = serializers.CharField(initial='default')
cache_mode = serializers.CharField(initial='none')
firmware = serializers.ChoiceField(choices=firmware_choices)
graphics = serializers.CharField(initial="vnc")
video = serializers.CharField(initial="vga")
storage = serializers.CharField(initial="default")
cache_mode = serializers.CharField(initial="none")
virtio = serializers.BooleanField(initial=True)
qemu_ga = serializers.BooleanField(initial=True)
class Meta:
model = CreateInstance
fields = [
'name',
'firmware',
'vcpu',
'vcpu_mode',
'memory',
'networks',
'mac',
'nwfilter',
'storage',
'hdd_size',
'cache_mode',
'meta_prealloc',
'virtio',
'qemu_ga',
'console_pass',
'graphics',
'video',
'listener_addr'
"name",
"firmware",
"vcpu",
"vcpu_mode",
"memory",
"networks",
"mac",
"nwfilter",
"storage",
"hdd_size",
"cache_mode",
"meta_prealloc",
"virtio",
"qemu_ga",
"console_pass",
"graphics",
"video",
"listener_addr",
]
@ -95,6 +93,17 @@ class MigrateSerializer(serializers.ModelSerializer):
instance = Instance.objects.all().prefetch_related("userinstance_set")
live = serializers.BooleanField(initial=True)
xml_del = serializers.BooleanField(initial=True)
class Meta:
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.views import get_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.decorators import action
@ -14,26 +22,39 @@ from rest_framework.response import Response
from vrtManager import util
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):
"""
A simple ViewSet for listing or retrieving ALL/Compute Instances.
"""
permission_classes = [permissions.IsAuthenticated]
def list(self, request):
if request.user.is_superuser or request.user.has_perm("instances.view_instances"):
queryset = Instance.objects.all().prefetch_related("userinstance_set")
else:
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)
def retrieve(self, request, pk=None, compute_pk=None):
queryset = get_instance(request.user, pk)
serializer = InstanceSerializer(queryset, context={'request': request})
serializer = InstanceSerializer(queryset, context={"request": request})
return Response(serializer.data)
@ -42,94 +63,110 @@ class InstanceViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving Compute Instances.
"""
#serializer_class = CreateInstanceSerializer
# serializer_class = CreateInstanceSerializer
permission_classes = [permissions.IsAuthenticated]
def list(self, request, compute_pk=None):
compute = get_object_or_404(Compute, pk=compute_pk)
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):
queryset = get_instance(request.user, pk)
serializer = InstanceDetailsSerializer(queryset, context={'request': request})
serializer = InstanceDetailsSerializer(queryset, context={"request": request})
return Response(serializer.data)
def destroy(self, request, pk=None, compute_pk=None):
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):
poweron(request, pk)
return Response({'status': 'poweron command send'})
@action(detail=True, methods=['post'])
return Response({"status": "poweron command send"})
@action(detail=True, methods=["post"])
def poweroff(self, request, pk=None):
poweroff(request, pk)
return Response({'status': 'poweroff command send'})
@action(detail=True, methods=['post'])
return Response({"status": "poweroff command send"})
@action(detail=True, methods=["post"])
def powercycle(self, request, pk=None):
powercycle(request, pk)
return Response({'status': 'powercycle command send'})
@action(detail=True, methods=['post'])
return Response({"status": "powercycle command send"})
@action(detail=True, methods=["post"])
def forceoff(self, request, pk=None):
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):
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):
resume(request, pk)
return Response({'status': 'resume command send'})
return Response({"status": "resume command send"})
class MigrateViewSet(viewsets.ViewSet):
"""
A viewset for migrating instances.
"""
serializer_class = MigrateSerializer
queryset = ""
def create(self, request):
serializer = MigrateSerializer(data=request.data)
if serializer.is_valid():
instance = serializer.validated_data['instance']
target_host = serializer.validated_data['target_compute']
live = serializer.validated_data['live']
unsafe = serializer.validated_data['unsafe']
xml_del = serializer.validated_data['xml_del']
offline = serializer.validated_data['offline']
autoconverge = serializer.validated_data['autoconverge']
postcopy = serializer.validated_data['postcopy']
compress = serializer.validated_data['compress']
instance = serializer.validated_data["instance"]
target_host = serializer.validated_data["target_compute"]
live = serializer.validated_data["live"]
unsafe = serializer.validated_data["unsafe"]
xml_del = serializer.validated_data["xml_del"]
offline = serializer.validated_data["offline"]
autoconverge = serializer.validated_data["autoconverge"]
postcopy = serializer.validated_data["postcopy"]
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:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class FlavorViewSet(viewsets.ModelViewSet):
"""
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
permission_classes = [permissions.IsAuthenticated]
@ -138,15 +175,15 @@ class CreateInstanceViewSet(viewsets.ViewSet):
"""
A viewset for creating instances.
"""
serializer_class = CreateInstanceSerializer
queryset = ""
def create(self, request, compute_pk=None, arch=None, machine=None):
serializer = CreateInstanceSerializer(data=request.data,
context = {'compute_pk': compute_pk,
'arch': arch,
'machine': machine
})
serializer = CreateInstanceSerializer(
data=request.data,
context={"compute_pk": compute_pk, "arch": arch, "machine": machine},
)
if serializer.is_valid():
volume_list = []
default_bus = app_settings.INSTANCE_VOLUME_DEFAULT_BUS
@ -166,14 +203,14 @@ class CreateInstanceViewSet(viewsets.ViewSet):
)
path = conn.create_volume(
serializer.validated_data['storage'],
serializer.validated_data['name'],
serializer.validated_data['hdd_size'],
default_disk_format,
serializer.validated_data['meta_prealloc'],
default_disk_owner_uid,
default_disk_owner_gid,
)
serializer.validated_data["storage"],
serializer.validated_data["name"],
serializer.validated_data["hdd_size"],
default_disk_format,
serializer.validated_data["meta_prealloc"],
default_disk_owner_uid,
default_disk_owner_gid,
)
volume = {}
firmware = {}
volume["device"] = "disk"
@ -189,8 +226,8 @@ class CreateInstanceViewSet(viewsets.ViewSet):
volume_list.append(volume)
if "UEFI" in serializer.validated_data['firmware']:
firmware["loader"] = serializer.validated_data['firmware'].split(":")[1].strip()
if "UEFI" in serializer.validated_data["firmware"]:
firmware["loader"] = (serializer.validated_data["firmware"].split(":")[1].strip())
firmware["secure"] = "no"
firmware["readonly"] = "yes"
firmware["type"] = "pflash"
@ -199,26 +236,26 @@ class CreateInstanceViewSet(viewsets.ViewSet):
firmware["secure"] = "yes"
ret = conn.create_instance(
name=serializer.validated_data['name'],
memory=serializer.validated_data['memory'],
vcpu=serializer.validated_data['vcpu'],
vcpu_mode=serializer.validated_data['vcpu_mode'],
name=serializer.validated_data["name"],
memory=serializer.validated_data["memory"],
vcpu=serializer.validated_data["vcpu"],
vcpu_mode=serializer.validated_data["vcpu_mode"],
uuid=util.randomUUID(),
arch=arch,
machine=machine,
firmware=firmware,
volumes=volume_list,
networks=serializer.validated_data['networks'],
nwfilter=serializer.validated_data['nwfilter'],
graphics=serializer.validated_data['graphics'],
virtio=serializer.validated_data['virtio'],
listener_addr=serializer.validated_data['listener_addr'],
video=serializer.validated_data['video'],
console_pass=serializer.validated_data['console_pass'],
mac=serializer.validated_data['mac'],
qemu_ga=serializer.validated_data['qemu_ga'],
networks=serializer.validated_data["networks"],
nwfilter=serializer.validated_data["nwfilter"],
graphics=serializer.validated_data["graphics"],
virtio=serializer.validated_data["virtio"],
listener_addr=serializer.validated_data["listener_addr"],
video=serializer.validated_data["video"],
console_pass=serializer.validated_data["console_pass"],
mac=serializer.validated_data["mac"],
qemu_ga=serializer.validated_data["qemu_ga"],
)
msg = f"Instance {serializer.validated_data['name']} is created"
return Response({'status': msg })
return Response({"status": msg})
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):
'''
"""
Migrate can clone instances user attribute to permission
'''
"""
from django.contrib.auth.models import Permission, User
plan = kwargs.get('plan', [])
plan = kwargs.get("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()
permission = Permission.objects.get(codename='clone_instances')
print('\033[1m* \033[92mMigrating can_clone_instaces user attribute to permission\033[0m')
permission = Permission.objects.get(codename="clone_instances")
print(
"\033[1m* \033[92mMigrating can_clone_instaces user attribute to permission\033[0m"
)
for user in users:
if user.userattributes:
if user.userattributes.can_clone_instances:
@ -22,25 +24,26 @@ def migrate_can_clone_instances(sender, **kwargs):
def apply_passwordless_console(sender, **kwargs):
'''
"""
Apply new passwordless_console permission for all users
'''
"""
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
User = get_user_model()
plan = kwargs.get('plan', [])
plan = kwargs.get("plan", [])
for migration, rolled_back in plan:
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')
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")
users = User.objects.all()
permission = Permission.objects.get(codename='passwordless_console')
permission = Permission.objects.get(codename="passwordless_console")
for user in users:
user.user_permissions.add(permission)
class InstancesConfig(AppConfig):
name = 'instances'
verbose_name = 'Instances'
name = "instances"
verbose_name = "Instances"
def ready(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 Meta:
model = Flavor
fields = '__all__'
fields = "__all__"
class ConsoleForm(forms.Form):
@ -20,17 +20,25 @@ class ConsoleForm(forms.Form):
listen_on = forms.ChoiceField()
generate_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)
keymap = forms.ChoiceField(required=False)
def __init__(self, *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())
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)
type_choices = (
(c, c)
for c in AppSettings.objects.get(key="QEMU_CONSOLE_DEFAULT_TYPE").choices_as_list()
)
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):
@ -57,12 +65,16 @@ class NewVMForm(forms.ModelForm):
# listener_addr = forms.ChoiceField(required=True, widget=forms.RadioSelect, choices=QEMU_CONSOLE_LISTENER_ADDRESSES)
class Meta:
model = CreateInstance
fields = '__all__'
exclude = ['compute']
fields = "__all__"
exclude = ["compute"]
def clean_name(self):
name = self.cleaned_data['name']
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
name = self.cleaned_data["name"]
have_symbol = re.match("^[a-zA-Z0-9._-]+$", name)
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

View file

@ -10,10 +10,10 @@ from vrtManager.instance import wvmInstance
class Flavor(models.Model):
label = models.CharField(_('label'), max_length=12, unique=True)
memory = models.IntegerField(_('memory'))
vcpu = models.IntegerField(_('vcpu'))
disk = models.IntegerField(_('disk'))
label = models.CharField(_("label"), max_length=12, unique=True)
memory = models.IntegerField(_("memory"))
vcpu = models.IntegerField(_("vcpu"))
disk = models.IntegerField(_("disk"))
def __str__(self):
return self.label
@ -21,21 +21,21 @@ class Flavor(models.Model):
class InstanceManager(models.Manager):
def get_queryset(self):
return super().get_queryset().select_related('compute')
return super().get_queryset().select_related("compute")
class Instance(models.Model):
compute = models.ForeignKey(Compute, on_delete=models.CASCADE)
name = models.CharField(_('name'), max_length=120, db_index=True)
uuid = models.CharField(_('uuid'), max_length=36, db_index=True)
is_template = models.BooleanField(_('is template'), default=False)
created = models.DateTimeField(_('created'), auto_now_add=True)
drbd = models.CharField(_('drbd'), max_length=24, default="None")
name = models.CharField(_("name"), max_length=120, db_index=True)
uuid = models.CharField(_("uuid"), max_length=36, db_index=True)
is_template = models.BooleanField(_("is template"), default=False)
created = models.DateTimeField(_("created"), auto_now_add=True)
drbd = models.CharField(_("drbd"), max_length=24, default="None")
objects = InstanceManager()
def __str__(self):
return f'{self.compute}/{self.name}'
return f"{self.compute}/{self.name}"
@cached_property
def proxy(self):
@ -173,7 +173,7 @@ class Instance(models.Model):
@cached_property
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
def inst_xml(self):
@ -209,35 +209,59 @@ class Instance(models.Model):
class MigrateInstance(models.Model):
instance = models.ForeignKey(Instance, related_name='source_host', on_delete=models.DO_NOTHING)
target_compute = models.ForeignKey(Compute, related_name='target_host', on_delete=models.DO_NOTHING)
instance = models.ForeignKey(
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'))
xml_del = models.BooleanField(_('Undefine XML'), default=True)
offline = models.BooleanField(_('Offline'))
autoconverge = models.BooleanField(_('Auto Converge'), default=True)
compress = models.BooleanField(_('Compress'), default=False)
postcopy = models.BooleanField(_('Post Copy'), default=False)
unsafe = models.BooleanField(_('Unsafe'), default=False)
live = models.BooleanField(_("Live"))
xml_del = models.BooleanField(_("Undefine XML"), default=True)
offline = models.BooleanField(_("Offline"))
autoconverge = models.BooleanField(_("Auto Converge"), default=True)
compress = models.BooleanField(_("Compress"), default=False)
postcopy = models.BooleanField(_("Post Copy"), default=False)
unsafe = models.BooleanField(_("Unsafe"), default=False)
class Meta:
managed = False
class CreateInstance(models.Model):
compute = models.ForeignKey(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')})
compute = models.ForeignKey(
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)
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)
disk = models.IntegerField(blank=True)
memory = models.IntegerField(error_messages={'required': _('No RAM size has been entered')})
networks = models.CharField(max_length=256, error_messages={'required': _('No Network pool has been choosen')})
memory = models.IntegerField(
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)
storage = models.CharField(max_length=256, blank=True)
template = 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)
meta_prealloc = models.BooleanField(default=False, blank=True)
virtio = models.BooleanField(default=True)
@ -246,9 +270,15 @@ class CreateInstance(models.Model):
console_pass = models.CharField(max_length=64, blank=True)
add_cdrom = 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')})
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)
graphics = models.CharField(
max_length=16, error_messages={"required": _("Please select a graphics type")}
)
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:
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
"""
class Meta:
default_permissions = ()
permissions = [
('clone_instances', 'Can clone instances'),
('passwordless_console', _('Can access console without password')),
('view_instances', 'Can view instances'),
('snapshot_instances', 'Can snapshot instances'),
("clone_instances", "Can clone instances"),
("passwordless_console", _("Can access console without password")),
("view_instances", "Can view instances"),
("snapshot_instances", "Can snapshot instances"),
]
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
app_name = 'instances'
app_name = "instances"
urlpatterns = [
path('', views.index, name='index'),
path('flavor/create/', views.flavor_create, name='flavor_create'),
path('flavor/<int:pk>/update/', views.flavor_update, name='flavor_update'),
path('flavor/<int:pk>/delete/', views.flavor_delete, name='flavor_delete'),
path('<int:pk>/', views.instance, name='instance'),
path('<int:pk>/poweron/', views.poweron, name='poweron'),
path('<int:pk>/powercycle/', views.powercycle, name='powercycle'),
path('<int:pk>/poweroff/', views.poweroff, name='poweroff'),
path('<int:pk>/suspend/', views.suspend, name='suspend'),
path('<int:pk>/resume/', views.resume, name='resume'),
path('<int:pk>/force_off/', views.force_off, name='force_off'),
path('<int:pk>/destroy/', views.destroy, name='destroy'),
path('<int:pk>/migrate/', views.migrate, name='migrate'),
path('<int:pk>/status/', views.status, name='status'),
path('<int:pk>/stats/', views.stats, name='stats'),
path('<int:pk>/osinfo/', views.osinfo, name='osinfo'),
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>/resizevm_cpu/', views.resizevm_cpu, name='resizevm_cpu'),
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>/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>/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>/edit_volume/', views.edit_volume, name='edit_volume'),
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>/detach_cdrom/<str:dev>/', views.detach_cdrom, name='detach_cdrom'),
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>/snapshot/', views.snapshot, name='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>/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_autostart/', views.set_autostart, name='set_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>/unset_bootmenu/', views.unset_bootmenu, name='unset_bootmenu'),
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>/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>/change_network/', views.change_network, name='change_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>/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>/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('<int:pk>/clone/', views.clone, name='clone'),
path('<int:pk>/update_console/', views.update_console, name='update_console'),
path('<int:pk>/change_options/', views.change_options, name='change_options'),
path('<int:pk>/getvvfile/', views.getvvfile, name='getvvfile'), # no links to this one???
path('create/<int:compute_id>/', 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'),
path("", views.index, name="index"),
path("flavor/create/", views.flavor_create, name="flavor_create"),
path("flavor/<int:pk>/update/", views.flavor_update, name="flavor_update"),
path("flavor/<int:pk>/delete/", views.flavor_delete, name="flavor_delete"),
path("<int:pk>/", views.instance, name="instance"),
path("<int:pk>/poweron/", views.poweron, name="poweron"),
path("<int:pk>/powercycle/", views.powercycle, name="powercycle"),
path("<int:pk>/poweroff/", views.poweroff, name="poweroff"),
path("<int:pk>/suspend/", views.suspend, name="suspend"),
path("<int:pk>/resume/", views.resume, name="resume"),
path("<int:pk>/force_off/", views.force_off, name="force_off"),
path("<int:pk>/destroy/", views.destroy, name="destroy"),
path("<int:pk>/migrate/", views.migrate, name="migrate"),
path("<int:pk>/status/", views.status, name="status"),
path("<int:pk>/stats/", views.stats, name="stats"),
path("<int:pk>/osinfo/", views.osinfo, name="osinfo"),
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>/resizevm_cpu/", views.resizevm_cpu, name="resizevm_cpu"),
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>/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>/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>/edit_volume/", views.edit_volume, name="edit_volume"),
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>/detach_cdrom/<str:dev>/", views.detach_cdrom, name="detach_cdrom"),
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>/snapshot/", views.snapshot, name="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>/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_autostart/", views.set_autostart, name="set_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>/unset_bootmenu/", views.unset_bootmenu, name="unset_bootmenu"),
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>/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>/change_network/", views.change_network, name="change_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>/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>/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("<int:pk>/clone/", views.clone, name="clone"),
path("<int:pk>/update_console/", views.update_console, name="update_console"),
path("<int:pk>/change_options/", views.change_options, name="change_options"),
path(
"<int:pk>/getvvfile/", views.getvvfile, name="getvvfile"
), # no links to this one???
path(
"create/<int:compute_id>/",
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()
for usr_inst in user_instances:
if connection_manager.host_is_up(
usr_inst.instance.compute.type,
usr_inst.instance.compute.hostname,
usr_inst.instance.compute.type,
usr_inst.instance.compute.hostname,
):
conn = wvmInstance(
usr_inst.instance.compute.hostname,
@ -51,8 +51,8 @@ def check_user_quota(user, instance, cpu, memory, disk_size):
cpu += int(conn.get_vcpu())
memory += int(conn.get_memory())
for disk in conn.get_disk_devices():
if disk['size']:
disk_size += int(disk['size']) >> 30
if disk["size"]:
disk_size += int(disk["size"]) >> 30
if ua.max_instances > 0 and instance > ua.max_instances:
msg = "instance"
@ -86,17 +86,17 @@ def get_new_disk_dev(media, disks, bus):
dev_base = "sd"
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
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:
dev = dev_base + al
if dev not in existing_disk_devs and dev not in existing_media_devs:
return dev
raise Exception(_('None available device name'))
raise Exception(_("None available device name"))
def get_network_tuple(network_source_str):
@ -104,7 +104,7 @@ def get_network_tuple(network_source_str):
if len(network_source_pack) > 1:
return network_source_pack[1], network_source_pack[0]
else:
return network_source_pack[0], 'net'
return network_source_pack[0], "net"
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(uuid__in=domain_uuids).delete()
# 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:
if domain.name() not in names:
Instance(compute=compute, name=domain.name(), uuid=domain.UUIDString()).save()
def get_dhcp_mac_address(vname):
dhcp_file = str(settings.BASE_DIR) + '/dhcpd.conf'
mac = ''
dhcp_file = str(settings.BASE_DIR) + "/dhcpd.conf"
mac = ""
if os.path.isfile(dhcp_file):
with open(dhcp_file, 'r') as f:
with open(dhcp_file, "r") as f:
name_found = False
for line in f:
if "host %s." % vname in line:
name_found = True
if name_found and "hardware ethernet" in line:
mac = line.split(' ')[-1].strip().strip(';')
mac = line.split(" ")[-1].strip().strip(";")
break
return mac
def get_random_mac_address():
mac = '52:54:00:%02x:%02x:%02x' % (
random.randint(0x00, 0xff),
random.randint(0x00, 0xff),
random.randint(0x00, 0xff),
mac = "52:54:00:%02x:%02x:%02x" % (
random.randint(0x00, 0xFF),
random.randint(0x00, 0xFF),
random.randint(0x00, 0xFF),
)
return mac
def get_clone_disk_name(disk, prefix, clone_name=''):
if not disk['image']:
def get_clone_disk_name(disk, prefix, clone_name=""):
if not disk["image"]:
return None
if disk['image'].startswith(prefix) and clone_name:
suffix = disk['image'][len(prefix):]
if disk["image"].startswith(prefix) and clone_name:
suffix = disk["image"][len(prefix) :]
image = f"{clone_name}{suffix}"
elif "." in disk['image'] and len(disk['image'].rsplit(".", 1)[1]) <= 7:
name, suffix = disk['image'].rsplit(".", 1)
elif "." in disk["image"] and len(disk["image"].rsplit(".", 1)[1]) <= 7:
name, suffix = disk["image"].rsplit(".", 1)
image = f"{name}-clone.{suffix}"
else:
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.utils.translation import gettext_lazy as _
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 vrtManager import util
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"):
instances = Instance.objects.all().prefetch_related("userinstance_set")
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):
@ -63,7 +71,9 @@ def instance(request, pk):
users = User.objects.all().order_by("username")
publickeys = UserSSHKey.objects.filter(user_id=request.user.id)
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(
initial={
"type": instance.console_type,
@ -74,10 +84,14 @@ def instance(request, pk):
)
console_listener_addresses = settings.QEMU_CONSOLE_LISTENER_ADDRESSES
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:
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:
userinstance = None
@ -116,7 +130,9 @@ def instance(request, pk):
# userinstances = UserInstance.objects.filter(instance=instance).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
vcpu_host = len(instance.vcpu_range)
@ -128,7 +144,7 @@ def instance(request, pk):
storages_host = sorted(instance.proxy.get_storages(True))
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.save()
@ -139,17 +155,25 @@ def status(request, pk):
instance = get_instance(request.user, pk)
return JsonResponse({"status": instance.proxy.get_status()})
def drbd_status(request, pk):
instance = get_instance(request.user, pk)
result = "None DRBD"
if instance.compute.type == 2:
conn = instance.compute.login + "@" + instance.compute.hostname
remoteDrbdStatus = subprocess.run(["ssh", conn, "sudo", "/usr/sbin/drbdadm", "status", "&&", "exit"], stdout=subprocess.PIPE, text=True)
remoteDrbdStatus = subprocess.run(
["ssh", conn, "sudo", "/usr/sbin/drbdadm", "status", "&&", "exit"],
stdout=subprocess.PIPE,
text=True,
)
if remoteDrbdStatus.stdout:
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)
primaryCount = 0
@ -179,6 +203,7 @@ def drbd_status(request, pk):
return result
def stats(request, pk):
instance = get_instance(request.user, pk)
json_blk = []
@ -192,10 +217,20 @@ def stats(request, pk):
current_time = time.strftime("%H:%M:%S")
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:
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(
{
@ -207,12 +242,14 @@ def stats(request, pk):
}
)
def osinfo(request, pk):
instance = get_instance(request.user, pk)
results = instance.proxy.osinfo()
return JsonResponse(results)
def guess_mac_address(request, vname):
data = {"vname": vname}
mac = utils.get_dhcp_mac_address(vname)
@ -232,7 +269,9 @@ def guess_clone_name(request):
dhcp_file = "/srv/webvirtcloud/dhcpd.conf"
prefix = app_settings.CLONE_INSTANCE_DEFAULT_PREFIX
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:
for line in f:
line = line.strip()
@ -281,7 +320,11 @@ def get_instance(user, pk):
instance = get_object_or_404(Instance, pk=pk)
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
else:
raise Http404()
@ -293,7 +336,9 @@ def poweron(request, pk):
messages.warning(request, _("Templates cannot be started."))
else:
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"))
@ -302,14 +347,18 @@ def powercycle(request, pk):
instance = get_instance(request.user, pk)
instance.proxy.force_shutdown()
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"))
def poweroff(request, pk):
instance = get_instance(request.user, pk)
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"))
@ -333,7 +382,9 @@ def resume(request, pk):
def force_off(request, pk):
instance = get_instance(request.user, pk)
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"))
@ -349,7 +400,9 @@ def destroy(request, pk):
instance.proxy.force_shutdown()
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:
instance.proxy.snapshot_delete(snapshot["name"])
instance.proxy.delete_all_disks()
@ -360,7 +413,9 @@ def destroy(request, pk):
instance.proxy.delete(VIR_DOMAIN_UNDEFINE_KEEP_NVRAM)
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 render(
@ -390,12 +445,26 @@ def migrate(request, pk):
target_host = Compute.objects.get(id=compute_id)
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:
messages.error(request, err)
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)
return redirect(request.META.get("HTTP_REFERER"))
@ -419,7 +488,9 @@ def set_root_pass(request, pk):
s.close()
if result["return"] == "success":
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)
else:
messages.error(request, result["message"])
@ -434,7 +505,11 @@ def add_public_key(request, pk):
if request.method == "POST":
sshkeyid = request.POST.get("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:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@ -445,7 +520,9 @@ def add_public_key(request, pk):
if result["return"] == "error":
msg = result["message"]
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)
if result["return"] == "success":
@ -470,9 +547,13 @@ def resizevm_cpu(request, pk):
new_vcpu = request.POST.get("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:
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,
"instance_name": instance.name,
}
@ -481,8 +562,13 @@ def resizevm_cpu(request, pk):
cur_vcpu = new_cur_vcpu
vcpu = new_vcpu
instance.proxy.resize_cpu(cur_vcpu, vcpu)
msg = _("CPU is resized: %(old)s to %(new)s") % {"old": cur_vcpu, "new": vcpu}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
msg = _("CPU is resized: %(old)s to %(new)s") % {
"old": cur_vcpu,
"new": vcpu,
}
addlogmsg(
request.user.username, instance.compute.name, instance.name, msg
)
messages.success(request, msg)
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", "")
if 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:
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,
"instance_name": instance.name,
}
messages.error(request, msg)
else:
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_max": memory,
"new_cur": new_cur_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)
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:
disks_new = list()
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):
disk["size_new"] = input_disk_size
disks_new.append(disk)
disk_sum = sum([disk["size"] >> 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:
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,
"instance_name": instance.name,
}
@ -558,7 +658,9 @@ def resize_disk(request, pk):
else:
instance.proxy.resize_disk(disks_new)
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)
return redirect(reverse("instances:instance", args=[instance.id]) + "#resize")
@ -566,7 +668,9 @@ def resize_disk(request, pk):
def add_new_vol(request, 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:
media = instance.proxy.get_media_devices()
@ -607,20 +711,34 @@ def add_new_vol(request, pk):
pool_type = conn_pool.get_type()
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()
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
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)
msg = _("Attach new disk: %(name)s (%(format)s)") % {"name": name, "format": format}
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,
)
msg = _("Attach new disk: %(name)s (%(format)s)") % {
"name": name,
"format": format,
}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
return redirect(request.META.get("HTTP_REFERER") + "#disks")
def add_existing_vol(request, 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:
storage = request.POST.get("selected_storage", "")
name = request.POST.get("vols", "")
@ -641,7 +759,7 @@ def add_existing_vol(request, pk):
format_type = conn_create.get_volume_format_type(name)
disk_type = conn_create.get_volume_type(name)
pool_type = conn_create.get_type()
if pool_type == 'rbd':
if pool_type == "rbd":
source_info = conn_create.get_rbd_source()
path = conn_create.get_source_name()
else:
@ -651,7 +769,16 @@ def add_existing_vol(request, pk):
target_dev = utils.get_new_disk_dev(media, disks, bus)
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}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
return redirect(request.META.get("HTTP_REFERER") + "#disks")
@ -659,7 +786,9 @@ def add_existing_vol(request, pk):
def edit_volume(request, 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:
target_dev = request.POST.get("dev", "")
@ -671,10 +800,16 @@ def edit_volume(request, pk):
new_bus = request.POST.get("vol_bus", bus)
serial = request.POST.get("vol_serial", "")
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)
discard = request.POST.get("vol_discard_mode", app_settings.INSTANCE_VOLUME_DEFAULT_DISCARD)
zeroes = request.POST.get("vol_detect_zeroes", app_settings.INSTANCE_VOLUME_DEFAULT_DETECT_ZEROES)
discard = request.POST.get(
"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)
if new_bus != bus:
@ -710,7 +845,10 @@ def edit_volume(request, pk):
if not instance.proxy.get_status() == 5:
messages.success(
request,
_("Volume changes are applied. " + "But it will be activated after shutdown"),
_(
"Volume changes are applied. "
+ "But it will be activated after shutdown"
),
)
else:
messages.success(request, _("Volume is changed successfully."))
@ -722,7 +860,9 @@ def edit_volume(request, pk):
def delete_vol(request, 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:
storage = request.POST.get("storage", "")
conn_delete = wvmStorage(
@ -746,7 +886,9 @@ def delete_vol(request, pk):
def detach_vol(request, 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:
dev = request.POST.get("dev", "")
@ -760,11 +902,20 @@ def detach_vol(request, pk):
def add_cdrom(request, 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:
bus = request.POST.get("bus", "ide" if instance.machine == "pc" else "sata")
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}
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):
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:
# dev = request.POST.get('detach_cdrom', '')
@ -786,7 +939,9 @@ def detach_cdrom(request, pk, dev):
def unmount_iso(request, 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:
image = request.POST.get("path", "")
dev = request.POST.get("umount_iso", "")
@ -799,7 +954,9 @@ def unmount_iso(request, pk):
def mount_iso(request, 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:
image = request.POST.get("media", "")
dev = request.POST.get("mount_iso", "")
@ -812,9 +969,13 @@ def mount_iso(request, pk):
def snapshot(request, 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", "")
desc = request.POST.get("description", "")
instance.proxy.create_snapshot(name, desc)
@ -825,8 +986,12 @@ def snapshot(request, pk):
def delete_snapshot(request, 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
if allow_admin_or_not_template and request.user.has_perm("instances.snapshot_instances"):
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"
):
snap_name = request.POST.get("name", "")
instance.proxy.snapshot_delete(snap_name)
msg = _("Delete snapshot: %(snap)s") % {"snap": snap_name}
@ -836,8 +1001,12 @@ def delete_snapshot(request, pk):
def revert_snapshot(request, 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
if allow_admin_or_not_template and request.user.has_perm("instances.snapshot_instances"):
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"
):
snap_name = request.POST.get("name", "")
instance.proxy.snapshot_revert(snap_name)
msg = _("Successful revert snapshot: ")
@ -865,7 +1034,7 @@ def set_vcpu(request, pk):
@superuser_only
def set_vcpu_hotplug(request, 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}
instance.proxy.set_vcpu_hotplug(status)
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:
messages.success(
request,
_("Boot menu changes applied. " + "But it will be activated after shutdown"),
_(
"Boot menu changes applied. "
+ "But it will be activated after shutdown"
),
)
else:
messages.success(request, _("Boot order changed successfully."))
@ -979,7 +1151,7 @@ def change_network(request, pk):
network_data[post] = source
network_data[post + "-type"] = source_type
if source_type == 'iface':
if source_type == "iface":
iface = wvmInterface(
instance.compute.hostname,
instance.compute.login,
@ -1007,14 +1179,14 @@ def add_network(request, pk):
nwfilter = request.POST.get("add-net-nwfilter")
(source, source_type) = utils.get_network_tuple(request.POST.get("add-net-network"))
if source_type == 'iface':
if source_type == "iface":
iface = wvmInterface(
instance.compute.hostname,
instance.compute.login,
instance.compute.password,
instance.compute.type,
source,
)
instance.compute.hostname,
instance.compute.login,
instance.compute.password,
instance.compute.type,
source,
)
source_type = iface.get_type()
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)
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:
messages.success(
request,
@ -1084,7 +1258,9 @@ def unset_qos(request, pk):
instance.proxy.unset_qos(mac, qos_dir)
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:
messages.success(
request,
@ -1108,7 +1284,9 @@ def add_owner(request, pk):
check_inst = UserInstance.objects.filter(instance=instance).count()
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:
add_user_inst = UserInstance(instance=instance, user_id=user_id)
add_user_inst.save()
@ -1137,7 +1315,9 @@ def clone(request, pk):
clone_data["name"] = request.POST.get("name", "")
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"])
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
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,
"clone_name": clone_data["name"],
}
messages.error(request, msg)
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)
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)
elif not re.match(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"]}
elif not re.match(
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)
else:
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_instance.uuid = new_uuid
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()
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)
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":
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]))
except Exception as e:
@ -1223,7 +1423,9 @@ def update_console(request, pk):
messages.error(request, msg)
else:
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 form.cleaned_data["clear_keymap"]:
@ -1232,17 +1434,23 @@ def update_console(request, pk):
instance.proxy.set_console_keymap(form.cleaned_data["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:
instance.proxy.set_console_type(form.cleaned_data["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:
instance.proxy.set_console_listener_addr(form.cleaned_data["listen_on"])
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")
@ -1326,7 +1534,15 @@ def create_instance_select_type(request, compute_id):
all_hypervisors = conn.get_hypervisors_machines()
# 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]
default_machine = app_settings.INSTANCE_MACHINE_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()
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_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))
default_graphics = app_settings.QEMU_CONSOLE_DEFAULT_TYPE
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
dom_caps = conn.get_dom_capabilities(arch, machine)
@ -1437,13 +1658,21 @@ def create_instance(request, compute_id, arch, machine):
meta_prealloc = True
if 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"]):
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 not data["mac"]:
raise libvirtError(_("No Virtual Machine MAC has been entered"))
raise libvirtError(
_("No Virtual Machine MAC has been entered")
)
else:
path = conn.create_volume(
data["storage"],
@ -1471,10 +1700,14 @@ def create_instance(request, compute_id, arch, machine):
elif 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:
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:
clone_path = conn.clone_from_template(
@ -1501,15 +1734,21 @@ def create_instance(request, compute_id, arch, machine):
is_disk_created = True
else:
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:
for idx, vol in enumerate(data["images"].split(",")):
path = conn.get_volume_path(vol)
volume = dict()
volume["path"] = path
volume["type"] = conn.get_volume_format_type(path)
volume["device"] = request.POST.get("device" + str(idx), "")
volume["bus"] = request.POST.get("bus" + str(idx), "")
volume["device"] = request.POST.get(
"device" + str(idx), ""
)
volume["bus"] = request.POST.get(
"bus" + str(idx), ""
)
if volume["bus"] == "scsi":
volume["scsi_model"] = default_scsi_disk_model
volume["cache_mode"] = data["cache_mode"]
@ -1560,12 +1799,21 @@ def create_instance(request, compute_id, arch, machine):
add_cdrom=data["add_cdrom"],
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()
msg = _("Instance is created")
messages.success(request, msg)
addlogmsg(request.user.username, create_instance.compute.name, create_instance.name, msg)
return redirect(reverse("instances:instance", args=[create_instance.id]))
addlogmsg(
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:
if data["hdd_size"] or len(volume_list) > 0:
if is_disk_created:

View file

@ -7,13 +7,33 @@ from django.utils.translation import gettext_lazy as _
class AddInterface(forms.Form):
name = forms.CharField(max_length=10, required=True)
itype = forms.ChoiceField(required=True, choices=(('bridge', 'bridge'), ('ethernet', 'ethernet')))
start_mode = forms.ChoiceField(required=True,
choices=(('none', 'none'), ('onboot', 'onboot'), ('hotplug', 'hotplug')))
start_mode = forms.ChoiceField(
required=True,
choices=(
('none', 'none'),
('onboot', 'onboot'),
('hotplug', 'hotplug')
)
)
netdev = forms.CharField(max_length=15, required=True)
ipv4_type = forms.ChoiceField(required=True, choices=(('dhcp', 'dhcp'), ('static', 'static'), ('none', 'none')))
ipv4_type = forms.ChoiceField(
required=True,
choices=(
('dhcp', 'dhcp'),
('static', 'static'),
('none', 'none')
)
)
ipv4_addr = forms.CharField(max_length=18, required=False)
ipv4_gw = forms.CharField(max_length=15, required=False)
ipv6_type = forms.ChoiceField(required=True, choices=(('dhcp', 'dhcp'), ('static', 'static'), ('none', 'none')))
ipv6_type = forms.ChoiceField(
required=True,
choices=(
('dhcp', 'dhcp'),
('static', 'static'),
('none', 'none')
)
)
ipv6_addr = forms.CharField(max_length=100, required=False)
ipv6_gw = forms.CharField(max_length=100, required=False)
stp = forms.ChoiceField(required=False, choices=(('on', 'on'), ('off', 'off')))
@ -39,7 +59,7 @@ class AddInterface(forms.Form):
def clean_ipv6_addr(self):
ipv6_addr = self.cleaned_data['ipv6_addr']
have_symbol = re.match('^[0-9a-f./:]+$', ipv6_addr)
have_symbol = re.match('^[0-9a-f./:]+|^$', ipv6_addr)
if not have_symbol:
raise forms.ValidationError(_('The IPv6 address must not contain any special characters'))
elif len(ipv6_addr) > 100:
@ -48,7 +68,7 @@ class AddInterface(forms.Form):
def clean_ipv6_gw(self):
ipv6_gw = self.cleaned_data['ipv6_gw']
have_symbol = re.match('^[0-9.]+$', ipv6_gw)
have_symbol = re.match('^[0-9a-f./:]+|^$', ipv6_gw)
if not have_symbol:
raise forms.ValidationError(_('The IPv6 gateway must not contain any special characters'))
elif len(ipv6_gw) > 100:

View file

@ -4,10 +4,14 @@ from django.utils.translation import gettext_lazy as _
# Create your models here.
class Interfaces(models.Model):
name = models.CharField(_('name'), max_length=20, 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)
name = models.CharField(
_("name"),
max_length=20,
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:
managed = False

View file

@ -95,13 +95,13 @@
<div class="row static_ipv4_form_group">
<label class="col-sm-3 col-form-label">{% trans "IPv4 Address" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="ipv4_addr" maxlength="19" pattern="[0-9\.\/]+">
<input type="text" class="form-control" name="ipv4_addr" maxlength="19" pattern="[0-9\.\/]+|^$">
</div>
</div>
<div class="row static_ipv4_form_group">
<label class="col-sm-3 col-form-label">{% trans "IPv4 Gateway" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="ipv4_gw" maxlength="16" pattern="[0-9\.]+">
<input type="text" class="form-control" name="ipv4_gw" maxlength="16" pattern="[0-9\.]+|^$">
</div>
</div>
</div>
@ -121,13 +121,13 @@
<div class="row static_ipv6_form_group">
<label class="col-sm-3 col-form-label">{% trans "IPv6 Address" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="ipv6_addr" maxlength="100" pattern="[0-9a-f\:\/]+">
<input type="text" class="form-control" name="ipv6_addr" maxlength="100" pattern="[0-9a-f\:\/]+|^$">
</div>
</div>
<div class="row static_ipv6_form_group">
<label class="col-sm-3 col-form-label">{% trans "IPv6 Gateway" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="ipv6_gw" maxlength="100" pattern="[0-9a-f\:]+">
<input type="text" class="form-control" name="ipv6_gw" maxlength="100" pattern="[0-9a-f\:]+|^$">
</div>
</div>
</div>

View file

@ -21,7 +21,12 @@ def interfaces(request, compute_id):
compute = get_object_or_404(Compute, pk=compute_id)
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()
try:
netdevs = conn.get_net_devices()
@ -29,7 +34,13 @@ def interfaces(request, compute_id):
netdevs = ["eth0", "eth1"]
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())
if request.method == "POST":
@ -75,7 +86,13 @@ def interface(request, compute_id, iface):
compute = get_object_or_404(Compute, pk=compute_id)
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()
state = conn.is_active()
mac = conn.get_mac()

View file

@ -1,12 +1,8 @@
from rest_framework import serializers
from networks.models import Networks
class NetworksSerializer(serializers.ModelSerializer):
class Meta:
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):
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(
error_messages={"required": _("No IPv4 subnet has been entered")},
max_length=20,
@ -27,27 +30,39 @@ class AddNetPool(forms.Form):
name = self.cleaned_data["name"]
have_symbol = re.match(r"^[a-zA-Z0-9\.\_\-]+$", name)
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:
raise forms.ValidationError(_("The pool name must not exceed 20 characters"))
raise forms.ValidationError(
_("The pool name must not exceed 20 characters")
)
return name
def clean_subnet(self):
subnet = self.cleaned_data["subnet"]
have_symbol = re.match("^[0-9./]+$", subnet if subnet else ".")
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:
raise forms.ValidationError(_("The IPv4 subnet must not exceed 20 characters"))
raise forms.ValidationError(
_("The IPv4 subnet must not exceed 20 characters")
)
return subnet
def clean_subnet6(self):
subnet = self.cleaned_data["subnet6"]
have_symbol = re.match("^[0-9a-fA-F:/]+$", subnet if subnet else ":")
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:
raise forms.ValidationError(_("The IPv6 subnet must not exceed 42 characters"))
raise forms.ValidationError(
_("The IPv6 subnet must not exceed 42 characters")
)
return subnet
def clean_bridge_name(self):
@ -55,7 +70,11 @@ class AddNetPool(forms.Form):
if self.cleaned_data["forward"] in ["bridge", "macvtap"]:
have_symbol = re.match(r"^[a-zA-Z0-9\.\_\:\-]+$", bridge_name)
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:
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

View file

@ -4,10 +4,14 @@ from django.utils.translation import gettext_lazy as _
# Create your models here.
class Networks(models.Model):
name = models.CharField(_('name'), max_length=20, 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)
name = models.CharField(
_("name"),
max_length=20,
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:
managed = False

View file

@ -43,18 +43,26 @@ def networks(request, compute_id):
msg = _("Network pool name already in use")
messages.error(request, msg)
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"))
errors = True
if data["subnet"]:
ipv4 = True
gateway4, netmask4, dhcp4 = network_size(data["subnet"], data["dhcp4"])
gateway4, netmask4, dhcp4 = network_size(
data["subnet"], data["dhcp4"]
)
if data["subnet6"]:
ipv6 = True
gateway6, prefix6, dhcp6 = network_size(data["subnet6"], data["dhcp6"])
gateway6, prefix6, dhcp6 = network_size(
data["subnet6"], data["dhcp6"]
)
if prefix6 != "64":
messages.error(
request, _("For libvirt, the IPv6 network prefix must be /64")
request,
_("For libvirt, the IPv6 network prefix must be /64"),
)
errors = True
if not errors:
@ -177,7 +185,11 @@ def network(request, compute_id, pool):
try:
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())
except libvirtError as lib_err:
messages.error(request, lib_err)
@ -187,7 +199,10 @@ def network(request, compute_id, pool):
ip = request.POST.get("address", "")
family = request.POST.get("family", "ipv4")
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())
if "modify_dhcp_range" in request.POST:
range_start = request.POST.get("range_start", "")
@ -195,7 +210,10 @@ def network(request, compute_id, pool):
family = request.POST.get("family", "ipv4")
try:
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())
except libvirtError as lib_err:
messages.error(request, lib_err)
@ -225,10 +243,16 @@ def network(request, compute_id, pool):
if conn.is_active():
messages.success(
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:
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:
messages.error(request, lib_err)
return HttpResponseRedirect(request.get_full_path())
@ -239,11 +263,17 @@ def network(request, compute_id, pool):
if conn.is_active():
messages.success(
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:
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())
conn.close()

View file

@ -23,7 +23,12 @@ def nwfilters(request, compute_id):
compute = get_object_or_404(Compute, pk=compute_id)
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():
nwfilters_all.append(conn.get_nwfilter_info(nwf))
@ -41,19 +46,27 @@ def nwfilters(request, compute_id):
for nwf in nwfilters_all:
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)
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)
else:
try:
msg = _("%(filter)s network filter is created") % {"filter": name}
msg = _("%(filter)s network filter is created") % {
"filter": name
}
conn.create_nwfilter(xml)
addlogmsg(request.user.username, compute.hostname, "", msg)
except libvirtError as 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:
name = request.POST.get("nwfiltername", "")
@ -63,18 +76,27 @@ def nwfilters(request, compute_id):
nwfilter_info = conn.get_nwfilter_info(name)
is_conn = wvmInstances(
compute.hostname, compute.login, compute.password, compute.type
compute.hostname,
compute.login,
compute.password,
compute.type
)
instances = is_conn.get_instances()
for inst in instances:
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()
if name in dom_filterrefs:
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)
addlogmsg(request.user.username, compute.hostname, "", msg)
i_conn.close()
@ -93,7 +115,10 @@ def nwfilters(request, compute_id):
conn.clone_nwfilter(name, 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)
conn.close()
@ -126,9 +151,18 @@ def nwfilter(request, compute_id, nwfltr):
try:
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():
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):
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)
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)
ceph_user = forms.CharField(required=False)
ceph_host = forms.CharField(required=False)
@ -17,49 +23,62 @@ class AddStgPool(forms.Form):
source_format = forms.CharField(required=False)
def clean_name(self):
name = self.cleaned_data['name']
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
name = self.cleaned_data["name"]
have_symbol = re.match("^[a-zA-Z0-9._-]+$", name)
if not have_symbol:
raise forms.ValidationError(_('The pool name must not contain any special characters'))
raise forms.ValidationError(
_("The pool name must not contain any special characters")
)
elif len(name) > 20:
raise forms.ValidationError(_('The pool name must not exceed 20 characters'))
raise forms.ValidationError(
_("The pool name must not exceed 20 characters")
)
return name
def clean_target(self):
storage_type = self.cleaned_data['stg_type']
target = self.cleaned_data['target']
have_symbol = re.match('^[a-zA-Z0-9/]+$', target)
if storage_type == 'dir' or storage_type == 'netfs':
storage_type = self.cleaned_data["stg_type"]
target = self.cleaned_data["target"]
have_symbol = re.match("^[a-zA-Z0-9/]+$", target)
if storage_type == "dir" or storage_type == "netfs":
if not have_symbol:
raise forms.ValidationError(_('The target must not contain any special characters'))
if storage_type == 'dir' or storage_type == 'netfs':
raise forms.ValidationError(
_("The target must not contain any special characters")
)
if storage_type == "dir" or storage_type == "netfs":
if not target:
raise forms.ValidationError(_('No path has been entered'))
raise forms.ValidationError(_("No path has been entered"))
return target
def clean_source(self):
storage_type = self.cleaned_data['stg_type']
source = self.cleaned_data['source']
have_symbol = re.match('^[a-zA-Z0-9\/]+$', source)
if storage_type == 'logical' or storage_type == 'netfs':
storage_type = self.cleaned_data["stg_type"]
source = self.cleaned_data["source"]
have_symbol = re.match("^[a-zA-Z0-9\/]+$", source)
if storage_type == "logical" or storage_type == "netfs":
if not source:
raise forms.ValidationError(_('No device or path has been entered'))
raise forms.ValidationError(_("No device or path has been entered"))
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
class CreateVolumeForm(forms.Form):
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()
meta_prealloc = forms.BooleanField(required=False)
def clean_name(self):
name = self.cleaned_data['name']
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
name = self.cleaned_data["name"]
have_symbol = re.match("^[a-zA-Z0-9._-]+$", name)
if not have_symbol:
raise forms.ValidationError(_('The image name must not contain any special characters'))
raise forms.ValidationError(
_("The image name must not contain any special characters")
)
return name
@ -67,14 +86,21 @@ class CloneImage(forms.Form):
name = forms.CharField(max_length=120)
image = forms.CharField(max_length=120)
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)
def clean_name(self):
name = self.cleaned_data['name']
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
name = self.cleaned_data["name"]
have_symbol = re.match("^[a-zA-Z0-9._-]+$", name)
if not have_symbol:
raise forms.ValidationError(_('The image name must not contain any special characters'))
raise forms.ValidationError(
_("The image name must not contain any special characters")
)
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

View file

@ -3,45 +3,55 @@ from django.utils.translation import gettext_lazy as _
# Create your models here.
class Storages(models.Model):
name = models.CharField(_('name'), max_length=20, error_messages={'required': _('No pool name has been entered')})
status = models.IntegerField(_('status'))
type = models.CharField(_('type'), max_length=100)
size = models.IntegerField(_('size'))
volumes = models.IntegerField(_('volumes'))
name = models.CharField(
_("name"),
max_length=20,
error_messages={"required": _("No pool name has been entered")},
)
status = models.IntegerField(_("status"))
type = models.CharField(_("type"), max_length=100)
size = models.IntegerField(_("size"))
volumes = models.IntegerField(_("volumes"))
class Meta:
managed = False
def __str__(self):
return f'{self.name}'
return f"{self.name}"
class Volume(models.Model):
name = models.CharField(_('name'), max_length=128)
type = models.CharField(_('format'), max_length=12, choices=(('qcow2', 'qcow2 (recommended)'), ('qcow', 'qcow'), ('raw', 'raw')))
allocation = models.IntegerField(_('allocation'))
size = models.IntegerField(_('size'))
name = models.CharField(_("name"), max_length=128)
type = models.CharField(
_("format"),
max_length=12,
choices=(("qcow2", "qcow2 (recommended)"), ("qcow", "qcow"), ("raw", "raw")),
)
allocation = models.IntegerField(_("allocation"))
size = models.IntegerField(_("size"))
class Meta:
managed = False
verbose_name_plural = "Volumes"
def __str__(self):
return f'{self.name}'
return f"{self.name}"
class Storage(models.Model):
state = models.IntegerField(_('state'))
size = models.IntegerField(_('size'))
free = models.IntegerField(_('free'))
status = models.CharField(_('status'), max_length=128)
path = models.CharField(_('path'), max_length=128)
type = models.CharField(_('type'), max_length=128)
autostart = models.BooleanField(_('autostart'))
volumes = models.ForeignKey(Volume, related_name="storage_volumes", on_delete=models.DO_NOTHING)
state = models.IntegerField(_("state"))
size = models.IntegerField(_("size"))
free = models.IntegerField(_("free"))
status = models.CharField(_("status"), max_length=128)
path = models.CharField(_("path"), max_length=128)
type = models.CharField(_("type"), max_length=128)
autostart = models.BooleanField(_("autostart"))
volumes = models.ForeignKey(
Volume, related_name="storage_volumes", on_delete=models.DO_NOTHING
)
class Meta:
managed = False
def __str__(self):
return f'{self.path}'
return f"{self.path}"

View file

@ -27,7 +27,12 @@ def storages(request, compute_id):
errors = False
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()
secrets = conn.get_secrets()
@ -45,7 +50,11 @@ def storages(request, compute_id):
msg = _("You need create secret for pool")
messages.error(request, msg)
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")
messages.error(request, msg)
errors = True
@ -69,8 +78,15 @@ def storages(request, compute_id):
data["target"],
)
else:
conn.create_storage(data["stg_type"], data["name"], data["source"], data["target"])
return HttpResponseRedirect(reverse("storage", args=[compute_id, data["name"]]))
conn.create_storage(
data["stg_type"],
data["name"],
data["source"],
data["target"],
)
return HttpResponseRedirect(
reverse("storage", args=[compute_id, data["name"]])
)
else:
for msg_err in form.errors.values():
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)))
if not target.startswith(path):
raise Exception(_("Security Issues with file uploading"))
try:
with open(target, "wb+") as f:
for chunk in f_name.chunks():
f.write(chunk)
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)
meta_prealloc = False
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()
state = conn.is_active()
@ -147,7 +170,9 @@ def storage(request, compute_id, pool):
volname = request.POST.get("volname", "")
vol = conn.get_volume(volname)
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 HttpResponseRedirect(request.get_full_path())
if "iso_upload" in request.POST:
@ -156,7 +181,10 @@ def storage(request, compute_id, pool):
messages.error(request, error_msg)
else:
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())
if "cln_volume" in request.POST:
form = CloneImage(request.POST)
@ -174,10 +202,13 @@ def storage(request, compute_id, pool):
else:
format = None
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(
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())
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)
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()
@ -223,7 +260,9 @@ def create_volume(request, compute_id, pool):
disk_owner_uid,
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:
for msg_err in form.errors.values():
messages.error(request, msg_err.as_text())
@ -241,7 +280,13 @@ def get_volumes(request, compute_id, pool):
data = {}
compute = get_object_or_404(Compute, pk=compute_id)
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()
data["vols"] = sorted(conn.get_volumes())
except libvirtError:

View file

@ -2,7 +2,10 @@ from django import forms
class AddSecret(forms.Form):
ephemeral = forms.ChoiceField(required=True, choices=(('no', 'no'), ('yes', 'yes')))
private = forms.ChoiceField(required=True, choices=(('no', 'no'), ('yes', 'yes')))
usage_type = forms.ChoiceField(required=True, choices=(('ceph', 'ceph'), ('volume', 'volume'), ('iscsi', 'iscsi')))
ephemeral = forms.ChoiceField(required=True, choices=(("no", "no"), ("yes", "yes")))
private = forms.ChoiceField(required=True, choices=(("no", "no"), ("yes", "yes")))
usage_type = forms.ChoiceField(
required=True,
choices=(("ceph", "ceph"), ("volume", "volume"), ("iscsi", "iscsi")),
)
data = forms.CharField(max_length=100, required=True)

View file

@ -35,7 +35,12 @@ def secrets(request, compute_id):
}
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()
for uuid in secrets:

View file

@ -42,8 +42,7 @@ class wvmHostDetails(wvmConnect):
total = sum(self.wvm.getCPUStats(-1, 0).values())
diff_idle = idle - prev_idle
diff_total = total - prev_total
diff_usage = (1000 * (diff_total - diff_idle) /
diff_total + 5) / 10
diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10
prev_total = total
prev_idle = idle
if num == 0:
@ -61,6 +60,8 @@ class wvmHostDetails(wvmConnect):
info.append(self.wvm.getInfo()[0]) # architecture
info.append(self.wvm.getInfo()[1] * 1048576) # memory
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
return info

View file

@ -21,10 +21,7 @@ try:
VIR_MIGRATE_POSTCOPY,
)
from libvirt import VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT
from libvirt_qemu import (
qemuAgentCommand,
VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT
)
from libvirt_qemu import qemuAgentCommand, VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT
except:
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE
@ -94,7 +91,18 @@ class wvmInstances(wvmConnect):
dom = self.get_instance(name)
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
if live and conn.get_status() != 5:
flags |= VIR_MIGRATE_LIVE
@ -117,29 +125,39 @@ class wvmInstances(wvmConnect):
dom_emulator = conn.get_dom_emulator()
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)
def graphics_type(self, 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:
return "None"
return console_type
def graphics_listen(self, 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:
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:
return "None"
return listener_addr
def graphics_port(self, 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:
return "None"
return console_port
@ -153,7 +171,9 @@ class wvmInstances(wvmConnect):
def graphics_passwd(self, 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:
return "None"
return password
@ -167,27 +187,30 @@ class wvmInstance(wvmConnect):
def osinfo(self):
info_results = qemuAgentCommand(
self.instance,
'{"execute":"guest-get-osinfo"}',
VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT, 0
)
self.instance,
'{"execute":"guest-get-osinfo"}',
VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT,
0,
)
timezone_results = qemuAgentCommand(
self.instance,
'{"execute":"guest-get-timezone"}',
VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT, 0
)
self.instance,
'{"execute":"guest-get-timezone"}',
VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT,
0,
)
hostname_results = qemuAgentCommand(
self.instance,
'{"execute":"guest-get-host-name"}',
VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT, 0
)
self.instance,
'{"execute":"guest-get-host-name"}',
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')
hostname_results = json.loads(hostname_results).get('return')
timezone_results = json.loads(timezone_results).get("return")
hostname_results = json.loads(hostname_results).get("return")
info_results.update(timezone_results)
info_results.update(hostname_results)
@ -283,7 +306,11 @@ class wvmInstance(wvmConnect):
enabled = vcpu.get("enabled")
hotplug = vcpu.get("hotpluggable")
order = vcpu.get("order")
vcpus[vcpu_id] = {"enabled": enabled, "hotpluggable": hotplug, "order": order}
vcpus[vcpu_id] = {
"enabled": enabled,
"hotpluggable": hotplug,
"order": order,
}
return vcpus
@ -382,7 +409,9 @@ class wvmInstance(wvmConnect):
return
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"
self._ip_cache["arp"] = self._get_interface_addresses(arp_flag)
@ -395,9 +424,17 @@ class wvmInstance(wvmConnect):
interface_type = net.xpath("@type")[0]
mac_inst = net.xpath("mac/@address")[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]
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]
target_inst = (
"" if not net.xpath("target/@dev") else net.xpath("target/@dev")[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]
if net.xpath("bandwidth/inbound"):
in_attr = net.xpath("bandwidth/inbound")[0]
@ -451,7 +488,9 @@ class wvmInstance(wvmConnect):
dev = disk.xpath("target/@dev")[0]
bus = disk.xpath("target/@bus")[0]
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:
v = disk.xpath("source/@volume")[0]
s_name = disk.xpath("source/@pool")[0]
@ -480,7 +519,11 @@ class wvmInstance(wvmConnect):
readonly = True if disk.xpath("readonly") 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:
vol = self.get_volume_by_path(src_file)
@ -541,7 +584,15 @@ class wvmInstance(wvmConnect):
except:
pass
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 util.get_xml_path(self._XMLDesc(0), func=disks)
@ -567,7 +618,9 @@ class wvmInstance(wvmConnect):
elif flag == -1: # Remove
os.remove(menu)
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()
self._defineXML(xmldom)
@ -614,7 +667,11 @@ class wvmInstance(wvmConnect):
elif dev_type == "usb":
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
@ -716,7 +773,7 @@ class wvmInstance(wvmConnect):
self,
target_dev,
source,
source_info = None,
source_info=None,
pool_type="dir",
target_bus="ide",
disk_type="file",
@ -733,7 +790,11 @@ class wvmInstance(wvmConnect):
):
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}' "
if io_mode is not None and io_mode != "default":
additionals += f"io='{io_mode}' "
@ -746,31 +807,33 @@ class wvmInstance(wvmConnect):
if disk_device == "cdrom":
xml_disk += f"<driver name='{driver_name}' type='{format_type}'/>"
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}'/>"
elif disk_type == 'network':
if pool_type == 'rbd':
auth_type = source_info.get('auth_type')
auth_user = source_info.get('auth_user')
elif disk_type == "network":
if pool_type == "rbd":
auth_type = source_info.get("auth_type")
auth_user = source_info.get("auth_user")
auth_uuid = source_info.get("auth_uuid")
xml_disk += f"""<auth username='{auth_user}'>
<secret type='{auth_type}' uuid='{auth_uuid}'/>
</auth>"""
xml_disk += f"""<source protocol='{pool_type}' name='{source}'>"""
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')}'/>"""
else:
xml_disk += f"""<host name="{host.get('hostname')}"/>"""
xml_disk +="""</source>"""
xml_disk += """</source>"""
else:
raise Exception("Not implemented disk type")
else:
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":
xml_disk += """<readonly/>"""
if shareable:
@ -787,7 +850,9 @@ class wvmInstance(wvmConnect):
def detach_disk(self, target_dev):
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()
devices = tree.find("devices")
devices.remove(disk_el)
@ -813,7 +878,9 @@ class wvmInstance(wvmConnect):
detect_zeroes_mode,
):
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_device = disk_el.get("device")
old_driver_name = disk_el.xpath("driver/@name")[0]
@ -833,7 +900,9 @@ class wvmInstance(wvmConnect):
if old_disk_device == "cdrom":
xml_disk += f"<driver name='{old_driver_name}' type='{format}'/>"
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}'/>
<target dev='{target_dev}' bus='{target_bus}'/>"""
@ -855,7 +924,7 @@ class wvmInstance(wvmConnect):
time.sleep(1)
cpu_use_now = self.instance.info()[4]
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:
cpu_usage["cpu"] = 0
return cpu_usage
@ -864,7 +933,7 @@ class wvmInstance(wvmConnect):
self.instance.setVcpu(str(cpu_id), enabled)
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
if self.get_status() == 5: # shutoff
if status:
@ -887,7 +956,9 @@ class wvmInstance(wvmConnect):
parent.remove(vcpu)
self._defineXML(etree.tostring(tree).decode())
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):
mem_usage = {}
@ -983,9 +1054,13 @@ class wvmInstance(wvmConnect):
return telnet_port
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:
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:
return "127.0.0.1"
return listener_addr
@ -1021,9 +1096,13 @@ class wvmInstance(wvmConnect):
return socket
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:
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
def set_console_type(self, console_type):
@ -1046,18 +1125,24 @@ class wvmInstance(wvmConnect):
def get_console_port(self, console_type=None):
if console_type is None:
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
def get_console_websocket_port(self):
console_type = self.get_console_type()
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
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):
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
@ -1100,10 +1185,15 @@ class wvmInstance(wvmConnect):
self._defineXML(newxml)
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):
""" :return only primary video card"""
""":return only primary video card"""
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
tree = etree.fromstring(xml)
video_models = tree.xpath("/domain/devices/video/model")
@ -1112,7 +1202,7 @@ class wvmInstance(wvmConnect):
return model.get("type")
def set_video_model(self, model):
""" Changes only primary video card"""
"""Changes only primary video card"""
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
tree = etree.fromstring(xml)
video_models = tree.xpath("/domain/devices/video/model")
@ -1208,7 +1298,7 @@ class wvmInstance(wvmConnect):
self.instance.snapshotCreateXML(xml, flag)
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>
<name>%s</name>
<description>%s</description>
@ -1227,11 +1317,17 @@ class wvmInstance(wvmConnect):
self.recover_snapshot_xml()
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)
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)
def get_snapshot(self):
@ -1239,8 +1335,12 @@ class wvmInstance(wvmConnect):
snapshot_list = self.instance.snapshotListNames(0)
for snapshot in snapshot_list:
snap = self.instance.snapshotLookupByName(snapshot, 0)
snap_description = util.get_xml_path(snap.getXMLDesc(0), "/domainsnapshot/description")
snap_time_create = util.get_xml_path(snap.getXMLDesc(0), "/domainsnapshot/creationTime")
snap_description = util.get_xml_path(
snap.getXMLDesc(0), "/domainsnapshot/description"
)
snap_time_create = util.get_xml_path(
snap.getXMLDesc(0), "/domainsnapshot/creationTime"
)
snapshots.append(
{
"date": datetime.fromtimestamp(int(snap_time_create)),
@ -1338,7 +1438,9 @@ class wvmInstance(wvmConnect):
elm.set("file", clone_path)
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:
meta_prealloc = True
@ -1373,7 +1475,9 @@ class wvmInstance(wvmConnect):
elm.set("name", clone_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"""
<volume type='network'>
@ -1417,15 +1521,22 @@ class wvmInstance(wvmConnect):
bridge_name = None
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":
interface_type = "network"
elif source_type == "bridge":
interface_type = "bridge"
else:
interface_type = "direct"
# network modes not handled: default is bridge
xml_iface = f"""
@ -1471,12 +1582,18 @@ class wvmInstance(wvmConnect):
net_source_type = network_data.get("net-source-0-type")
net_filter = network_data.get("net-nwfilter-0")
net_model = network_data.get("net-model-0")
# 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.
status = self.delete_network(net_mac)
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:
if status is not None:
if self.get_status() == 1:
@ -1485,12 +1602,11 @@ class wvmInstance(wvmConnect):
if self.get_status() == 5:
self.instance.attachDeviceFlags(status, VIR_DOMAIN_AFFECT_CONFIG)
def change_network_oldway(self, network_data):
'''
change network firsh version...
will be removed if new one works as expected for all scenarios
'''
"""
change network firsh version...
will be removed if new one works as expected for all scenarios
"""
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
tree = ElementTree.fromstring(xml)
for num, interface in enumerate(tree.findall("devices/interface")):
@ -1512,9 +1628,13 @@ class wvmInstance(wvmConnect):
elif net_source_type == "iface":
source.set("dev", net_source)
else:
raise libvirtError("Unknown network type: {}".format(net_source_type))
raise libvirtError(
"Unknown network type: {}".format(net_source_type)
)
else:
raise libvirtError("Unknown network type: {}".format(interface.get("type")))
raise libvirtError(
"Unknown network type: {}".format(interface.get("type"))
)
source = interface.find("model")
if net_model != "default":
@ -1615,7 +1735,12 @@ class wvmInstance(wvmConnect):
out_peak = out_qos.get("peak")
out_burst = out_qos.get("burst")
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
return qos_values
@ -1652,9 +1777,13 @@ class wvmInstance(wvmConnect):
def unset_qos(self, mac, direction):
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()
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")
if parent_mac[0] == mac:
band_el.remove(direct)
@ -1675,7 +1804,9 @@ class wvmInstance(wvmConnect):
def remove_guest_agent(self):
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()
channel_xml = etree.tostring(parent).decode()
if self.get_status() == 1:

View file

@ -88,7 +88,8 @@ class wvmNWFilter(wvmConnect):
tree = ElementTree.fromstring(self._XMLDesc(0))
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:
tree.remove(rule_tree[0])
@ -111,7 +112,8 @@ class wvmNWFilter(wvmConnect):
rule_priority = rule.get("priority")
rule_directives = rule.find("./")
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:

View file

@ -15,7 +15,13 @@ class wvmStorages(wvmConnect):
stg_vol = len(stg.listVolumes()) if stg_status else None
stg_size = stg.info()[1]
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
@ -47,7 +53,9 @@ class wvmStorages(wvmConnect):
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"""
<pool type='{stg_type}'>
<name>{name}</name>
@ -65,7 +73,9 @@ class wvmStorages(wvmConnect):
stg.create(0)
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"""
<pool type='{stg_type}'>
<name>{name}</name>
@ -96,7 +106,12 @@ class wvmStorage(wvmConnect):
return self.pool.name()
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:
return status[self.pool.info()[0]]
except ValueError:
@ -161,27 +176,28 @@ class wvmStorage(wvmConnect):
hosts_array = []
for host in doc.xpath("/pool/source/host"):
name = host.get('name')
name = host.get("name")
if name:
port = host.get('port')
port = host.get("port")
if port:
hosts_array.append({"hostname": name, "hostport": port})
else:
hosts_array.append({"hostname": name})
name = doc.get('name')
name = doc.get("name")
auth = doc.xpath("/pool/source/auth")
auth_type = auth[0].get("type")
auth_user = auth[0].get("username")
auth_uuid = auth[0].xpath("secret/@uuid")[0]
return({
return {
"name": name,
"auth_type": auth_type,
"auth_user": auth_user,
"auth_uuid": auth_uuid,
"hosts": hosts_array
})
"hosts": hosts_array,
}
return util.get_xml_path(self._XMLDesc(0), func=hosts)
def get_pretty_allocation(self):
@ -231,30 +247,49 @@ class wvmStorage(wvmConnect):
self.refresh()
vols = self.get_volumes()
return [{"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 get_volume_details(self, volname):
with contextlib.suppress(Exception):
self.refresh()
return {
return [
{
"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 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):
with contextlib.suppress(Exception):
self.refresh()
vols = self.get_volumes()
return [{"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]
return [
{
"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
storage_type = self.get_type()
alloc = size

View file

@ -273,6 +273,14 @@ OTP_ENABLED = False
LOGIN_REQUIRED_IGNORE_VIEW_NAMES = ["accounts:login", "accounts:email_otp"]
# EMAIL Config
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST_USER = #sender's email-id
EMAIL_HOST_PASSWORD = #password associated with above email-id
# LDAP Config
#