Commit ffafa697 authored by Giorgos Korfiatis's avatar Giorgos Korfiatis

cyclades: Reassign resources

Add API calls to assign a VM, network, or floating IP to a new project.
parent a4dc4bb0
......@@ -1623,6 +1623,7 @@ Operations Cyclades OS/Compute
`Reboot <#reboot-server>`_ ✔ ✔
`Get Console <#get-server-console>`_ ✔ **✘**
`Set Firewall <#set-server-firewall-profile>`_ ✔ **✘**
`Reassign <#reassign-server>`_ ✔ **✘**
`Change Admin Password <#os-compute-specific>`_ **✘** ✔
`Rebuild <#os-compute-specific>`_ **✘** ✔
`Resize <#os-compute-specific>`_ **✘** ✔
......@@ -1804,6 +1805,23 @@ Request body contents::
.. note:: Response body should be empty
Reassign Server
...............
This operation assigns the VM to a different project.
Request body contents::
reassign: { project: <project-id>}
*Example Action reassign: JSON**
.. code-block:: javascript
{"reassign": {"project": "9969f2fd-86d8-45d6-9106-5e251f7dd92f"}}
.. note:: Response body should be empty
OS/Compute Specific
...................
......@@ -2756,6 +2774,7 @@ Description URI
`Delete <#delete-network>`_ ``/networks/<network-id>`` DELETE
`Connect <#connect-network-to-server>`_ ``/networks/<network-id>/action`` POST
`Disconnect <#disconnect-network-from-server>`_ ``/networks/<network-id>/action`` POST
`Reassign <#reassign-network>`_ ``/networks/<network-id>/action`` POST
=============================================== ================================= ======
......@@ -3331,6 +3350,66 @@ Return Code Description
.. note:: In case of a 202 code, the request body should be empty
Reassign Network
................
Assign a network to a different project.
.. rubric:: Request
================================= ======
URI Method
================================= ======
``/networks/<network-id>/action`` POST
================================= ======
* **network-id** is the identifier of the network
|
============== =========================
Request Header Value
============== =========================
X-Auth-Token User authentication token
Content-Type Type or request body
Content-Length Length of request body
============== =========================
**Example Request Headers**::
X-Auth-Token: z31uRXUn1LZy45p1r7V==
Content-Type: application/json
Content-Length: 31
.. note:: Request parameters should be empty
Response body content (reassign)::
reassign {project: <project-id>}
*Example Action Reassign: JSON*
.. code-block:: javascript
{"reassign" : {"project" : "9969f2fd-86d8-45d6-9106-5e251f7dd92f"}}
.. rubric:: Response
=========================== =====================
Return Code Description
=========================== =====================
200 (OK) Request succeeded
400 (Bad Request) Malformed request
401 (Unauthorized) Missing or expired user token
403 (Forbidden) Not allowed to modify this network (e.g. public)
404 (Not Found) Network not found
500 (Internal Server Error) The request cannot be completed because of an
\ internal error
503 (Service Unavailable) The service is not currently available
=========================== =====================
.. note:: In case of a 200 code, the request body should be empty
Index of Attributes
-------------------
......
......@@ -62,7 +62,9 @@ ips_urlpatterns = patterns(
'synnefo.api.floating_ips',
(r'^(?:/|.json|.xml)?$', 'demux'),
(r'^/detail(?:.json|.xml)?$', 'list_floating_ips', {'detail': True}),
(r'^/(\w+)(?:/|.json|.xml)?$', 'floating_ip_demux'))
(r'^/(\w+)(?:/|.json|.xml)?$', 'floating_ip_demux'),
(r'^/(\w+)/action(?:.json|.xml)?$', 'floating_ip_action_demux'),
)
def demux(request):
......@@ -87,6 +89,29 @@ def floating_ip_demux(request, floating_ip_id):
allowed_methods=['GET', 'DELETE'])
@api.api_method(http_method='POST', user_required=True, logger=log,
serializations=["json"])
def floating_ip_action_demux(request, floating_ip_id):
userid = request.user_uniq
req = utils.get_request_dict(request)
log.debug('floating_ip_action %s %s', floating_ip_id, req)
if len(req) != 1:
raise faults.BadRequest('Malformed request.')
floating_ip = util.get_floating_ip_by_id(userid,
floating_ip_id,
for_update=True)
action = req.keys()[0]
try:
f = FLOATING_IP_ACTIONS[action]
except KeyError:
raise faults.BadRequest("Action %s not supported." % action)
action_args = req[action]
if not isinstance(action_args, dict):
raise faults.BadRequest("Invalid argument.")
return f(request, floating_ip, action_args)
def ip_to_dict(floating_ip):
machine_id = None
port_id = None
......@@ -236,6 +261,20 @@ def list_floating_ip_pools(request):
return HttpResponse(data, status=200)
@transaction.commit_on_success
def reassign(request, floating_ip, args):
project = args.get("project")
if project is None:
raise faults.BadRequest("Missing 'project' attribute.")
ips.reassign_floating_ip(floating_ip, project)
return HttpResponse(status=200)
FLOATING_IP_ACTIONS = {
"reassign": reassign,
}
def network_to_floating_ip_pool(network):
"""Convert a 'Network' object to a floating IP pool dict."""
total, free = network.ip_count()
......
......@@ -40,6 +40,7 @@ from django.db.models import Q
from django.template.loader import render_to_string
from snf_django.lib import api
from snf_django.lib.api import utils
from synnefo.api import util
from synnefo.db.models import Network
......@@ -53,7 +54,9 @@ urlpatterns = patterns(
'synnefo.api.networks',
(r'^(?:/|.json|.xml)?$', 'demux'),
(r'^/detail(?:.json|.xml)?$', 'list_networks', {'detail': True}),
(r'^/(\w+)(?:/|.json|.xml)?$', 'network_demux'))
(r'^/(\w+)(?:/|.json|.xml)?$', 'network_demux'),
(r'^/(\w+)/action(?:/|.json|.xml)?$', 'network_action_demux'),
)
def demux(request):
......@@ -81,6 +84,22 @@ def network_demux(request, network_id):
'DELETE'])
@api.api_method(http_method='POST', user_required=True, logger=log)
def network_action_demux(request, network_id):
req = utils.get_request_dict(request)
network = util.get_network(network_id, request.user_uniq, for_update=True)
action = req.keys()[0]
try:
f = NETWORK_ACTIONS[action]
except KeyError:
raise faults.BadRequest("Action %s not supported." % action)
action_args = req[action]
if not isinstance(action_args, dict):
raise faults.BadRequest("Invalid argument.")
return f(request, network, action_args)
@api.api_method(http_method='GET', user_required=True, logger=log)
def list_networks(request, detail=True):
log.debug('list_networks detail=%s', detail)
......@@ -196,6 +215,20 @@ def network_to_dict(network, detail=True):
return d
@transaction.commit_on_success
def reassign_network(request, network, args):
project = args.get("project")
if project is None:
raise faults.BadRequest("Missing 'project' attribute.")
networks.reassign(network, project)
return HttpResponse(status=200)
NETWORK_ACTIONS = {
"reassign": reassign_network,
}
def render_network(request, networkdict, status=200):
if request.serialization == 'xml':
data = render_to_string('network.xml', {'network': networkdict})
......
......@@ -479,7 +479,7 @@ def delete_server(request, server_id):
# additional server actions
ARBITRARY_ACTIONS = ['console', 'firewallProfile']
ARBITRARY_ACTIONS = ['console', 'firewallProfile', 'reassign']
def key_to_action(key):
......@@ -878,6 +878,15 @@ def revert_resize(request, vm, args):
raise faults.NotImplemented('Resize not supported.')
@server_action('reassign')
def reassign(request, vm, args):
project = args.get("project")
if project is None:
raise faults.BadRequest("Missing 'project' attribute.")
servers.reassign(vm, project)
return HttpResponse(status=200)
@network_action('add')
@transaction.commit_on_success
def add(request, net, args):
......
......@@ -230,3 +230,13 @@ def delete_floating_ip(floating_ip):
log.info("Deleted floating IP '%s' of user '%s", floating_ip,
floating_ip.userid)
floating_ip.delete()
@transaction.commit_on_success
def reassign_floating_ip(floating_ip, project):
action_fields = {"to_project": project,
"from_project": floating_ip.project}
floating_ip.project = project
floating_ip.save()
quotas.issue_and_accept_commission(floating_ip, action="REASSIGN",
action_fields=action_fields)
......@@ -174,3 +174,13 @@ def delete(network):
# If network does not exist in any backend, update the network state
backend_mod.update_network_state(network)
return network
@network_command("REASSIGN")
def reassign(network, project):
action_fields = {"to_project": project, "from_project": network.project}
network.project = project
network.save()
quotas.issue_and_accept_commission(network, action="REASSIGN",
action_fields=action_fields)
return network
......@@ -333,6 +333,15 @@ def _resize(vm, flavor):
return backend.resize_instance(vm, vcpus=flavor.cpu, memory=flavor.ram)
@transaction.commit_on_success
def reassign(vm, project):
action_fields = {"to_project": project, "from_project": vm.project}
vm.project = project
vm.save()
quotas.issue_and_accept_commission(vm, action="REASSIGN",
action_fields=action_fields)
@server_command("SET_FIREWALL_PROFILE")
def set_firewall_profile(vm, profile, nic):
log.info("Setting VM %s, NIC %s, firewall %s", vm, nic, profile)
......
......@@ -131,7 +131,21 @@ def issue_commission(resource, action, name="", force=False, auto_accept=False,
source = resource.project
qh = Quotaholder.get()
if True: # placeholder
if action == "REASSIGN":
try:
from_project = action_fields["from_project"]
to_project = action_fields["to_project"]
except KeyError:
raise Exception("Missing project attribute.")
projects = [from_project, to_project]
with AstakosClientExceptionHandler(user=user, projects=projects):
serial = qh.issue_resource_reassignment(user,
from_project, to_project,
provisions, name=name,
force=force,
auto_accept=auto_accept)
else:
with AstakosClientExceptionHandler(user=user, projects=[source]):
serial = qh.issue_one_commission(user, source,
provisions, name=name,
......@@ -352,6 +366,10 @@ def get_commission_info(resource, action, action_fields=None):
ram = beparams.get("maxmem", flavor.ram)
return {"cyclades.total_cpu": cpu - flavor.cpu,
"cyclades.total_ram": 1048576 * (ram - flavor.ram)}
elif action == "REASSIGN":
if resource.operstate in ["STARTED", "BUILD", "ERROR"]:
resources.update(online_resources)
return resources
else:
#["CONNECT", "DISCONNECT", "SET_FIREWALL_PROFILE"]:
return None
......@@ -361,6 +379,8 @@ def get_commission_info(resource, action, action_fields=None):
return resources
elif action == "DESTROY":
return reverse_quantities(resources)
elif action == "REASSIGN":
return resources
elif isinstance(resource, IPAddress):
if resource.floating_ip:
resources = {"cyclades.floating_ip": 1}
......@@ -368,6 +388,8 @@ def get_commission_info(resource, action, action_fields=None):
return resources
elif action == "DESTROY":
return reverse_quantities(resources)
elif action == "REASSIGN":
return resources
else:
return None
......
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 to comment