Commit d7c3c84c by Carpoon

VM import export funtionailty

VM import export funtionailty
Extend Export disk functions
parent 34920b70
{% load i18n %}
<form action="{% url "dashboard.views.template-import" %}" method="POST"
id="template-choose-form">
{% csrf_token %}
<div class="template-choose-list">
{% for t in templates %}
<div class="panel panel-default template-choose-list-element">
<input type="radio" name="parent" value="{{ t.pk }}"/>
{{ t.name }} - {{ t.system }}
<small>Cores: {{ t.num_cores }} RAM: {{ t.ram_size }}</small>
<div class="clearfix"></div>
</div>
{% endfor %}
<button type="submit" id="template-choose-next-button" class="btn btn-success pull-right">{% trans "Next" %}</button>
<div class="clearfix"></div>
</div>
</form>
<script>
$(function() {
$(".template-choose-list-element").click(function() {
$("input", $(this)).prop("checked", true);
});
$(".template-choose-list-element").hover(
function() {
$("small", $(this)).stop().fadeIn(200);
},
function() {
$("small", $(this)).stop().fadeOut(200);
}
);
});
</script>
......@@ -15,18 +15,13 @@
{% if op.export_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.export_disk.icon }} fa-fw-12"></i> {% trans "Export" %}
</a>
<a href="/image-dl/{{ d.filename }}.vmdk"
class="btn btn-xs btn-{{ op.export_disk.effect }} operation disk-export-btn">
<i class="fa fa-{{ op.export_disk.icon }} fa-fw-12"></i> {% trans "VMDK" %}
</a>
<a href="/image-dl/{{ d.filename }}.vdi"
class="btn btn-xs btn-{{ op.export_disk.effect }} operation disk-export-btn">
<i class="fa fa-{{ op.export_disk.icon }} fa-fw-12"></i> {% trans "VDI" %}
</a>
<a href="/image-dl/{{ d.filename }}.vdi"
{% for export in d.exporteddisk_set.all %}
<a href="/image-dl/{{ export.filename }}"
class="btn btn-xs btn-{{ op.export_disk.effect }} operation disk-export-btn">
<i class="fa fa-{{ op.export_disk.icon }} fa-fw-12"></i> {% trans "VPC" %}
<i class="fa fa-{{ op.export_disk.icon }} fa-fw-12"></i> {% trans export.format %}
</a>
{% endfor %}
{% endif %}
{% else %}
<small class="btn-xs">
......
......@@ -26,6 +26,12 @@
{% trans "Create a new base VM without disk" %}
</div>
{% endif %}
{% if perms.vm.import_template %}
<div class="panel panel-default template-choose-list-element">
<input type="radio" name="parent" value="import_vm"/>
{% trans "Import a VM" %}
</div>
{% endif %}
<button type="submit" id="template-choose-next-button" class="btn btn-success pull-right">{% trans "Next" %}</button>
<div class="clearfix"></div>
</div>
......
{% load crispy_forms_tags %}
{% load i18n %}
<p class="text-muted">
{% trans "Import a previously exported VM from the user store." %}
</p>
<p class="alert alert-info">
{% trans "Please don't forget to add network interfaces, as those won't be imported!" %}
</p>
<form method="POST" action="{% url "dashboard.views.template-import" %}">
{% csrf_token %}
{% crispy form %}
</form>
......@@ -231,6 +231,13 @@
<i class="fa fa-cloud-upload fa-2x"></i><br>
{% trans "Cloud-init" %}</a>
</li>
{% if perms.vm.export_vm %}
<li>
<a href="#exports" data-toggle="pill" data-target="#_exports" class="text-center">
<i class="fa fa fa-cloud-download fa-2x"></i><br>
{% trans "Exports" %}</a>
</li>
{% endif %}
<li>
<a href="#activity" data-toggle="pill" data-target="#_activity" class="text-center"
data-activity-url="{% url "dashboard.views.vm-activity-list" instance.pk %}">
......@@ -253,6 +260,10 @@
<hr class="js-hidden"/>
<div class="not-tab-pane" id="_activity">{% include "dashboard/vm-detail/activity.html" %}</div>
<hr class="js-hidden"/>
{% if perms.vm.export_vm %}
<div class="not-tab-pane" id="_exports">{% include "dashboard/vm-detail/exports.html" %}</div>
<hr class="js-hidden"/>
{% endif %}
</div>
</div>
</div>
......
{% load i18n %}
{% load sizefieldtags %}
{% load crispy_forms_tags %}
{% load static %}
<div>
<h3>
{% trans "Exports" %}
</h3>
<div class="clearfix"></div>
{% if not instance.exportedvm_set.all %}
{% trans "No exports are created." %}
{% endif %}
{% for export in instance.exportedvm_set.all %}
<h4 class="list-group-item-heading dashboard-vm-details-network-h3">
<i class="fa fa-file"></i> {{ export.name }} exportend on {{ export.created }}
<a href="/image-dl/{{ export.filename }}" class="btn btn-info operation disk-export-btn">
<i class="fa fa-{{ op.export_disk.icon }} fa-fw-12"></i> {% trans "Download" %}
</a>
<a href="{% url "dashboard.views.exportedvm-delete" pk=export.pk %}" class="btn btn-danger operation disk-export-btn">
<i class="fa fa-times fa-fw-12"></i> {% trans "Delete" %}
</a>
</h4>
{% endfor %}
</div>
......@@ -16,3 +16,4 @@ from .storage import *
from request import *
from .message import *
from .autocomplete import *
from .exam import *
......@@ -49,7 +49,7 @@ from braces.views import SuperuserRequiredMixin, LoginRequiredMixin
from storage.tasks import storage_tasks
from vm.tasks.local_tasks import abortable_async_downloaddisk_operation
from vm.operations import (DeployOperation, DestroyOperation, DownloadDiskOperation, RemovePortOperation, ShutdownOperation, RenewOperation,
ResizeDiskOperation, RemoveDiskOperation, SleepOperation, WakeUpOperation, AddPortOperation, SaveAsTemplateOperation,
ResizeDiskOperation, RemoveDiskOperation, SleepOperation, WakeUpOperation, AddPortOperation, SaveAsTemplateOperation, ExportVmOperation,
)
from common.models import (
......@@ -77,7 +77,7 @@ from ..forms import (
VmMigrateForm, VmDeployForm,
VmPortRemoveForm, VmPortAddForm,
VmRemoveInterfaceForm,
VmRenameForm,
VmRenameForm, VmExportViewForm,
)
from django.views.generic.edit import FormMixin
from request.models import TemplateAccessType, LeaseType
......@@ -168,6 +168,7 @@ class DownloadPersistentDiskREST(APIView):
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
class VlanREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
......@@ -209,6 +210,7 @@ class HotplugMemSetREST(APIView):
serializer = InstanceSerializer(instance)
return JsonResponse(serializer.data, status=201)
class HotplugVCPUSetREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
......@@ -223,6 +225,7 @@ class HotplugVCPUSetREST(APIView):
serializer = InstanceSerializer(instance)
return JsonResponse(serializer.data, status=201)
class InterfaceREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
......@@ -466,6 +469,7 @@ class DownloadDiskREST(APIView):
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
class CreateTemplateREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
......@@ -483,6 +487,7 @@ class CreateTemplateREST(APIView):
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
class CreateDiskREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
......@@ -988,6 +993,16 @@ class VmPortAddView(FormOperationMixin, VmOperationView):
return val
class VmExportView(FormOperationMixin, VmOperationView):
op = 'export_vm'
form_class = VmExportViewForm
def get_form_kwargs(self):
op = self.get_op()
val = super(VmExportView, self).get_form_kwargs()
return val
class VmSaveView(FormOperationMixin, VmOperationView):
op = 'save_as_template'
icon = 'save'
......@@ -1004,6 +1019,7 @@ class VmSaveView(FormOperationMixin, VmOperationView):
val['clone'] = True
return val
class CIDataUpdate(VmOperationView):
op = 'cloudinit_change'
icon = "cloud-upload"
......@@ -1278,6 +1294,9 @@ vm_ops = OrderedDict([
('export_disk', VmDiskModifyView.factory(
op='export_disk', form_class=VmDiskExportForm,
icon='download', effect='info')),
('export_vm', VmExportView.factory(
op='export_vm', form_class=VmExportViewForm,
icon='download', effect="info")),
('resize_disk', VmDiskModifyView.factory(
op='resize_disk', form_class=VmDiskResizeForm,
icon='arrows-alt', effect="warning")),
......
......@@ -18,8 +18,8 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import logging
import os
import uuid
import time
......@@ -29,6 +29,7 @@ from celery.result import allow_join_result
from celery.exceptions import TimeoutError
from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse
from django.utils import timezone
from django.db.models import (Model, BooleanField, CharField, DateTimeField, IntegerField,
ForeignKey)
from django.db import models
......@@ -37,6 +38,8 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop
from model_utils.models import TimeStampedModel
from os.path import join
from sizefield.models import FileSizeField
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from common.models import (
WorkerNotFound, HumanReadableException, humanize_exception, join_activity_code, method_cache
......@@ -160,7 +163,7 @@ class Disk(TimeStampedModel):
('download_disk', _('Can download a disk.')),
('resize_disk', _('Can resize a disk.')),
('import_disk', _('Can import a disk.')),
('export_disk', _('Can export a disk.'))
('export_disk', _('Can export a disk.')),
)
class DiskError(HumanReadableException):
......@@ -550,13 +553,14 @@ class Disk(TimeStampedModel):
queue=queue_name)
return self._run_abortable_task(remote, task)
def export_disk_to_datastore(self, task, disk_format, datastore):
def export_disk_to_datastore(self, task, disk_format, datastore, folder="exports"):
queue_name = self.get_remote_queue_name('storage', priority='slow')
remote = storage_tasks.export_disk_to_datastore.apply_async(
kwargs={
"disk_desc": self.get_disk_desc(),
"disk_format": disk_format,
"datastore": datastore,
"folder": folder,
},
queue=queue_name)
return self._run_abortable_task(remote, task)
......@@ -670,3 +674,19 @@ class StorageActivity(ActivityModel):
started=timezone.now(), task_uuid=task_uuid, user=user)
act.save()
return act
class ExportedDisk(Model):
FORMAT = Disk.EXPORT_FORMATS
name = CharField(blank=True, max_length=100, verbose_name=_("name"))
format = models.CharField(max_length=5, choices=FORMAT)
filename = CharField(max_length=256, unique=True, verbose_name=_("filename"))
datastore = ForeignKey(DataStore, verbose_name=_("datastore"), help_text=_("The datastore that holds the exported disk."), on_delete=models.CASCADE)
disk = ForeignKey(Disk, verbose_name=_("disk"), help_text=_("The disk that the export was made from."), on_delete=models.CASCADE)
created = DateTimeField(blank=True, default=timezone.now, null=False, editable=False)
@receiver(pre_delete, sender=ExportedDisk)
def delete_repo(sender, instance, **kwargs):
if os.path.exists(os.path.join(instance.datastore.path, "exports", instance.filename)):
os.unlink(os.path.join(instance.datastore.path, "exports", instance.filename))
<?xml version="1.0"?>
<Envelope ovf:version="2.0" xml:lang="en-US" xmlns="http://schemas.dmtf.org/ovf/envelope/2" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/2" xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:epasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_EthernetPortAllocationSettingData.xsd" xmlns:sasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_StorageAllocationSettingData.xsd">
<References>
{%- for disk in disks %}
<File ovf:id="file{{ loop.index }}" ovf:href="{{ disk.filename }}.vmdk"/>
{%- endfor %}
</References>
<DiskSection>
{%- for disk in disks %}
<Disk ovf:capacity="{{ disk.size }}" ovf:diskId="disk{{ loop.index }}" ovf:fileRef="file{{ loop.index }}" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized"/>
{%- endfor %}
</DiskSection>
<NetworkSection>
<Info>Logical networks used in the package</Info>
<Network ovf:name="NAT">
<Description>Logical network used by this appliance.</Description>
</Network>
</NetworkSection>
<VirtualSystem ovf:id="{{ name }}">
<Description>{{ vm.description }}</Description>
<name>{{ name }}</name>
<OperatingSystemSection ovf:id="{{ os_id }}">
<Info>Specifies the operating system installed</Info>
<Description>{{ vm.system }}</Description>
</OperatingSystemSection>
<VirtualHardwareSection>
<Info>Virtual hardware requirements for a virtual machine</Info>
<Item>
<rasd:Description>Virtual CPU</rasd:Description>
<rasd:InstanceID>1</rasd:InstanceID>
<rasd:ResourceType>3</rasd:ResourceType>
<rasd:VirtualQuantity>{{ vm.num_cores }}</rasd:VirtualQuantity>
<rasd:VirtualQuantityUnit>Count</rasd:VirtualQuantityUnit>
</Item>
<Item>
<rasd:AllocationUnits>MegaBytes</rasd:AllocationUnits>
<rasd:Description>Memory Size</rasd:Description>
<rasd:InstanceID>2</rasd:InstanceID>
<rasd:ResourceType>4</rasd:ResourceType>
<rasd:VirtualQuantity>{{ vm.ram_size }}</rasd:VirtualQuantity>
</Item>
<Item>
<rasd:Address>0</rasd:Address>
<rasd:InstanceID>3</rasd:InstanceID>
<rasd:ResourceSubType>PIIX4</rasd:ResourceSubType>
<rasd:ResourceType>5</rasd:ResourceType>
</Item>
<Item>
<rasd:Address>1</rasd:Address>
<rasd:InstanceID>4</rasd:InstanceID>
<rasd:ResourceSubType>PIIX4</rasd:ResourceSubType>
<rasd:ResourceType>5</rasd:ResourceType>
</Item>
{%- set ns = namespace(InstanceID = 4) %}{%- for disk in disks %}{%- set ns.InstanceID = ns.InstanceID + 1 %}
<StorageItem>
<sasd:AddressOnParent>{{ (loop.index - 1) % 2}}</sasd:AddressOnParent>
<sasd:Caption>{{ disk.name }}</sasd:Caption>
<sasd:Description>Disk Image</sasd:Description>
<sasd:Parent>{% if loop.index < 3 %}3{% else %}4{% endif %}</sasd:Parent>
<sasd:HostResource>/disk/disk{{ loop.index }}</sasd:HostResource>
<sasd:InstanceID>{{ ns.InstanceID }}</sasd:InstanceID>
<sasd:ResourceType>17</sasd:ResourceType>
</StorageItem>
{%- endfor %}
{%- for interface in interfaces %}{%- set ns.InstanceID = ns.InstanceID + 1 %}
<EthernetPortItem>
<epasd:AutomaticAllocation>true</epasd:AutomaticAllocation>
<epasd:Caption>{{ interface.vlan.name }}</epasd:Caption>
<epasd:Connection>NAT</epasd:Connection>
<epasd:InstanceID>{{ ns.InstanceID }}</epasd:InstanceID>
<epasd:ResourceType>10</epasd:ResourceType>
</EthernetPortItem>
{%- endfor %}
</VirtualHardwareSection>
</VirtualSystem>
</Envelope>
......@@ -12,6 +12,8 @@ from .instance import Instance
from .instance import post_state_changed
from .instance import pre_state_changed
from .instance import pwgen
from .instance import ExportedVM
from .instance import OS_TYPES
from .network import InterfaceTemplate
from .network import Interface
from .node import Node
......@@ -21,5 +23,5 @@ __all__ = [
'NamedBaseResourceConfig', 'VirtualMachineDescModel', 'InstanceTemplate',
'Instance', 'post_state_changed', 'pre_state_changed', 'InterfaceTemplate',
'Interface', 'Trait', 'Node', 'NodeActivity', 'Lease', 'node_activity',
'pwgen'
'pwgen', 'ExportedVM', 'OS_TYPES',
]
......@@ -14,7 +14,7 @@
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import os.path
import random, string
from contextlib import contextmanager
from datetime import timedelta
......@@ -27,13 +27,15 @@ from urllib import request
from warnings import warn
from xml.dom.minidom import Text
from django.db.models.signals import pre_delete
from django.dispatch import receiver
import django.conf
from django.contrib.auth.models import User
from django.core import signing
from django.core.exceptions import PermissionDenied
from django.db.models import (BooleanField, CharField, DateTimeField,
IntegerField, ForeignKey, Manager,
ManyToManyField, SET_NULL, TextField)
ManyToManyField, SET_NULL, TextField, Model)
from django.db import IntegrityError
from django.dispatch import Signal
from django.urls import reverse
......@@ -64,6 +66,8 @@ from .activity import (ActivityInProgressError, InstanceActivity)
from .common import BaseResourceConfigModel, Lease, Variable
from .network import Interface
from .node import Node, Trait
from storage.models import DataStore
import subprocess
logger = getLogger(__name__)
......@@ -78,6 +82,131 @@ ACCESS_PROTOCOLS = django.conf.settings.VM_ACCESS_PROTOCOLS
ACCESS_METHODS = [(key, name) for key, (name, port, transport)
in list(ACCESS_PROTOCOLS.items())]
# CIM operationg system types 2.54.0
OS_TYPES = {
0: "Unknown",
1: "Other",
2: "MACOS",
3: "ATTUNIX",
4: "DGUX",
5: "DECNT",
6: "Tru64 UNIX",
7: "OpenVMS",
8: "HPUX",
9: "AIX",
10: "MVS",
11: "OS400",
12: "OS/2",
13: "JavaVM",
14: "MSDOS",
15: "WIN3x",
16: "WIN95",
17: "WIN98",
18: "WINNT",
19: "WINCE",
20: "NCR3000",
21: "NetWare",
22: "OSF",
23: "DC/OS",
24: "Reliant UNIX",
25: "SCO UnixWare",
26: "SCO OpenServer",
27: "Sequent",
28: "IRIX",
29: "Solaris",
30: "SunOS",
31: "U6000",
32: "ASERIES",
33: "HP NonStop OS",
34: "HP NonStop OSS",
35: "BS2000",
36: "LINUX",
37: "Lynx",
38: "XENIX",
39: "VM",
40: "Interactive UNIX",
41: "BSDUNIX",
42: "FreeBSD",
43: "NetBSD",
44: "GNU Hurd",
45: "OS9",
46: "MACH Kernel",
47: "Inferno",
48: "QNX",
49: "EPOC",
50: "IxWorks",
51: "VxWorks",
52: "MiNT",
53: "BeOS",
54: "HP MPE",
55: "NextStep",
56: "PalmPilot",
57: "Rhapsody",
58: "Windows 2000",
59: "Dedicated",
60: "OS/390",
61: "VSE",
62: "TPF",
63: "Windows (R) Me",
64: "Caldera Open UNIX",
65: "OpenBSD",
66: "Not Applicable",
67: "Windows XP",
68: "z/OS",
69: "Microsoft Windows Server 2003",
70: "Microsoft Windows Server 2003 64-Bit",
71: "Windows XP 64-Bit",
72: "Windows XP Embedded",
73: "Windows Vista",
74: "Windows Vista 64-Bit",
75: "Windows Embedded for Point of Service",
76: "Microsoft Windows Server 2008",
77: "Microsoft Windows Server 2008 64-Bit",
78: "FreeBSD 64-Bit",
79: "RedHat Enterprise Linux",
80: "RedHat Enterprise Linux 64-Bit",
81: "Solaris 64-Bit",
82: "SUSE",
83: "SUSE 64-Bit",
84: "SLES",
85: "SLES 64-Bit",
86: "Novell OES",
87: "Novell Linux Desktop",
88: "Sun Java Desktop System",
89: "Mandriva",
90: "Mandriva 64-Bit",
91: "TurboLinux",
92: "TurboLinux 64-Bit",
93: "Ubuntu",
94: "Ubuntu 64-Bit",
95: "Debian",
96: "Debian 64-Bit",
97: "Linux 2.4.x",
98: "Linux 2.4.x 64-Bit",
99: "Linux 2.6.x",
100: "Linux 2.6.x 64-Bit",
101: "Linux 64-Bit",
102: "Other 64-Bit",
103: "Microsoft Windows Server 2008 R2",
104: "VMware ESXi",
105: "Microsoft Windows 7",
106: "CentOS 32-bit",
107: "CentOS 64-bit",
108: "Oracle Linux 32-bit",
109: "Oracle Linux 64-bit",
110: "eComStation 32-bitx",
111: "Microsoft Windows Server 2011",
113: "Microsoft Windows Server 2012",
114: "Microsoft Windows 8",
115: "Microsoft Windows 8 64-bit",
116: "Microsoft Windows Server 2012 R2",
117: "Microsoft Windows Server 2016",
118: "Microsoft Windows 8.1",
119: "Microsoft Windows 8.1 64-bit",
120: "Microsoft Windows 10",
121: "Microsoft Windows 10 64-bit",
}
CI_META_DATA_DEF = """
instance-id: {{ hostname }}
local-hostname: {{ hostname }}
......@@ -216,6 +345,7 @@ class InstanceTemplate(AclBase, VirtualMachineDescModel, TimeStampedModel):
ordering = ('name', )
permissions = (
('create_template', _('Can create an instance template.')),
('import_template', _('Can import an instance template.')),
('create_base_template',
_('Can create an instance template (base).')),
('change_template_resources',
......@@ -397,6 +527,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
('set_resources', _('Can change resources of a new VM.')),
('create_vm', _('Can create a new VM.')),
('redeploy', _('Can redeploy a VM.')),
('export_vm', _('Can export a vm.')),
('config_ports', _('Can configure port forwards.')),
('recover', _('Can recover a destroyed VM.')),
('emergency_change_state', _('Can change VM state to NOSTATE.')),
......@@ -634,6 +765,9 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
missing_users = []
instances = []
for user_id in users:
if isinstance(user_id, User):
user_instances.append(user_id)
else:
try:
user_instances.append(User.objects.get(profile__org_id=user_id))
except User.DoesNotExist:
......@@ -645,8 +779,22 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
for user in user_instances:
instance = cls.create_from_template(template, user, **kwargs)
if admin:
if hasattr(admin, '__iter__') and not isinstance(admin, str):
for admin_user in admin:
if isinstance(admin_user, User):
instance.set_level(admin_user, 'owner')
else:
instance.set_level(User.objects.get(username=admin_user), 'owner')
else:
instance.set_level(User.objects.get(username=admin), 'owner')
if operator:
if hasattr(operator, '__iter__') and not isinstance(operator, str):
for operator_user in operator:
if isinstance(operator_user, User):
instance.set_level(operator_user, 'operator')
else:
instance.set_level(User.objects.get(username=operator_user), 'operator')
else:
instance.set_level(User.objects.get(username=operator), 'operator')
instance.deploy._async(user=user)
instances.append(instance)
......@@ -1165,3 +1313,23 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
user=user, concurrency_check=concurrency_check,
readable_name=readable_name, resultant_state=resultant_state)
return activitycontextimpl(act, on_abort=on_abort, on_commit=on_commit)
class ExportedVM(Model):
name = CharField(blank=True, max_length=100, verbose_name=_("name"))
filename = CharField(max_length=256, unique=True, verbose_name=_("filename"))
datastore = ForeignKey(DataStore, verbose_name=_("datastore"), help_text=_("The datastore that holds the exported VM."), on_delete=models.CASCADE)
vm = ForeignKey(Instance, verbose_name=_("disk"), help_text=_("The vm that the export was made from."), on_delete=models.CASCADE)
created = DateTimeField(blank=True, default=timezone.now, null=False, editable=False)
def has_level(self, user, level):
self.vm.has_level(user, level)
def __str__(self):
return self.name
@receiver(pre_delete, sender=ExportedVM)
def delete_repo(sender, instance, **kwargs):
if os.path.exists(os.path.join(instance.datastore.path, "exports", instance.filename)):
os.unlink(os.path.join(instance.datastore.path, "exports", instance.filename))
......@@ -194,3 +194,7 @@ def hotplug_memset(params):
@celery.task(name='vmdriver.hotplug_vcpuset')
def hotplug_vcpuset(params):
pass
@celery.task(name='vmdriver.export_vm')
def export_vm(params):
pass
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment