initial
This commit is contained in:
0
proxmox/__init__.py
Normal file
0
proxmox/__init__.py
Normal file
79
proxmox/admin.py
Normal file
79
proxmox/admin.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import logging
|
||||
|
||||
from django.contrib import admin
|
||||
from django.db.models import Q
|
||||
|
||||
from lib.decorators import readonly
|
||||
from lib.proxmox import Proxmox
|
||||
from proxmox.models import Lxc, LxcTemplate
|
||||
|
||||
|
||||
@readonly
|
||||
def sync_all_lxc_templates_from_proxmox(*args, **kwargs):
|
||||
pm = Proxmox()
|
||||
for storage in pm.storage_get(enabled=1):
|
||||
logging.debug(f'Syncing Templates from storage {storage["storage"]}')
|
||||
storage_name = storage['storage']
|
||||
for tmpl in pm.storage(f'{storage_name}/content').get(content='vztmpl'):
|
||||
try:
|
||||
logging.debug(f'Updating {tmpl["volid"]}')
|
||||
template = LxcTemplate.objects.get(volid=tmpl['volid'])
|
||||
except LxcTemplate.DoesNotExist:
|
||||
logging.debug(f'Fail - Creating {tmpl["volid"]}')
|
||||
template = LxcTemplate.objects.create(volid=tmpl['volid'])
|
||||
|
||||
template.write(**tmpl)
|
||||
|
||||
|
||||
@readonly
|
||||
def sync_all_lxc_from_proxmox(*args, **kwargs):
|
||||
from lib.proxmox import Proxmox
|
||||
pm = Proxmox()
|
||||
existing_vms = []
|
||||
for lxc_data in pm.get_all_lxc(as_dict=True, **kwargs):
|
||||
vmid = lxc_data.pop('vmid')
|
||||
try:
|
||||
lx: Lxc = Lxc.objects.get(vmid=vmid)
|
||||
logging.info(f'Updating {vmid}')
|
||||
except Lxc.DoesNotExist:
|
||||
logging.info(f'Creating {vmid}')
|
||||
lx = Lxc.objects.create(**{'vmid': vmid})
|
||||
|
||||
lx.from_proxmox(**lxc_data)
|
||||
|
||||
existing_vms.append(vmid)
|
||||
|
||||
to_delete = Lxc.objects.filter(~Q(vmid__in=existing_vms))
|
||||
if to_delete:
|
||||
logging.info(f'Deleting {[d.vmid for d in to_delete]}')
|
||||
to_delete.delete()
|
||||
|
||||
|
||||
sync_all_lxc_from_proxmox.short_description = 'Sync all LXC from Proxmox'
|
||||
|
||||
|
||||
@admin.register(Lxc)
|
||||
class LxcAdmin(admin.ModelAdmin):
|
||||
actions = [sync_all_lxc_from_proxmox, 'sync_selected_from_proxmox']
|
||||
search_fields = ('name', 'vmid', 'hostname', 'hwaddr')
|
||||
list_display = ('name', 'vmid', 'hwaddr', 'disksize', 'memory', 'cpus', 'status')
|
||||
list_filter = ('status', 'disksize',)
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
if obj:
|
||||
return [k.name for k in obj._meta.fields if
|
||||
k.name not in ['name', 'cores', 'hwaddr', 'size', 'cpus', 'memory', 'description', 'hostname']]
|
||||
return self.readonly_fields
|
||||
|
||||
@admin.action(description='Sync selected from proxmox')
|
||||
@readonly
|
||||
def sync_selected_from_proxmox(self, request, queryset):
|
||||
for lx in queryset:
|
||||
lx.sync_from_proxmox()
|
||||
|
||||
|
||||
@admin.register(LxcTemplate)
|
||||
class LxcTemplateAdmin(admin.ModelAdmin):
|
||||
actions = [sync_all_lxc_templates_from_proxmox]
|
||||
search_fields = ('volid',)
|
||||
list_display = ('volid', 'human_size', 'content')
|
||||
6
proxmox/apps.py
Normal file
6
proxmox/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ProxmoxConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'proxmox'
|
||||
80
proxmox/migrations/0001_initial.py
Normal file
80
proxmox/migrations/0001_initial.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-07 11:19
|
||||
|
||||
import django.core.validators
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Lxc',
|
||||
fields=[
|
||||
('internal_id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('vmid', models.IntegerField(blank=True, null=True, unique=True)),
|
||||
('name', models.CharField(blank=True, default='', max_length=150, null=True, verbose_name='Container Name')),
|
||||
('hostname', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('hwaddr', models.CharField(blank=True, default=uuid.uuid4, max_length=150, null=True, unique=True)),
|
||||
('size', models.CharField(blank=True, max_length=4, null=True)),
|
||||
('cores', models.IntegerField(blank=True, default=1, null=True)),
|
||||
('memory', models.IntegerField(default=512, help_text='in MB', validators=[django.core.validators.MinValueValidator(128)])),
|
||||
('disksize', models.IntegerField(default=12, help_text='in GB', validators=[django.core.validators.MinValueValidator(8)])),
|
||||
('swap', models.IntegerField(blank=True, null=True)),
|
||||
('description', models.TextField(blank=True, default='', null=True)),
|
||||
('cpus', models.IntegerField(blank=True, default=1, null=True, validators=[django.core.validators.MinValueValidator(1)])),
|
||||
('uptime', models.IntegerField(blank=True, null=True)),
|
||||
('maxswap', models.IntegerField(blank=True, null=True)),
|
||||
('cpu', models.IntegerField(blank=True, null=True)),
|
||||
('disk', models.IntegerField(blank=True, null=True)),
|
||||
('netout', models.IntegerField(blank=True, null=True)),
|
||||
('diskwrite', models.IntegerField(blank=True, null=True)),
|
||||
('diskread', models.IntegerField(blank=True, null=True)),
|
||||
('pid', models.IntegerField(blank=True, null=True)),
|
||||
('maxdisk', models.IntegerField(blank=True, null=True)),
|
||||
('mem', models.IntegerField(blank=True, null=True)),
|
||||
('maxmem', models.IntegerField(blank=True, null=True)),
|
||||
('netin', models.IntegerField(blank=True, null=True)),
|
||||
('status', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('type', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('onboot', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('nameserver', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('digest', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('rootfs', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('arch', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('ostype', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('net0', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('features', models.CharField(blank=True, default='', max_length=250, null=True)),
|
||||
('snaptime', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('parent', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('tags', models.CharField(blank=True, default='', max_length=250, null=True)),
|
||||
('console', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('tty', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('searchdomain', models.CharField(blank=True, default='', max_length=150, null=True)),
|
||||
('unprivileged', models.CharField(blank=True, default='', max_length=10, null=True)),
|
||||
('lxc', models.CharField(blank=True, default='', max_length=10, null=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LxcTemplate',
|
||||
fields=[
|
||||
('internal_id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('volid', models.CharField(max_length=150, unique=True)),
|
||||
('ctime', models.IntegerField(default=0)),
|
||||
('size', models.IntegerField(default=0)),
|
||||
('format', models.CharField(max_length=10)),
|
||||
('content', models.CharField(default='tgz', max_length=10)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-08 11:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('proxmox', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='disk',
|
||||
field=models.CharField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='diskread',
|
||||
field=models.CharField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='diskwrite',
|
||||
field=models.CharField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='netout',
|
||||
field=models.CharField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='uptime',
|
||||
field=models.CharField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-08 11:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('proxmox', '0002_alter_lxc_disk_alter_lxc_diskread_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='disksize',
|
||||
field=models.IntegerField(default=12, help_text='in GB'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='memory',
|
||||
field=models.IntegerField(default=512, help_text='in MB'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,74 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-08 11:29
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('proxmox', '0003_alter_lxc_disksize_alter_lxc_memory'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='cores',
|
||||
field=models.BigIntegerField(blank=True, default=1, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='cpu',
|
||||
field=models.BigIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='cpus',
|
||||
field=models.BigIntegerField(blank=True, default=1, null=True, validators=[django.core.validators.MinValueValidator(1)]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='disksize',
|
||||
field=models.BigIntegerField(default=12, help_text='in GB'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='maxdisk',
|
||||
field=models.BigIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='maxmem',
|
||||
field=models.BigIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='maxswap',
|
||||
field=models.BigIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='mem',
|
||||
field=models.BigIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='memory',
|
||||
field=models.BigIntegerField(default=512, help_text='in MB'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='netin',
|
||||
field=models.BigIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='pid',
|
||||
field=models.BigIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='swap',
|
||||
field=models.BigIntegerField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
18
proxmox/migrations/0005_alter_lxc_lxc.py
Normal file
18
proxmox/migrations/0005_alter_lxc_lxc.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-08 14:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('proxmox', '0004_alter_lxc_cores_alter_lxc_cpu_alter_lxc_cpus_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='lxc',
|
||||
field=models.CharField(blank=True, default='', max_length=150, null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,36 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-21 11:03
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('proxmox', '0005_alter_lxc_lxc'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='lxc',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lxc',
|
||||
name='updated_at',
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lxctemplate',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lxctemplate',
|
||||
name='updated_at',
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
),
|
||||
]
|
||||
23
proxmox/migrations/0007_lxctemplate_net0_alter_lxc_net0.py
Normal file
23
proxmox/migrations/0007_lxctemplate_net0_alter_lxc_net0.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-22 13:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('proxmox', '0006_lxc_created_at_lxc_updated_at_lxctemplate_created_at_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='lxctemplate',
|
||||
name='net0',
|
||||
field=models.CharField(blank=True, default='name=eth0,bridge=vmbr0,firewall=0,ip=dhcp', max_length=150, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lxc',
|
||||
name='net0',
|
||||
field=models.CharField(blank=True, default='name=eth0,bridge=vmbr0,firewall=0,ip=dhcp', max_length=150, null=True),
|
||||
),
|
||||
]
|
||||
0
proxmox/migrations/__init__.py
Normal file
0
proxmox/migrations/__init__.py
Normal file
354
proxmox/models.py
Normal file
354
proxmox/models.py
Normal file
@@ -0,0 +1,354 @@
|
||||
import logging
|
||||
import re
|
||||
from uuid import uuid4
|
||||
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.db import models
|
||||
from django.db.models.signals import pre_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from django_proxmox_mikrotik.configs import ProxmoxConfig
|
||||
from lib import human_size
|
||||
from lib.db import BaseModel, TaskAwareModelMixin
|
||||
from lib.decorators import skip_signal
|
||||
from lib.proxmox import Proxmox, get_comma_separated_values
|
||||
from manager.models import DevContainer
|
||||
|
||||
no_int_re = re.compile(r'[^\d]')
|
||||
|
||||
|
||||
class ProxmoxAbstractModel(BaseModel, TaskAwareModelMixin):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@property
|
||||
def change_map(self) -> dict:
|
||||
raise NotImplemented('@property change_map" not implemented')
|
||||
|
||||
@property
|
||||
def comma_separated_values(self) -> list:
|
||||
"""A list of fields that are comma separated values (X=1,Y=2,...
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def no_csv_overwrite(self) -> list:
|
||||
"""Maybe a value within a csv options
|
||||
has the same name than a non-csv option.
|
||||
Ommit those"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def non_proxmox_values(self) -> list:
|
||||
"""A list of fields that are not in proxmox, but in db
|
||||
Ao so not sync directly (maybe as csv)"""
|
||||
return []
|
||||
|
||||
def to_proxmox(self):
|
||||
raise NotImplemented('Not implemented')
|
||||
|
||||
def sync_from_proxmox(self):
|
||||
raise NotImplemented('Not implemented')
|
||||
|
||||
@property
|
||||
def csv_field_map(self):
|
||||
return {}
|
||||
|
||||
@property
|
||||
def int_fields(self):
|
||||
return []
|
||||
|
||||
def from_proxmox(self, **kwargs):
|
||||
"""Sets the values in DB
|
||||
Extracts csv Values into self, if given"""
|
||||
logging.debug(f"Got {kwargs} from proxmox")
|
||||
kwkeys = list(kwargs.keys())
|
||||
params = {}
|
||||
for k in kwkeys:
|
||||
logging.info(f"Process {k} with value {kwargs[k]}")
|
||||
v = kwargs[k]
|
||||
if not hasattr(self, k):
|
||||
continue
|
||||
if k in self.comma_separated_values:
|
||||
_vals = get_comma_separated_values(v)
|
||||
logging.debug(f"Got {_vals}")
|
||||
for _k, _v in _vals.items():
|
||||
if _k in self.no_csv_overwrite:
|
||||
logging.debug(f"{_k} is in no_csv_overwrite - omitting")
|
||||
continue
|
||||
csvkey, selfkey, csvfun = self.csv_field_map.get(k, (_k, _k, lambda x: x))
|
||||
|
||||
if hasattr(self, selfkey):
|
||||
if csvfun:
|
||||
_v = csvfun(_v)
|
||||
elif selfkey in self.int_fields:
|
||||
_v = int(no_int_re.sub('', _v) or 0)
|
||||
logging.debug(f"Update {selfkey} to {_v}")
|
||||
params[selfkey] = _v
|
||||
else:
|
||||
logging.info(f"{selfkey} not found in {type(self)}")
|
||||
else:
|
||||
if isinstance(getattr(self, k), models.IntegerField):
|
||||
v = no_int_re.sub('', _v) or 0
|
||||
params[k] = v
|
||||
logging.debug(f"No CSValues for {self}")
|
||||
return self.write(**params)
|
||||
|
||||
|
||||
class Lxc(ProxmoxAbstractModel):
|
||||
|
||||
@property
|
||||
def int_fields(self):
|
||||
return ['cores', 'memory', 'disksize']
|
||||
|
||||
@property
|
||||
def comma_separated_values(self):
|
||||
return ['net0', 'rootfs', 'features']
|
||||
|
||||
@property
|
||||
def no_csv_overwrite(self):
|
||||
return ['name']
|
||||
|
||||
@property
|
||||
def non_proxmox_values(self):
|
||||
return ['vmid', 'hwaddr', 'disksize']
|
||||
|
||||
@property
|
||||
def csv_field_map(self):
|
||||
return {
|
||||
'rootfs': ('size', 'disksize', lambda x: int(no_int_re.sub('', x) or 0)),
|
||||
}
|
||||
|
||||
@property
|
||||
def proxmox_console_url(self):
|
||||
return (
|
||||
f"https://{ProxmoxConfig.HOST}:8006/"
|
||||
f"?console=lxc&xtermjs=1&vmid={self.vmid}"
|
||||
f"&vmname={self.hostname}&node={ProxmoxConfig.NODE}&cmd="
|
||||
)
|
||||
|
||||
def delete(self, task=None):
|
||||
with Proxmox() as pm:
|
||||
try:
|
||||
if task:
|
||||
task.wrap_proxmox_function(self.stop)
|
||||
else:
|
||||
self.stop()
|
||||
except Exception as e:
|
||||
if 'running' in str(e):
|
||||
logging.info(f"Could not stop {self.vmid} - {e}")
|
||||
else:
|
||||
raise
|
||||
try:
|
||||
if task:
|
||||
task.wrap_proxmox_function(pm.lxc_delete, self.vmid, force=1, purge=1)
|
||||
else:
|
||||
result = pm.lxc_delete(self.vmid, force=1, purge=1)
|
||||
logging.info(f"Deleted {self.vmid} from proxmox - {result}")
|
||||
except Exception as e:
|
||||
logging.error(f"Could not delete {self.vmid} from proxmox", e)
|
||||
finally:
|
||||
super().delete()
|
||||
|
||||
vmid = models.IntegerField(null=True, blank=True, unique=True)
|
||||
name = models.CharField(max_length=150, null=True, blank=True, default='', verbose_name='Container Name')
|
||||
hostname = models.CharField(max_length=150, null=True, blank=True, default='', )
|
||||
# This one is from net0
|
||||
hwaddr = models.CharField(max_length=150, null=True, blank=True, default=uuid4, unique=True)
|
||||
# this comes from rootfs
|
||||
size = models.CharField(max_length=4, null=True, blank=True)
|
||||
cores = models.BigIntegerField(null=True, blank=True, default=1)
|
||||
memory = models.BigIntegerField(default=512, help_text='in MB', ) # validators=[MinValueValidator(128)])
|
||||
disksize = models.BigIntegerField(default=12, help_text='in GB', ) # validators=[MinValueValidator(8)])
|
||||
swap = models.BigIntegerField(null=True, blank=True)
|
||||
description = models.TextField(null=True, blank=True, default='')
|
||||
cpus = models.BigIntegerField(null=True, blank=True, validators=[MinValueValidator(1)], default=1)
|
||||
uptime = models.CharField(null=True, blank=True)
|
||||
maxswap = models.BigIntegerField(null=True, blank=True)
|
||||
cpu = models.BigIntegerField(null=True, blank=True)
|
||||
disk = models.CharField(null=True, blank=True)
|
||||
netout = models.CharField(null=True, blank=True)
|
||||
diskwrite = models.CharField(null=True, blank=True)
|
||||
diskread = models.CharField(null=True, blank=True)
|
||||
pid = models.BigIntegerField(null=True, blank=True)
|
||||
maxdisk = models.BigIntegerField(null=True, blank=True)
|
||||
mem = models.BigIntegerField(null=True, blank=True)
|
||||
maxmem = models.BigIntegerField(null=True, blank=True)
|
||||
netin = models.BigIntegerField(null=True, blank=True)
|
||||
status = models.CharField(max_length=150, null=True, blank=True, default='')
|
||||
type = models.CharField(max_length=150, null=True, blank=True, default='')
|
||||
onboot = models.CharField(max_length=150, null=True, blank=True, default='')
|
||||
nameserver = models.CharField(max_length=150, null=True, blank=True, default='')
|
||||
digest = models.CharField(max_length=150, null=True, blank=True, default='')
|
||||
rootfs = models.CharField(max_length=150, null=True, blank=True, default='')
|
||||
arch = models.CharField(max_length=150, null=True, blank=True, default='')
|
||||
ostype = models.CharField(max_length=150, null=True, blank=True, default='')
|
||||
net0 = models.CharField(max_length=150, null=True, blank=True, default='name=eth0,bridge=vmbr0,firewall=0,ip=dhcp')
|
||||
features = models.CharField(max_length=250, null=True, blank=True, default='')
|
||||
snaptime = models.CharField(max_length=150, null=True, blank=True, default='')
|
||||
parent = models.CharField(max_length=150, null=True, blank=True, default='')
|
||||
tags = models.CharField(max_length=250, null=True, blank=True, default='')
|
||||
console = models.CharField(max_length=150, null=True, blank=True, default='')
|
||||
tty = models.CharField(max_length=150, null=True, blank=True, default='')
|
||||
searchdomain = models.CharField(max_length=150, null=True, blank=True, default='')
|
||||
unprivileged = models.CharField(max_length=10, null=True, blank=True, default='')
|
||||
lxc = models.CharField(max_length=150, null=True, blank=True, default='')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name} ({self.vmid})'
|
||||
|
||||
def sync_from_proxmox(self):
|
||||
pm = Proxmox()
|
||||
try:
|
||||
data = pm.lxc_get(f'{self.vmid}/config')
|
||||
logging.debug(f"Got raw data '{data}' from proxmox")
|
||||
if not data:
|
||||
logging.warning(f'Could not find {self.vmid} in proxmox - deleting from local database!')
|
||||
return self.delete()
|
||||
self.from_proxmox(**data)
|
||||
super().save()
|
||||
except Exception as e:
|
||||
logging.error(f"Could not get config for {self.vmid} - {e}")
|
||||
return self
|
||||
|
||||
@property
|
||||
def _ch_disksize(self):
|
||||
if self._old_values['disksize'] == self.disksize:
|
||||
return False
|
||||
if self.disksize > 100:
|
||||
logging.warning(f'disksize is > 100')
|
||||
return False
|
||||
if self.disksize < self._old_values['disksize']:
|
||||
logging.warning(f"Can not shrink disksize")
|
||||
return False
|
||||
return True
|
||||
|
||||
def change_disksize(self):
|
||||
"""Just to disable some magick at the moment"""
|
||||
if self._ch_disksize:
|
||||
route = f'{self.vmid}/resize'
|
||||
args = {
|
||||
'disk': 'rootfs',
|
||||
'size': f'{self.disksize}G',
|
||||
}
|
||||
with Proxmox() as pm:
|
||||
try:
|
||||
result = pm.lxc_put(route, **args)
|
||||
logging.info(f"Changed disksize for container {self.vmid} to {self.disksize}G - {result}")
|
||||
logs = pm.get_task_status(taskhash=result)
|
||||
logging.debug(f"Tasklog for {self.vmid} is {logs}")
|
||||
except Exception as e:
|
||||
logging.error(f"Could not change disksize for container {self.vmid} to {self.disksize}G - {e}")
|
||||
return self
|
||||
|
||||
def change_memory(self):
|
||||
"""Just to disable some magick at the moment"""
|
||||
if self._old_values['memory'] != self.memory:
|
||||
route = f'{self.vmid}/config'
|
||||
args = {
|
||||
'memory': self.memory,
|
||||
}
|
||||
with Proxmox() as pm:
|
||||
try:
|
||||
result = pm.lxc_put(route, **args)
|
||||
logging.info(f"Changed memory for container {self.vmid} to {self.memory}MB - {result}")
|
||||
except Exception as e:
|
||||
self.memory = self._old_values['memory']
|
||||
super().save(update_fields=['memory'])
|
||||
logging.error(f"Could not change memory for container {self.vmid} to {self.memory}MB - {e}")
|
||||
return self
|
||||
|
||||
def change_cores(self):
|
||||
if self._old_values['cores'] != self.cores:
|
||||
logging.debug(f"Changing cores for {self.vmid} to {self.cores}")
|
||||
route = f'{self.vmid}/config'
|
||||
args = {
|
||||
'cores': self.cores,
|
||||
}
|
||||
with Proxmox() as pm:
|
||||
try:
|
||||
result = pm.lxc_put(route, **args)
|
||||
logging.info(f"Changed cores for container {self.vmid} to {self.cores} - {result}")
|
||||
return result
|
||||
except Exception as e:
|
||||
self.cores = self._old_values['cores']
|
||||
super().save(update_fields=['cores'])
|
||||
logging.error(f"Could not change cores for container {self.vmid} to {self.cores} - {e}")
|
||||
return None
|
||||
|
||||
def start(self):
|
||||
startresult = self._start_stop_actions('start')
|
||||
if startresult:
|
||||
self.status = 'running'
|
||||
return startresult
|
||||
|
||||
def stop(self):
|
||||
stopresult = self._start_stop_actions('stop')
|
||||
if stopresult:
|
||||
self.status = 'stopped'
|
||||
return stopresult
|
||||
|
||||
def reboot(self):
|
||||
rebootresult = self._start_stop_actions('reboot')
|
||||
if rebootresult:
|
||||
self.status = 'running'
|
||||
return rebootresult
|
||||
|
||||
def shutdown(self):
|
||||
shresult = self._start_stop_actions('shutdown')
|
||||
if shresult:
|
||||
self.status = 'stopped'
|
||||
return shresult
|
||||
|
||||
def _start_stop_actions(self, action):
|
||||
assert action in ('start', 'stop', 'shutdown', 'reboot')
|
||||
with Proxmox() as pm:
|
||||
try:
|
||||
result = pm.lxc_post(f'{self.vmid}/status/{action}')
|
||||
logging.info(f"{action}ed {self.vmid} - {result}")
|
||||
return result
|
||||
except Exception as e:
|
||||
logging.error(f"Could not {action} {self.vmid} - {e}")
|
||||
return False
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
logging.debug(f"Saving {self}")
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def to_proxmox(self):
|
||||
self.change_disksize()
|
||||
self.change_memory()
|
||||
self.change_cores()
|
||||
|
||||
|
||||
@receiver(pre_save, sender=Lxc)
|
||||
@skip_signal()
|
||||
def pre_save_lxc(sender, instance: Lxc, **kwargs):
|
||||
instance.hwaddr = str(instance.hwaddr or uuid4()).upper()
|
||||
if instance._state.adding:
|
||||
logging.info(f'Created {instance} via post_save event - do nothing, must be done via CloneContainer')
|
||||
return
|
||||
instance.change_disksize()
|
||||
instance.change_memory()
|
||||
instance.change_cores()
|
||||
|
||||
|
||||
class LxcTemplate(ProxmoxAbstractModel, TaskAwareModelMixin):
|
||||
volid = models.CharField(max_length=150, unique=True)
|
||||
ctime = models.IntegerField(default=0)
|
||||
size = models.IntegerField(default=0)
|
||||
format = models.CharField(max_length=10)
|
||||
content = models.CharField(max_length=10, default='tgz')
|
||||
net0 = models.CharField(max_length=150, null=True, blank=True, default='name=eth0,bridge=vmbr0,firewall=0,ip=dhcp')
|
||||
is_default_template = models.BooleanField(default=False,
|
||||
help_text='If true, this template will be used when creating new containers as default, or preselected')
|
||||
|
||||
def __str__(self):
|
||||
return self.volid.split('/')[-1]
|
||||
|
||||
def __repr__(self):
|
||||
return self.volid
|
||||
|
||||
@property
|
||||
def human_size(self):
|
||||
return human_size(self.size)
|
||||
3
proxmox/tests.py
Normal file
3
proxmox/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
proxmox/views.py
Normal file
3
proxmox/views.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
Reference in New Issue
Block a user