Revision 99667854

b/docs/compute-api-guide.rst
1623 1623
`Reboot <#reboot-server>`_                      ✔        ✔
1624 1624
`Get Console <#get-server-console>`_            ✔        **✘**
1625 1625
`Set Firewall <#set-server-firewall-profile>`_  ✔        **✘**
1626
`Reassign <#reassign-server>`_                  ✔        **✘**
1626 1627
`Change Admin Password <#os-compute-specific>`_ **✘**    ✔
1627 1628
`Rebuild <#os-compute-specific>`_               **✘**    ✔
1628 1629
`Resize <#os-compute-specific>`_                **✘**    ✔
......
1804 1805

  
1805 1806
.. note:: Response body should be empty
1806 1807

  
1808
Reassign Server
1809
...............
1810

  
1811
This operation assigns the VM to a different project.
1812

  
1813
Request body contents::
1814

  
1815
  reassign: { project: <project-id>}
1816

  
1817
*Example Action reassign: JSON**
1818

  
1819
.. code-block:: javascript
1820

  
1821
  {"reassign": {"project": "9969f2fd-86d8-45d6-9106-5e251f7dd92f"}}
1822

  
1823
.. note:: Response body should be empty
1824

  
1807 1825
OS/Compute Specific
1808 1826
...................
1809 1827

  
......
2756 2774
`Delete <#delete-network>`_                     ``/networks/<network-id>``        DELETE
2757 2775
`Connect <#connect-network-to-server>`_         ``/networks/<network-id>/action`` POST
2758 2776
`Disconnect <#disconnect-network-from-server>`_ ``/networks/<network-id>/action`` POST
2777
`Reassign <#reassign-network>`_                 ``/networks/<network-id>/action`` POST
2759 2778
=============================================== ================================= ======
2760 2779

  
2761 2780

  
......
3331 3350

  
3332 3351
.. note:: In case of a 202 code, the request body should be empty
3333 3352

  
3353
Reassign Network
3354
................
3355

  
3356
Assign a network to a different project.
3357

  
3358
.. rubric:: Request
3359

  
3360
================================= ======
3361
URI                               Method
3362
================================= ======
3363
``/networks/<network-id>/action`` POST
3364
================================= ======
3365

  
3366
* **network-id** is the identifier of the network
3367

  
3368
|
3369

  
3370
==============  =========================
3371
Request Header  Value
3372
==============  =========================
3373
X-Auth-Token    User authentication token
3374
Content-Type    Type or request body
3375
Content-Length  Length of request body
3376
==============  =========================
3377

  
3378
**Example Request Headers**::
3379

  
3380
  X-Auth-Token:   z31uRXUn1LZy45p1r7V==
3381
  Content-Type:   application/json
3382
  Content-Length: 31
3383

  
3384
.. note:: Request parameters should be empty
3385

  
3386
Response body content (reassign)::
3387

  
3388
  reassign {project: <project-id>}
3389

  
3390
*Example Action Reassign: JSON*
3391

  
3392
.. code-block:: javascript
3393

  
3394
  {"reassign" : {"project" : "9969f2fd-86d8-45d6-9106-5e251f7dd92f"}}
3395

  
3396
.. rubric:: Response
3397

  
3398
=========================== =====================
3399
Return Code                 Description
3400
=========================== =====================
3401
200 (OK)                    Request succeeded
3402
400 (Bad Request)           Malformed request
3403
401 (Unauthorized)          Missing or expired user token
3404
403 (Forbidden)             Not allowed to modify this network (e.g. public)
3405
404 (Not Found)             Network not found
3406
500 (Internal Server Error) The request cannot be completed because of an
3407
\                           internal error
3408
503 (Service Unavailable)   The service is not currently available
3409
=========================== =====================
3410

  
3411
.. note:: In case of a 200 code, the request body should be empty
3412

  
3334 3413
Index of Attributes
3335 3414
-------------------
3336 3415

  
b/snf-cyclades-app/synnefo/api/floating_ips.py
62 62
    'synnefo.api.floating_ips',
63 63
    (r'^(?:/|.json|.xml)?$', 'demux'),
64 64
    (r'^/detail(?:.json|.xml)?$', 'list_floating_ips', {'detail': True}),
65
    (r'^/(\w+)(?:/|.json|.xml)?$', 'floating_ip_demux'))
65
    (r'^/(\w+)(?:/|.json|.xml)?$', 'floating_ip_demux'),
66
    (r'^/(\w+)/action(?:.json|.xml)?$', 'floating_ip_action_demux'),
