Commit 04394fdf by Fukász Rómeó Ervin

occi: implemented network and storage with the links

compute instances can now be modified
minor bug fixes
parent 17a73b8c
Pipeline #443 failed with stage
in 0 seconds
...@@ -358,6 +358,7 @@ LOCAL_APPS = ( ...@@ -358,6 +358,7 @@ LOCAL_APPS = (
'acl', 'acl',
'monitor', 'monitor',
'request', 'request',
'occi',
) )
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps # See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
......
# Copyright 2017 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth import login from django.contrib.auth import login
class OcciAuthForm(AuthenticationForm): class OcciAuthForm(AuthenticationForm):
""" An authentication form for the OCCI implementation. """ """ An authentication form for the OCCI implementation. """
def __init__(self, request, *args, **kwargs): def __init__(self, request, *args, **kwargs):
super(OcciAuthForm, self).__init__(*args, **kwargs) super(OcciAuthForm, self).__init__(*args, **kwargs)
self.request = request self.request = request
......
# Copyright 2017 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import requests import requests
import json import json
# import urllib3 # import urllib3
...@@ -16,6 +33,7 @@ loginData = {"username": username, "password": password} ...@@ -16,6 +33,7 @@ loginData = {"username": username, "password": password}
# Csinalunk egy sessiont, hogy a cookie ami az auth-ert felelos # Csinalunk egy sessiont, hogy a cookie ami az auth-ert felelos
# automatikusan benne maradjon az osszes keresunkben # automatikusan benne maradjon az osszes keresunkben
with requests.Session() as session: with requests.Session() as session:
session.timeout = None
headers = {"Content-Type": "application/json", "Referer": server} headers = {"Content-Type": "application/json", "Referer": server}
# Csrf-Token a bejelentkezeshez # Csrf-Token a bejelentkezeshez
req = session.get(server + "occi/login/", headers=headers, req = session.get(server + "occi/login/", headers=headers,
...@@ -68,7 +86,7 @@ with requests.Session() as session: ...@@ -68,7 +86,7 @@ with requests.Session() as session:
vmid = json.loads(req.text)["resources"][0]["id"] vmid = json.loads(req.text)["resources"][0]["id"]
req = session.get(server + "occi/compute/" + vmid + "/", req = session.get(server + "occi/compute/" + vmid + "/",
headers=headers, verify=False) headers=headers, verify=False)
print("compute-"+str(vmid)) print("compute-" + str(vmid))
print("------------") print("------------")
print("status_code: " + str(req.status_code)) print("status_code: " + str(req.status_code))
print print
...@@ -100,12 +118,12 @@ with requests.Session() as session: ...@@ -100,12 +118,12 @@ with requests.Session() as session:
headers["X-CSRFToken"] = req.cookies['csrftoken'] headers["X-CSRFToken"] = req.cookies['csrftoken']
except: except:
pass pass
actionatrs = {"method": "cold"} actionatrs = {"method": "warm"}
actioninv = {"action": action + "restart", "attributes": actionatrs} actioninv = {"action": action + "restart", "attributes": actionatrs}
req = session.post(server + "occi/compute/" + vmid + "/", req = session.post(server + "occi/compute/" + vmid + "/",
headers=headers, verify=False, headers=headers, verify=False,
data=json.dumps(actioninv)) data=json.dumps(actioninv))
print("compute-"+str(vmid) + "-restart") print("compute-" + str(vmid) + "-restart")
print("-----------------") print("-----------------")
print("status_code: " + str(req.status_code)) print("status_code: " + str(req.status_code))
print print
...@@ -118,12 +136,12 @@ with requests.Session() as session: ...@@ -118,12 +136,12 @@ with requests.Session() as session:
headers["X-CSRFToken"] = req.cookies['csrftoken'] headers["X-CSRFToken"] = req.cookies['csrftoken']
except: except:
pass pass
actioninv["action"] = action + "suspend" actioninv["action"] = action + "stop"
actioninv["attributes"]["method"] = "suspend" actioninv["attributes"]["method"] = "graceful"
req = session.post(server + "occi/compute/" + vmid + "/", req = session.post(server + "occi/compute/" + vmid + "/",
headers=headers, verify=False, headers=headers, verify=False,
data=json.dumps(actioninv)) data=json.dumps(actioninv))
print("compute-" + str(vmid) + "-suspend") print("compute-" + str(vmid) + "-stop")
print("-----------------") print("-----------------")
print("status_code: " + str(req.status_code)) print("status_code: " + str(req.status_code))
print print
...@@ -136,11 +154,11 @@ with requests.Session() as session: ...@@ -136,11 +154,11 @@ with requests.Session() as session:
headers["X-CSRFToken"] = req.cookies["csrftoken"] headers["X-CSRFToken"] = req.cookies["csrftoken"]
except: except:
pass pass
actioninv["action"] = action + "noaction" actioninv["action"] = action + "renew"
req = session.post(server + "occi/compute/" + vmid + "/", req = session.post(server + "occi/compute/" + vmid + "/",
headers=headers, verify=False, headers=headers, verify=False,
data=json.dumps(actioninv)) data=json.dumps(actioninv))
print("compute-" + str(vmid) + "-noaction") print("compute-" + str(vmid) + "-renew")
print("-------------------") print("-------------------")
print("status_code: " + str(req.status_code)) print("status_code: " + str(req.status_code))
print print
...@@ -186,6 +204,50 @@ with requests.Session() as session: ...@@ -186,6 +204,50 @@ with requests.Session() as session:
indent=4, separators=(",", ": "))) indent=4, separators=(",", ": ")))
print print
try:
headers["X-CSRFToken"] = req.cookies["csrftoken"]
except:
pass
req = session.get(server + "occi/network/1/", headers=headers,
verify=False)
print("storage")
print("-------")
print("status_code " + str(req.status_code))
print
print(json.dumps(json.loads(req.text), sort_keys=True,
indent=4, separators=(",", ": ")))
try:
headers["X-CSRFToken"] = req.cookies["csrftoken"]
except:
pass
req = session.post(server + "occi/network/1/", headers=headers,
verify=False, data=json.dumps({"action": "online"}))
print("storage")
print("-------")
print("status_code " + str(req.status_code))
print
print(json.dumps(json.loads(req.text), sort_keys=True,
indent=4, separators=(",", ": ")))
try:
headers["X-CSRFToken"] = req.cookies["csrftoken"]
except:
pass
req = session.post(server + "occi/compute/96/", headers=headers,
verify=False, data=json.dumps(
{
"attributes": {
"occi.compute.memory": 0.250
}
}))
print("computerehelelel")
print("-------")
print("status_code " + str(req.status_code))
print
print(json.dumps(json.loads(req.text), sort_keys=True,
indent=4, separators=(",", ": ")))
# Kijelentkezes # Kijelentkezes
req = session.get(server + "occi/logout/", headers=headers, req = session.get(server + "occi/logout/", headers=headers,
verify=False) verify=False)
......
# Copyright 2017 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
""" Implementation of the OCCI - Core model classes """ """ Implementation of the OCCI - Core model classes """
...@@ -75,6 +93,7 @@ class Kind(Category): ...@@ -75,6 +93,7 @@ class Kind(Category):
class Action(Category): class Action(Category):
""" OCCI 1.2 - CORE - Classification - Action """ """ OCCI 1.2 - CORE - Classification - Action """
def __init(self, *args, **kwargs): def __init(self, *args, **kwargs):
super(Action, self).__init__(*args, **kwargs) super(Action, self).__init__(*args, **kwargs)
......
# Copyright 2017 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
""" Implementation of the OCCI - Infrastructure extension classes """ """ Implementation of the OCCI - Infrastructure extension classes """
from occi_core import Resource from occi_core import Resource, Link
from occi_utils import action_list_for_resource, OcciActionInvocationError from occi_utils import action_list_for_resource, OcciActionInvocationError
from occi_instances import COMPUTE_ACTIONS from occi_instances import (COMPUTE_ACTIONS, LEASETIME_ACTIONS,
STORAGE_ACTIONS, NETWORK_ACTIONS)
from common.models import HumanReadableException from common.models import HumanReadableException
from vm.models import InstanceActivity from celery.exceptions import TimeoutError
import logging
logger = logging.getLogger(__name__)
COMPUTE_STATES = { COMPUTE_STATES = {
...@@ -36,16 +59,21 @@ COMPUTE_ARCHITECTURES = {"x86_64": "x64", ...@@ -36,16 +59,21 @@ COMPUTE_ARCHITECTURES = {"x86_64": "x64",
class Compute(Resource): class Compute(Resource):
""" OCCI 1.2 - Infrastructure extension - Compute """ """ OCCI 1.2 - Infrastructure extension - Compute """
def __init__(self, vm): def __init__(self, vm):
""" Creates a Compute instance of a VM instance object """ """ Creates a Compute instance of a VM instance object """
super(Compute, self).__init__( super(Compute, self).__init__(
"http://schemas.ogf.org/occi/infrastructure#compute", "http://schemas.ogf.org/occi/infrastructure#compute",
str(vm.pk)) str(vm.pk),
title=vm.name)
self.vm = vm self.vm = vm
self.attributes = self.set_attributes() self.attributes = self.set_attributes()
self.actions = action_list_for_resource(COMPUTE_ACTIONS) self.links = self.set_links()
self.actions = action_list_for_resource(
COMPUTE_ACTIONS + LEASETIME_ACTIONS)
self.mixins = [ self.mixins = [
"http://circlecloud.org/occi/infrastructure#credentials", "http://circlecloud.org/occi/infrastructure/compute#credentials",
"http://circlecloud.org/occi/infrastructure/compute#leasetime",
] ]
if vm.template: if vm.template:
self.mixins.append( self.mixins.append(
...@@ -56,7 +84,6 @@ class Compute(Resource): ...@@ -56,7 +84,6 @@ class Compute(Resource):
""" Sets the attributes of the Compute object based on the VM """ Sets the attributes of the Compute object based on the VM
instance. """ instance. """
attributes = {} attributes = {}
attributes["occi.core.title"] = self.vm.name
attributes["occi.compute.architecture"] = ( attributes["occi.compute.architecture"] = (
COMPUTE_ARCHITECTURES.get(self.vm.arch)) COMPUTE_ARCHITECTURES.get(self.vm.arch))
attributes["occi.compute.cores"] = self.vm.num_cores attributes["occi.compute.cores"] = self.vm.num_cores
...@@ -77,38 +104,48 @@ class Compute(Resource): ...@@ -77,38 +104,48 @@ class Compute(Resource):
self.vm.pw) self.vm.pw)
attributes["org.circlecloud.occi.credentials.command"] = ( attributes["org.circlecloud.occi.credentials.command"] = (
self.vm.get_connect_command()) self.vm.get_connect_command())
attributes["org.circlecloud.occi.leasetime.suspend"] = (
unicode(self.vm.time_of_suspend)[:-16])
attributes["org.circlecloud.occi.leasetime.remove"] = (
unicode(self.vm.time_of_delete)[:-16])
return attributes return attributes
def set_links(self):
links = []
disks = self.vm.disks.all()
storages = []
for disk in disks:
storages.append(Storage(disk))
for storage in storages:
links.append(StorageLink(self, storage).render_as_json())
nics = [NetworkInterface(self, Network(nic.vlan))
for nic in self.vm.interface_set.all()]
for networkinterface in nics:
links.append(networkinterface.render_as_json())
return links
def invoke_action(self, user, action, attributes): def invoke_action(self, user, action, attributes):
base = "http://schemas.ogf.org/occi/infrastructure/compute/action#" if action.endswith("start"):
if action == (base + "start"):
self.start(user) self.start(user)
elif action == (base + "stop"): elif action.endswith("stop"):
self.stop(user, attributes) self.stop(user, attributes)
elif action == (base + "restart"): elif action.endswith("restart"):
self.restart(user, attributes) self.restart(user, attributes)
elif action == (base + "suspend"): elif action.endswith("suspend"):
self.suspend(user, attributes) self.suspend(user, attributes)
elif action == (base + "save"): elif action.endswith("save"):
self.save(user, attributes) self.save(user, attributes)
elif action.endswith("renew"):
self.renew(user)
else: else:
raise OcciActionInvocationError(message="Undefined action.") raise OcciActionInvocationError(message="Undefined action.")
# to refresh Compute attribute
self.__init__(self.vm) self.__init__(self.vm)
def has_agent(self): def renew(self, user):
last_boot_time = self.vm.activity_log.filter(
succeeded=True, activity_code__in=(
"vm.Instance.deploy", "vm.Instance.reset",
"vm.Instance.reboot")).latest("finished").finished
try: try:
InstanceActivity.objects.filter( self.vm.renew(user=user, force=True)
activity_code="vm.Instance.agent.starting", except HumanReadableException as e:
started__gt=last_boot_time, instance=self.vm raise OcciActionInvocationError(message=e.get_user_text())
).latest("started")
except InstanceActivity.DoesNotExist: # no agent since last boot
return False
return True
def start(self, user): def start(self, user):
""" Start action on a compute instance """ """ Start action on a compute instance """
...@@ -125,13 +162,31 @@ class Compute(Resource): ...@@ -125,13 +162,31 @@ class Compute(Resource):
if "method" not in attributes: if "method" not in attributes:
raise OcciActionInvocationError(message="No method given.") raise OcciActionInvocationError(message="No method given.")
if attributes["method"] in ("graceful", "acpioff",): if attributes["method"] in ("graceful", "acpioff",):
if not self.has_agent(): # NOTE: at kene nezni hogy minden except ag kell-e
raise OcciActionInvocationError( timeout = self.vm.shutdown.remote_timeout + 10
message="User agent is required.") result = None
try: try:
self.vm.shutdown(task=None, user=user) task = self.vm.shutdown.async(user=user)
except HumanReadableException as e: except HumanReadableException as e:
raise OcciActionInvocationError(message=e.get_user_text()) logger.exception("Could not start operation")
result = e
except Exception as e:
logger.exception("Could not start operation")
result = e
else:
try:
task.get(timeout=timeout)
except TimeoutError:
logger.debug("Result didn't arrive in %ss",
timeout, exc_info=True)
except HumanReadableException as e:
logger.exception(e)
result = e
except Exception as e:
logger.debug("Operation failed.", exc_info=True)
result = e
if result:
raise OcciActionInvocationError(unicode(result))
elif attributes["method"] in ("poweroff",): elif attributes["method"] in ("poweroff",):
try: try:
self.vm.shut_off(user=user) self.vm.shut_off(user=user)
...@@ -146,11 +201,8 @@ class Compute(Resource): ...@@ -146,11 +201,8 @@ class Compute(Resource):
if "method" not in attributes: if "method" not in attributes:
raise OcciActionInvocationError(message="No method given.") raise OcciActionInvocationError(message="No method given.")
if attributes["method"] in ("graceful", "warm",): if attributes["method"] in ("graceful", "warm",):
if not self.has_agent():
raise OcciActionInvocationError(
message="User agent is required.")
try: try:
self.vm.reboot(user=user) self.vm.restart(user=user)
except HumanReadableException as e: except HumanReadableException as e:
raise OcciActionInvocationError(message=e.get_user_text()) raise OcciActionInvocationError(message=e.get_user_text())
elif attributes["method"] in ("cold",): elif attributes["method"] in ("cold",):
...@@ -180,3 +232,160 @@ class Compute(Resource): ...@@ -180,3 +232,160 @@ class Compute(Resource):
# TODO: save template # TODO: save template
raise OcciActionInvocationError( raise OcciActionInvocationError(
message="Save action not implemented") message="Save action not implemented")
STORAGE_STATES_BY_IN_USE = {
"online": "The disk is attached to an active compute instance.",
"offline": "The disk is not used by any compute instances at the moment.",
"error": "The disk is destroyed.",
}
class Storage(Resource):
""" OCCI 1.2 - Infrastructure extension - Storage """
def __init__(self, disk):
super(Storage, self).__init__(
"http://schemas.ogf.org/occi/infrastructure#storage",
str(disk.pk))
self.disk = disk
self.actions = action_list_for_resource(STORAGE_ACTIONS)
self.attributes = self.set_attributes()
def set_attributes(self):
attributes = {}
attributes["occi.storage.size"] = float(self.disk.size) / 10**9
if self.disk.destroyed is None:
if self.disk.is_in_use:
state_key = "online"
else:
state_key = "offline"
else:
state_key = "error"
attributes["occi.storage.state"] = state_key
attributes["occi.storage.state.message"] = (
STORAGE_STATES_BY_IN_USE[state_key])
return attributes
def invoke_action(self, user, action, attributes):
message = ("Action invokation on storage instances is not supported. "
"Please invoke actions on compute instances instead.")
raise OcciActionInvocationError(message=message, status=405)
STORAGELINK_STATES_BY_STORAGE_STATE = {
"online": "active",
"offline": "inactive",
"error": "error",
}
class StorageLink(Link):
""" OCCI 1.2 - Infrastructure extension - StorageLink """
def __init__(self, compute, storage):
super(StorageLink, self).__init__(
{
"location": "/compute/" + compute.id,
"kind": "http://schemas.ogf.org/occi/infrastructure#compute",
},
{
"location": "/storage/" + storage.id,
"kind": "http://schemas.ogf.org/occi/infrastructure#storage",
},
"http://schemas.ogf.org/occi/infrastructure#storagelink",
"compute" + compute.id + "-" + "storage" + storage.id
)
self.compute = compute
self.storage = storage
self.attributes = self.set_attributes()
def set_attributes(self):
attributes = {}
attributes["occi.storagelink.deviceid"] = self.storage.disk.pk
attributes["occi.storagelink.mountpoint"] = (
"/dev/%s%s" % (self.storage.disk.device_type,
self.storage.disk.dev_num)
)
attributes["occi.storagelink.state"] = (
STORAGELINK_STATES_BY_STORAGE_STATE[
self.storage.attributes["occi.storage.state"]])
attributes["occi.storagelink.state.message"] = (
self.storage.attributes["occi.storage.state.message"])
return attributes
class Network(Resource):
""" OCCI 1.2 - Infrastructure extension - Network """
def __init__(self, vlan):
super(Network, self).__init__(
"http://schemas.ogf.org/occi/infrastructure#network",
str(vlan.pk),
)
self.vlan = vlan
self.actions = action_list_for_resource(NETWORK_ACTIONS)
self.attributes = self.set_attributes()
self.mixins = [
"http://schemas.ogf.org/occi/infrastructure/network#ipnetwork",
]
def set_attributes(self):
attributes = {}
attributes["occi.network.vlan"] = self.vlan.vid
attributes["occi.network.state"] = "active"
attributes["occi.network.state.message"] = (
"The network instance is active.")
attributes["occi.network.address"] = unicode(self.vlan.network4)
attributes["occi.network.gateway"] = unicode(self.vlan.network4.ip)
attributes["occi.network.allocation"] = (
"static" if self.vlan.dhcp_pool == "" else "dynamic")
return attributes
def invoke_action(self, user, action, attributes):
message = ("Action invokation on network instances is not supported. "
"Please invoke actions on compute instances instead.")
raise OcciActionInvocationError(message=message, status=405)
class NetworkInterface(Link):
""" OCCI 1.2 - Infrastructure extension - NetworkInterace """
def __init__(self, compute, network):
super(NetworkInterface, self).__init__(
{
"location": "/compute/" + compute.id,
"kind": "http://schemas.ogf.org/occi/infrastructure#compute",
},
{
"location": "/network/" + network.id,
"kind": "http://schemas.ogf.org/occi/infrastructure#network",
},
"http://schemas.ogf.org/occi/infrastructure#networkinterface",
"compute" + compute.id + "-" + "network" + network.id
)
self.compute = compute
self.network = network
self.interface = compute.vm.interface_set.get(vlan=network.vlan)
self.mixins = [
("http://schemas.ogf.org/occi/infrastructure/networkinterface#" +
"ipnetworkinterface"),
]
self.attributes = self.set_attributes()
def set_attributes(self):
attributes = {}
attributes["occi.networkinterface.interface"] = (
self.interface.vlan.name)
attributes["occi.networkinterface.mac"] = unicode(self.interface.mac)
attributes["occi.networkinterface.state"] = "active"
attributes["occi.networkinterface.state.message"] = (
"The networkinterface is active.")
attributes["occi.networkinterface.address"] = (
unicode(self.interface.host.ipv4))
attributes["occi.networkinterface.gateway"] = (
unicode(self.interface.vlan.network4.ip))
attributes["occi.networkinterface.allocation"] = (
self.network.attributes["occi.network.allocation"])
return attributes
# Copyright 2017 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
""" Required instances of the OCCI classes """ """ Required instances of the OCCI classes """
from vm.models.instance import InstanceTemplate from vm.models.instance import InstanceTemplate
...@@ -211,21 +229,46 @@ CREDENTIALS_ATTRIBUTES = [ ...@@ -211,21 +229,46 @@ CREDENTIALS_ATTRIBUTES = [
"connect to the compute instance."), "connect to the compute instance."),
] ]
CREDENTIALS_MIXIN = Mixin("http://circlecloud.org/occi/infrastructure#", CREDENTIALS_MIXIN = Mixin("http://circlecloud.org/occi/infrastructure/" +
"compute#",
"credentials", "credentials",
title="Credentials Mixin", title="Credentials Mixin",
attributes=CREDENTIALS_ATTRIBUTES, attributes=CREDENTIALS_ATTRIBUTES,
applies="http://schemas.ogf.org/occi/" + applies="http://schemas.ogf.org/occi/" +
"infrastructure#compute") "infrastructure#compute")
LEASETIME_ATTRIBUTES = [
Attribute("org.circlecloud.occi.leasetime.suspend", "String", False,
False, description="The time remaining until the compute " +
"instance is suspended."),
Attribute("org.circlecloud.occi.leasetime.remove", "String", False,
False, description="The time remaining until the compute " +
"instance is deleted."),
]
LEASETIME_ACTIONS = [
Action("http://circlecloud.org/occi/infrastructure/compute/action#",
"renew", title="Renew the lease time of the compute instance."),
]
LEASETIME_MIXIN = Mixin("http://circlecloud.org/occi/infrastucture/compute#",
"leasetime",
title="Compute Lease Time Mixin",
attributes=LEASETIME_ATTRIBUTES,
actions=LEASETIME_ACTIONS,
applies="http://schemas.ogf.org/occi/infrastructure" +
"#compute")
OS_TPL_MIXIN = Mixin("http://schemas.ogf.org/occi/infrastructure#", OS_TPL_MIXIN = Mixin("http://schemas.ogf.org/occi/infrastructure#",
"os_tpl", "os_tpl",
title="OS Template") title="OS Template")
ACTION_ARRAYS = [ ACTION_ARRAYS = [
COMPUTE_ACTIONS, COMPUTE_ACTIONS,
# NETWORK_ACTIONS, NETWORK_ACTIONS,
# STORAGE_ACTIONS, STORAGE_ACTIONS,
LEASETIME_ACTIONS,
] ]
...@@ -235,9 +278,10 @@ def ALL_KINDS(): ...@@ -235,9 +278,10 @@ def ALL_KINDS():
RESOURCE_KIND, RESOURCE_KIND,
LINK_KIND, LINK_KIND,
COMPUTE_KIND, COMPUTE_KIND,
# NETWORK_KIND, NETWORK_KIND,
# STORAGE_KIND, STORAGE_KIND,
# NETWORKINTERFACE_KIND NETWORKINTERFACE_KIND,
STORAGELINK_KIND,
] ]
...@@ -255,10 +299,11 @@ def os_tpl_mixins(user): ...@@ -255,10 +299,11 @@ def os_tpl_mixins(user):
def ALL_MIXINS(user): def ALL_MIXINS(user):
mixins = [ mixins = [
# IPNETWORK_MIXIN, IPNETWORK_MIXIN,
# IPNETWORKINTERFACE_MIXIN, IPNETWORKINTERFACE_MIXIN,
CREDENTIALS_MIXIN, CREDENTIALS_MIXIN,
OS_TPL_MIXIN, OS_TPL_MIXIN,
LEASETIME_MIXIN,
] ]
template_mixins = os_tpl_mixins(user) template_mixins = os_tpl_mixins(user)
for template in template_mixins: for template in template_mixins:
......
# Copyright 2017 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
"""" Utilities for the OCCI implementation of CIRCLE """ """" Utilities for the OCCI implementation of CIRCLE """
from django.http import HttpResponse from django.http import HttpResponse
...@@ -7,6 +25,7 @@ import json ...@@ -7,6 +25,7 @@ import json
class OcciException(Exception): class OcciException(Exception):
""" The superclass for OCCI exceptions. It creates a response to be """ The superclass for OCCI exceptions. It creates a response to be
returned when an error occures. """ returned when an error occures. """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
message = kwargs.get("message", "An error occured.") message = kwargs.get("message", "An error occured.")
status = kwargs.get("status", 400) status = kwargs.get("status", 400)
...@@ -17,6 +36,7 @@ class OcciException(Exception): ...@@ -17,6 +36,7 @@ class OcciException(Exception):
class OcciResourceInstanceNotExist(OcciException): class OcciResourceInstanceNotExist(OcciException):
""" An exception to be raised when a resource instance which has been """ An exception to be raised when a resource instance which has been
asked for does not exist. """ asked for does not exist. """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if "message" not in kwargs: if "message" not in kwargs:
kwargs["message"] = "The resource instance does not exist." kwargs["message"] = "The resource instance does not exist."
...@@ -26,6 +46,7 @@ class OcciResourceInstanceNotExist(OcciException): ...@@ -26,6 +46,7 @@ class OcciResourceInstanceNotExist(OcciException):
class OcciActionInvocationError(OcciException): class OcciActionInvocationError(OcciException):
""" An exception to be raised when an action could not be invoked on """ An exception to be raised when an action could not be invoked on
an entity instance for some reason """ an entity instance for some reason """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if "message" not in kwargs: if "message" not in kwargs:
kwargs["message"] = "Could not invoke action." kwargs["message"] = "Could not invoke action."
...@@ -35,6 +56,7 @@ class OcciActionInvocationError(OcciException): ...@@ -35,6 +56,7 @@ class OcciActionInvocationError(OcciException):
class OcciResourceCreationError(OcciException): class OcciResourceCreationError(OcciException):
""" An exception to be raised when a resource instance could not be """ An exception to be raised when a resource instance could not be
created for a reason. """ created for a reason. """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if "message" not in kwargs: if "message" not in kwargs:
kwargs["message"] = "Could not create resource instance." kwargs["message"] = "Could not create resource instance."
...@@ -44,6 +66,7 @@ class OcciResourceCreationError(OcciException): ...@@ -44,6 +66,7 @@ class OcciResourceCreationError(OcciException):
class OcciResourceDeletionError(OcciException): class OcciResourceDeletionError(OcciException):
""" An exception to be raised when a resource instance could not be """ An exception to be raised when a resource instance could not be
deleted for some reason. """ deleted for some reason. """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if "message" not in kwargs: if "message" not in kwargs:
kwargs["message"] = "Could not delete resource instance." kwargs["message"] = "Could not delete resource instance."
...@@ -53,6 +76,7 @@ class OcciResourceDeletionError(OcciException): ...@@ -53,6 +76,7 @@ class OcciResourceDeletionError(OcciException):
class OcciRequestNotValid(OcciException): class OcciRequestNotValid(OcciException):
""" An exception to be raised when the request sent by the client is """ An exception to be raised when the request sent by the client is
not valid for a reason. (e.g, wrong content type, etc.) """ not valid for a reason. (e.g, wrong content type, etc.) """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if "message" not in kwargs: if "message" not in kwargs:
kwargs["message"] = "The request is not valid." kwargs["message"] = "The request is not valid."
......
# Copyright 2017 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.conf.urls import url from django.conf.urls import url
from views import (OcciLoginView, OcciLogoutView, OcciQueryInterfaceView, from views import (OcciLoginView, OcciLogoutView, OcciQueryInterfaceView,
OcciComputeView, OcciComputeCollectionView) OcciComputeView, OcciComputeCollectionView,
OcciStorageView, OcciStorageCollectionView,
OcciNetworkView, OcciNetworkCollectionView)
urlpatterns = [ urlpatterns = [
...@@ -9,4 +29,8 @@ urlpatterns = [ ...@@ -9,4 +29,8 @@ urlpatterns = [
url(r'^-/$', OcciQueryInterfaceView.as_view()), url(r'^-/$', OcciQueryInterfaceView.as_view()),
url(r'^compute/$', OcciComputeCollectionView.as_view()), url(r'^compute/$', OcciComputeCollectionView.as_view()),
url(r'^compute/(?P<id>\d+)/$', OcciComputeView.as_view()), url(r'^compute/(?P<id>\d+)/$', OcciComputeView.as_view()),
url(r'^storage/$', OcciStorageCollectionView.as_view()),
url(r'^storage/(?P<id>\d+)/$', OcciStorageView.as_view()),
url(r'^network/$', OcciNetworkCollectionView.as_view()),
url(r'^network/(?P<id>\d+)/$', OcciNetworkView.as_view()),
] ]
# Copyright 2017 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
""" The views of the OCCI implementation of CIRCLE. """ The views of the OCCI implementation of CIRCLE.
These views handle the http requests of the API. """ These views handle the http requests of the API. """
...@@ -10,8 +28,10 @@ from django.shortcuts import get_object_or_404 ...@@ -10,8 +28,10 @@ from django.shortcuts import get_object_or_404
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from vm.models.instance import Instance, InstanceTemplate from vm.models.instance import Instance, InstanceTemplate
from storage.models import Disk
from firewall.models import Vlan
from forms import OcciAuthForm from forms import OcciAuthForm
from occi_infrastructure import Compute from occi_infrastructure import Compute, Storage, Network
from occi_utils import (OcciResourceInstanceNotExist, from occi_utils import (OcciResourceInstanceNotExist,
OcciActionInvocationError, OcciActionInvocationError,
OcciRequestNotValid, OcciRequestNotValid,
...@@ -20,6 +40,10 @@ from occi_utils import (OcciResourceInstanceNotExist, ...@@ -20,6 +40,10 @@ from occi_utils import (OcciResourceInstanceNotExist,
occi_response, occi_response,
validate_request) validate_request)
from occi_instances import ALL_KINDS, ALL_MIXINS, ALL_ACTIONS from occi_instances import ALL_KINDS, ALL_MIXINS, ALL_ACTIONS
from common.models import HumanReadableException
import logging
log = logging.getLogger(__name__)
class OcciLoginView(View): class OcciLoginView(View):
...@@ -38,6 +62,8 @@ class OcciLoginView(View): ...@@ -38,6 +62,8 @@ class OcciLoginView(View):
""" Returns a response with a cookie to be used for the OCCI api """ Returns a response with a cookie to be used for the OCCI api
requests. """ requests. """
data = json.loads(request.body.decode("utf-8")) data = json.loads(request.body.decode("utf-8"))
log.error(data)
print(data)
form = OcciAuthForm(data=data, request=request) form = OcciAuthForm(data=data, request=request)
if form.is_valid(): if form.is_valid():
result = {"result": "OK"} result = {"result": "OK"}
...@@ -51,6 +77,7 @@ class OcciLoginView(View): ...@@ -51,6 +77,7 @@ class OcciLoginView(View):
class OcciLogoutView(View): class OcciLogoutView(View):
""" Logout """ """ Logout """
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
logout(request) logout(request)
result = {"result": "OK"} result = {"result": "OK"}
...@@ -84,7 +111,7 @@ class OcciQueryInterfaceView(View): ...@@ -84,7 +111,7 @@ class OcciQueryInterfaceView(View):
def put(self, request, *args, **kwargs): def put(self, request, *args, **kwargs):
return occi_response({"error": "Put method is not defined on the " + return occi_response({"error": "Put method is not defined on the " +
"query interface."}, status=400) "query interface."}, status=400)
class OcciComputeCollectionView(View): class OcciComputeCollectionView(View):
...@@ -94,7 +121,7 @@ class OcciComputeCollectionView(View): ...@@ -94,7 +121,7 @@ class OcciComputeCollectionView(View):
validate_request(request) validate_request(request)
except OcciRequestNotValid as e: except OcciRequestNotValid as e:
return e.response return e.response
vms = (Instance.get_objects_with_level("user", request.user) vms = (Instance.get_objects_with_level("owner", request.user)
.filter(destroyed_at=None)) .filter(destroyed_at=None))
json = {"resources": []} json = {"resources": []}
for vm in vms: for vm in vms:
...@@ -114,10 +141,11 @@ class OcciComputeCollectionView(View): ...@@ -114,10 +141,11 @@ class OcciComputeCollectionView(View):
class OcciComputeView(View): class OcciComputeView(View):
""" View of a compute instance """ """ View of a compute instance """
def get_vm_object(self, user, vmid): def get_vm_object(self, user, vmid):
try: try:
vm = get_object_or_404(Instance.get_objects_with_level("user", vm = get_object_or_404(Instance.get_objects_with_level(
user).filter(destroyed_at=None), pk=vmid) "owner", user).filter(destroyed_at=None), pk=vmid)
except Http404: except Http404:
raise OcciResourceInstanceNotExist() raise OcciResourceInstanceNotExist()
return Compute(vm) return Compute(vm)
...@@ -135,21 +163,42 @@ class OcciComputeView(View): ...@@ -135,21 +163,42 @@ class OcciComputeView(View):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
requestData = json.loads(request.body.decode("utf-8")) requestData = json.loads(request.body.decode("utf-8"))
if not requestData["action"]: if "action" in requestData:
return occi_response({"error": "Action invocation rendering " + try:
"is not supplied."}, compute = self.get_vm_object(request.user, kwargs["id"])
status=400) except OcciResourceInstanceNotExist as e:
try: return e.response
compute = self.get_vm_object(request.user, kwargs["id"]) try:
except OcciResourceInstanceNotExist as e: compute.invoke_action(request.user,
return e.response requestData.get("action", None),
try: requestData.get("attributes", None))
compute.invoke_action(request.user, except OcciActionInvocationError as e:
requestData.get("action", None), return e.response
requestData.get("attributes", None)) return occi_response(compute.render_as_json(), status=200)
except OcciActionInvocationError as e: elif "attributes" in requestData:
return e.response attrs = requestData["attributes"]
return occi_response(compute.render_as_json(), status=200) try:
vm = get_object_or_404(Instance.get_objects_with_level(
"owner", request.user).filter(destroyed_at=None),
pk=kwargs["id"])
except Http404:
return OcciResourceInstanceNotExist().response
num_cores = attrs.get("occi.compute.cores", vm.num_cores)
ram_size = (
attrs.get("occi.compute.memory", vm.ram_size / 1024.0) * 1024)
priority = attrs.get("occi.compute.share", vm.priority)
try:
vm.resources_change(
user=request.user,
num_cores=num_cores,
ram_size=ram_size,
max_ram_size=vm.max_ram_size,
priority=priority
)
except HumanReadableException as e:
log.warning(e.get_user_text())
return occi_response(Compute(vm).render_as_json(), status=200)
return occi_response({"error": "Bad request"}, status=400)
def put(self, request, *args, **kwargs): def put(self, request, *args, **kwargs):
# checking if the requested resource exists # checking if the requested resource exists
...@@ -195,3 +244,135 @@ class OcciComputeView(View): ...@@ -195,3 +244,135 @@ class OcciComputeView(View):
except: except:
return OcciResourceDeletionError().response return OcciResourceDeletionError().response
return occi_response({"result": "Compute instance deleted."}) return occi_response({"result": "Compute instance deleted."})
class OcciStorageCollectionView(View):
@method_decorator(ensure_csrf_cookie)
def get(self, request, *args, **kwargs):
try:
validate_request(request)
except OcciRequestNotValid as e:
return e.response
vms = (Instance.get_objects_with_level("owner", request.user)
.filter(destroyed_at=None))
json = {"resources": []}
for vm in vms:
disks = vm.disks.all()
for disk in disks:
json["resources"].append(Storage(disk).render_as_json())
return occi_response(json)
def put(self, request, *args, **kwargs):
return occi_response({"message": "Not supported."}, status=501)
class OcciStorageView(View):
""" View of a storage instance """
def get_disk_object(self, user, diskid):
try:
disk = get_object_or_404(Disk, pk=diskid)
except Http404:
raise OcciResourceInstanceNotExist()
diskvms = disk.instance_set.all()
uservms = Instance.get_objects_with_level(
"user", user).filter(destroyed_at=None)
if len(diskvms & uservms) > 0:
return Storage(disk)
raise OcciResourceInstanceNotExist()
@method_decorator(ensure_csrf_cookie)
def get(self, request, *args, **kwargs):
try:
validate_request(request)
except OcciRequestNotValid as e:
return e.response
try:
disk = self.get_disk_object(request.user, kwargs["id"])
except OcciResourceInstanceNotExist as e:
return e.response
return occi_response(disk.render_as_json(), charset="utf-8")
def post(self, request, *args, **kwargs):
requestData = json.loads(request.body.decode("utf-8"))
if "action" not in requestData:
return occi_response(
{
"error": ("Storage pratial update is not supported. " +
"Action invocation rendering must be supplied."),
},
status=400
)
try:
storage = self.get_disk_object(request.user, kwargs["id"])
except OcciResourceInstanceNotExist as e:
return e.response
try:
storage.invoke_action(request.user,
requestData.get("action", None),
requestData.get("attributes", None))
except OcciActionInvocationError as e:
return e.response
return occi_response(storage.render_as_json(), status=200)
class OcciNetworkCollectionView(View):
@method_decorator(ensure_csrf_cookie)
def get(self, request, *args, **kwargs):
try:
validate_request(request)
except OcciRequestNotValid as e:
return e.response
vlans = (Vlan.get_objects_with_level("owner", request.user))
json = {"resources": []}
for vlan in vlans:
json["resources"].append(Network(vlan).render_as_json())
return occi_response(json)
class OcciNetworkView(View):
""" View of a compute instance """
def get_vlan_object(self, user, vlanid):
try:
vlan = get_object_or_404(Vlan.get_objects_with_level(
"user", user), pk=vlanid)
except Http404:
raise OcciResourceInstanceNotExist()
return Network(vlan)
@method_decorator(ensure_csrf_cookie)
def get(self, request, *args, **kwargs):
try:
validate_request(request)
except OcciRequestNotValid as e:
return e.response
try:
network = self.get_vlan_object(request.user, kwargs["id"])
except OcciResourceInstanceNotExist as e:
return e.response
return occi_response(network.render_as_json(), charset="utf-8")
def post(self, request, *args, **kwargs):
requestData = json.loads(request.body.decode("utf-8"))
if "action" not in requestData:
return occi_response(
{
"error": ("Network partial update is not supported. " +
"Action invocation rendering must be supplied."),
},
status=400
)
try:
network = self.get_vlan_object(request.user, kwargs["id"])
except OcciResourceInstanceNotExist as e:
return e.response
try:
network.invoke_action(request.user,
requestData.get("action", None),
requestData.get("attributes", None))
except OcciActionInvocationError as e:
return e.response
return occi_response(network.render_as_json(), status=200)
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