diff --git a/circle/occi/occi.py b/circle/occi/occi.py index 0b7c48a..5d2a6f5 100644 --- a/circle/occi/occi.py +++ b/circle/occi/occi.py @@ -1,5 +1,6 @@ import re +from django.shortcuts import get_object_or_404 from django.contrib.auth.models import User from django.template.loader import render_to_string from django.utils import timezone @@ -43,6 +44,8 @@ occi_os_tpl_regex = re.compile( 'class="mixin"; ?location=".*"; ?title=".*"$' ) +occi_attribute_link_regex = '^/%s/(?P<id>\d+)/?' + class Category(): """Represents a Category object @@ -395,7 +398,7 @@ class StorageLink(Link): def init_attrs(self, instance, disk): self.attrs = {} - self.attrs['occi.core.id'] = "%d_at_%d" % (disk.pk, instance.pk) + self.attrs['occi.core.id'] = "vm%d_disk%d" % (instance.pk, disk.pk) self.attrs['occi.core.target'] = Storage(disk).render_location() self.attrs['occi.core.source'] = Compute(instance).render_location() # deviceid? mountpoint? @@ -404,10 +407,40 @@ class StorageLink(Link): self.instance = instance self.disk = disk + @classmethod + def create_object(cls, data): + attributes = {} + + for d in data: + attr = occi_attribute_regex.match(d) + if attr: + attributes[attr.group("attribute")] = attr.group("value") + + source = attributes.get("occi.core.source") + target = attributes.get("occi.core.target") + if not (source and target): + return None + + # TODO user + user = User.objects.get(username="test") + g = re.match(occi_attribute_link_regex % "storage", target) + disk_pk = g.group("id") + g = re.match(occi_attribute_link_regex % "vm", source) + vm_pk = g.group("id") + + disk = get_object_or_404(Disk, pk=disk_pk) + vm = get_object_or_404(Instance, pk=vm_pk) + + + vm.attach_disk(user=user, disk=disk) + cls.location = "%sstoragelink/%svm_%sdisk" % (OCCI_ADDR, vm_pk, + disk_pk) + return cls + def render_location(self): - return "/link/storagelink/%d_at_%d" % (self.disk.pk, self.instance.pk) + return "/link/storagelink/vm%d_disk%d" % (instance.pk, disk.pk) - def render_body(self): + def render_as_link(self): kind = STORAGE_LINK_KIND return render_to_string("occi/link.html", { @@ -417,6 +450,14 @@ class StorageLink(Link): 'attrs': self.attrs, }) + def render_as_category(self): + kind = STORAGE_LINK_KIND + + return render_to_string("occi/storagelink.html", { + 'kind': kind, + 'attrs': self.attrs, + }) + """predefined stuffs diff --git a/circle/occi/templates/occi/compute.html b/circle/occi/templates/occi/compute.html index ea7bd15..36c3870 100644 --- a/circle/occi/templates/occi/compute.html +++ b/circle/occi/templates/occi/compute.html @@ -9,5 +9,5 @@ Category: compute; scheme="{{ kind.scheme }}"; class="{{ kind.class }}"; {% endfor %} {% for l in links %} - {{ l.render_body }} + {{ l.render_as_link }} {% endfor %} diff --git a/circle/occi/templates/occi/storagelink.html b/circle/occi/templates/occi/storagelink.html new file mode 100644 index 0000000..dbda0fd --- /dev/null +++ b/circle/occi/templates/occi/storagelink.html @@ -0,0 +1,4 @@ +Category: compute; scheme="{{ kind.scheme }}"; class="{{ kind.class }}"; +{% for k, v in attrs.items %} + X-OCCI-Attribute: {{ k }}={% if v.isdigit == False or k == "occi.core.id" %}"{{ v }}"{% else %}{{ v }}{% endif %} +{% endfor %} diff --git a/circle/occi/urls.py b/circle/occi/urls.py index 4caa315..a434bd3 100644 --- a/circle/occi/urls.py +++ b/circle/occi/urls.py @@ -20,7 +20,7 @@ from django.conf.urls import url, patterns from occi.views import ( QueryInterface, ComputeInterface, VmInterface, OsTplInterface, - StorageInterface, DiskInterface, + StorageInterface, DiskInterface, StorageLinkInterface ) urlpatterns = patterns( @@ -31,4 +31,8 @@ urlpatterns = patterns( url(r'^vm/(?P<pk>\d+)/$', VmInterface.as_view(), name="occi.vm"), url(r'^storage/$', StorageInterface.as_view(), name="occi.storage"), url(r'^disk/(?P<pk>\d+)/$', DiskInterface.as_view(), name="occi.disk"), + + url(r'^link/storagelink/$', StorageLinkInterface.as_view()), + url(r'^link/storagelink/vm(?P<vm_pk>\d+)_disk(?P<disk_pk>\d+)/$', + StorageLinkInterface.as_view(), name="occi.storagelink"), ) diff --git a/circle/occi/views.py b/circle/occi/views.py index d071e05..538b269 100644 --- a/circle/occi/views.py +++ b/circle/occi/views.py @@ -1,4 +1,5 @@ from django.http import HttpResponse +from django.shortcuts import get_object_or_404 from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from django.views.generic import View, DetailView @@ -10,6 +11,7 @@ from .occi import ( Compute, Storage, OsTemplate, + StorageLink, COMPUTE_KIND, STORAGE_KIND, LINK_KIND, @@ -184,3 +186,40 @@ class DiskInterface(DetailView): @method_decorator(csrf_exempt) def dispatch(self, *args, **kwargs): return super(DiskInterface, self).dispatch(*args, **kwargs) + + +class StorageLinkInterface(View): + + def get_vm_and_disk(self): + vm = get_object_or_404(Instance, pk=self.kwargs['vm_pk']) + disk = get_object_or_404(Disk, pk=self.kwargs['disk_pk']) + return vm, disk + + def get(self, request, *args, **kwargs): + vm, disk = self.get_vm_and_disk() + sl = StorageLink(instance=vm, disk=disk) + return HttpResponse( + sl.render_as_category(), + content_type="text/plain", + ) + + def post(self, request, *args, **kwargs): + # we don't support actions for storagelinks + # (they don't even exist in the model) + if request.GET.get("action"): + return HttpResponse("", status=500) + else: + data = get_post_data_from_request(request) + sl = StorageLink.create_object(data=data) + response = HttpResponse( + "X-OCCI-Location: %s" % sl.location, + status=201, + content_type="text/plain", + ) + return response + + return HttpResponse() + + @method_decorator(csrf_exempt) + def dispatch(self, *args, **kwargs): + return super(StorageLinkInterface, self).dispatch(*args, **kwargs) diff --git a/circle/vm/models/instance.py b/circle/vm/models/instance.py index 541b7c1..a2d4672 100644 --- a/circle/vm/models/instance.py +++ b/circle/vm/models/instance.py @@ -817,7 +817,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, return acts def get_merged_activities(self, user=None): - whitelist = ("create_disk", "download_disk") + whitelist = ("create_disk", "download_disk", "attach_disk") acts = self.get_activities(user) merged_acts = [] latest = None diff --git a/circle/vm/operations.py b/circle/vm/operations.py index 9555341..9dc2789 100644 --- a/circle/vm/operations.py +++ b/circle/vm/operations.py @@ -1284,3 +1284,39 @@ class DetachNetwork(DetachMixin, AbstractNetworkOperation): id = "_detach_network" name = _("detach network") task = vm_tasks.detach_network + + +@register_operation +class AttachDiskOperation(InstanceOperation): + id = 'attach_disk' + name = _("attach disk") + description = _("Attach an already created disk to the virtual machine.") + required_perms = () + accept_states = ('STOPPED', 'PENDING', 'RUNNING') + + def _operation(self, user, activity, disk): + devnums = list(ascii_lowercase) + for d in self.instance.disks.all(): + devnums.remove(d.dev_num) + disk.dev_num = devnums.pop(0) + disk.save() + + self.instance.disks.add(disk) + + if self.instance.is_running: + with activity.sub_activity( + 'deploying_disk', + readable_name=ugettext_noop("deploying disk") + ): + disk.deploy() + self.instance._attach_disk(parent_activity=activity, disk=disk) + + activity.result = create_readable( + ugettext_noop("%(name)s (#%(pk)s), dev num: %(dev_num)s"), + name=disk.name, pk=disk.pk, dev_num=disk.dev_num + ) + + def get_activity_name(self, kwargs): + return create_readable( + ugettext_noop("attach disk %(name)s"), + name=kwargs['disk'].name)