67
)
66 68

  
67 69

  
68 70
def demux(request):
......
87 89
                                          allowed_methods=['GET', 'DELETE'])
88 90

  
89 91

  
92
@api.api_method(http_method='POST', user_required=True, logger=log,
93
                serializations=["json"])
94
def floating_ip_action_demux(request, floating_ip_id):
95
    userid = request.user_uniq
96
    req = utils.get_request_dict(request)
97
    log.debug('floating_ip_action %s %s', floating_ip_id, req)
98
    if len(req) != 1:
99
        raise faults.BadRequest('Malformed request.')
100
    floating_ip = util.get_floating_ip_by_id(userid,
101
                                             floating_ip_id,
102
                                             for_update=True)
103
    action = req.keys()[0]
104
    try:
105
        f = FLOATING_IP_ACTIONS[action]
106
    except KeyError:
107
        raise faults.BadRequest("Action %s not supported." % action)
108
    action_args = req[action]
109
    if not isinstance(action_args, dict):
110
        raise faults.BadRequest("Invalid argument.")
111

  
112
    return f(request, floating_ip, action_args)
113

  
114

  
90 115
def ip_to_dict(floating_ip):
91 116
    machine_id = None
92 117
    port_id = None
......
236 261
    return HttpResponse(data, status=200)
237 262

  
238 263

  
264
@transaction.commit_on_success
265
def reassign(request, floating_ip, args):
266
    project = args.get("project")
267
    if project is None:
268
        raise faults.BadRequest("Missing 'project' attribute.")
269
    ips.reassign_floating_ip(floating_ip, project)
270
    return HttpResponse(status=200)
271

  
272

  
273
FLOATING_IP_ACTIONS = {
274
    "reassign": reassign,
275
}
276

  
277

  
239 278
def network_to_floating_ip_pool(network):
240 279
    """Convert a 'Network' object to a floating IP pool dict."""
241 280
    total, free = network.ip_count()
b/snf-cyclades-app/synnefo/api/networks.py
40 40
from django.template.loader import render_to_string
41 41

  
42 42
from snf_django.lib import api
43
from snf_django.lib.api import utils
43 44

  
44 45
from synnefo.api import util
45 46
from synnefo.db.models import Network
......
53 54
    'synnefo.api.networks',
54 55
    (r'^(?:/|.json|.xml)?$', 'demux'),
55 56
    (r'^/detail(?:.json|.xml)?$', 'list_networks', {'detail': True}),
56
    (r'^/(\w+)(?:/|.json|.xml)?$', 'network_demux'))
57
    (r'^/(\w+)(?:/|.json|.xml)?$', 'network_demux'),
58
    (r'^/(\w+)/action(?:/|.json|.xml)?$', 'network_action_demux'),
59
)
57 60

  
58 61

  
59 62
def demux(request):
......
81 84
                                                           'DELETE'])
82 85

  
83 86

  
87
@api.api_method(http_method='POST', user_required=True, logger=log)
88
def network_action_demux(request, network_id):
89
    req = utils.get_request_dict(request)
90
    network = util.get_network(network_id, request.user_uniq, for_update=True)
91
    action = req.keys()[0]
92
    try:
93
        f = NETWORK_ACTIONS[action]
94
    except KeyError:
95
        raise faults.BadRequest("Action %s not supported." % action)
96
    action_args = req[action]
97
    if not isinstance(action_args, dict):
98
        raise faults.BadRequest("Invalid argument.")
99

  
100
    return f(request, network, action_args)
101

  
102

  
84 103
@api.api_method(http_method='GET', user_required=True, logger=log)
85 104
def list_networks(request, detail=True):
86 105
    log.debug('list_networks detail=%s', detail)
......
196 215
    return d
197 216

  
198 217

  
218
@transaction.commit_on_success
219
def reassign_network(request, network, args):
220
    project = args.get("project")
221
    if project is None:
222
        raise faults.BadRequest("Missing 'project' attribute.")
223
    networks.reassign(network, project)
224
    return HttpResponse(status=200)
225

  
226

  
227
NETWORK_ACTIONS = {
228
    "reassign": reassign_network,
229
}
230

  
231

  
199 232
def render_network(request, networkdict, status=200):
200 233
    if request.serialization == 'xml':
201 234
        data = render_to_string('network.xml', {'network': networkdict})
b/snf-cyclades-app/synnefo/api/servers.py
479 479

  
480 480

  
481 481
# additional server actions
482
ARBITRARY_ACTIONS = ['console', 'firewallProfile']
482
ARBITRARY_ACTIONS = ['console', 'firewallProfile', 'reassign']
483 483

  
484 484

  
485 485
def key_to_action(key):
......
878 878
    raise faults.NotImplemented('Resize not supported.')
879 879

  
880 880

  
881
@server_action('reassign')
882
def reassign(request, vm, args):
883
    project = args.get("project")
884
    if project is None:
885
        raise faults.BadRequest("Missing 'project' attribute.")
886
    servers.reassign(vm, project)
887
    return HttpResponse(status=200)
888

  
889

  
881 890
@network_action('add')
882 891
@transaction.commit_on_success
883 892
def add(request, net, args):
b/snf-cyclades-app/synnefo/logic/ips.py
230 230
    log.info("Deleted floating IP '%s' of user '%s", floating_ip,
231 231
             floating_ip.userid)
232 232
    floating_ip.delete()
233

  
234

  
235
@transaction.commit_on_success
236
def reassign_floating_ip(floating_ip, project):
237
    action_fields = {"to_project": project,
238
                     "from_project": floating_ip.project}
239
    floating_ip.project = project
240
    floating_ip.save()
241
    quotas.issue_and_accept_commission(floating_ip, action="REASSIGN",
242
                                       action_fields=action_fields)
b/snf-cyclades-app/synnefo/logic/networks.py
174 174
        # If network does not exist in any backend, update the network state
175 175
        backend_mod.update_network_state(network)
176 176
    return network
177

  
178

  
179
@network_command("REASSIGN")
180
def reassign(network, project):
181
    action_fields = {"to_project": project, "from_project": network.project}
182
    network.project = project
183
    network.save()
184
    quotas.issue_and_accept_commission(network, action="REASSIGN",
185
                                       action_fields=action_fields)
186
    return network
b/snf-cyclades-app/synnefo/logic/servers.py
333 333
    return backend.resize_instance(vm, vcpus=flavor.cpu, memory=flavor.ram)
334 334

  
335 335

  
336
@transaction.commit_on_success
337
def reassign(vm, project):
338
    action_fields = {"to_project": project, "from_project": vm.project}
339
    vm.project = project
340
    vm.save()
341
    quotas.issue_and_accept_commission(vm, action="REASSIGN",
342
                                       action_fields=action_fields)
343

  
344

  
336 345
@server_command("SET_FIREWALL_PROFILE")
337 346
def set_firewall_profile(vm, profile, nic):
338 347
    log.info("Setting VM %s, NIC %s, firewall %s", vm, nic, profile)
b/snf-cyclades-app/synnefo/quotas/__init__.py
131 131
    source = resource.project
132 132

  
133 133
    qh = Quotaholder.get()
134
    if True:  # placeholder
134
    if action == "REASSIGN":
135
        try:
136
            from_project = action_fields["from_project"]
137
            to_project = action_fields["to_project"]
138
        except KeyError:
139
            raise Exception("Missing project attribute.")
140

  
141
        projects = [from_project, to_project]
142
        with AstakosClientExceptionHandler(user=user, projects=projects):
143
            serial = qh.issue_resource_reassignment(user,
144
                                                    from_project, to_project,
145
                                                    provisions, name=name,
146
                                                    force=force,
147
                                                    auto_accept=auto_accept)
148
    else:
135 149
        with AstakosClientExceptionHandler(user=user, projects=[source]):
136 150
            serial = qh.issue_one_commission(user, source,
137 151
                                             provisions, name=name,
......
352 366
            ram = beparams.get("maxmem", flavor.ram)
353 367
            return {"cyclades.total_cpu": cpu - flavor.cpu,
354 368
                    "cyclades.total_ram": 1048576 * (ram - flavor.ram)}
369
        elif action == "REASSIGN":
370
            if resource.operstate in ["STARTED", "BUILD", "ERROR"]:
371
                resources.update(online_resources)
372
            return resources
355 373
        else:
356 374
            #["CONNECT", "DISCONNECT", "SET_FIREWALL_PROFILE"]:
357 375
            return None
......
361 379
            return resources
362 380
        elif action == "DESTROY":
363 381
            return reverse_quantities(resources)
382
        elif action == "REASSIGN":
383
            return resources
364 384
    elif isinstance(resource, IPAddress):
365 385
        if resource.floating_ip:
366 386
            resources = {"cyclades.floating_ip": 1}
......
368 388
                return resources
369 389
            elif action == "DESTROY":
370 390
                return reverse_quantities(resources)
391
            elif action == "REASSIGN":
392
                return resources
371 393
        else:
372 394
            return None
373 395

  

Also available in: Unified diff