commit b016b47662668914ebc378645ad0d52cda11be3c
Author: Giorgos Verigakis <verigak@gmail.com>
Date:   Tue Mar 22 17:10:46 2011 +0200

    Improve REDUX API
    
    * Add server actions
    * Add address listing
    
    API mostly complete now.
    
    Refs #301

diff --git a/api/actions.py b/api/actions.py
new file mode 100644
index 0000000..025518b
--- /dev/null
+++ b/api/actions.py
@@ -0,0 +1,141 @@
+#
+# Copyright (c) 2010 Greek Research and Technology Network
+#
+
+from synnefo.api.errors import *
+from synnefo.util.rapi import GanetiRapiClient
+
+from django.conf import settings
+from django.http import HttpResponse
+
+server_actions = {}
+
+rapi = GanetiRapiClient(*settings.GANETI_CLUSTER_INFO)
+
+
+def server_action(name):
+    def decorator(func):
+        server_actions[name] = func
+        return func
+    return decorator
+
+
+@server_action('changePassword')
+def change_password(server, args):
+    # Normal Response Code: 202
+    # Error Response Codes: computeFault (400, 500),
+    #                       serviceUnavailable (503),
+    #                       unauthorized (401),
+    #                       badRequest (400),
+    #                       badMediaType(415),
+    #                       itemNotFound (404),
+    #                       buildInProgress (409),
+    #                       overLimit (413)
+    
+    try:
+        adminPass = args['adminPass']
+    except KeyError:
+        raise BadRequest()
+
+    raise ServiceUnavailable()
+
+@server_action('reboot')
+def reboot(server, args):
+    # Normal Response Code: 202
+    # Error Response Codes: computeFault (400, 500),
+    #                       serviceUnavailable (503),
+    #                       unauthorized (401),
+    #                       badRequest (400),
+    #                       badMediaType(415),
+    #                       itemNotFound (404),
+    #                       buildInProgress (409),
+    #                       overLimit (413)
+    
+    type = args.get('type', '')
+    if type not in ('SOFT', 'HARD'):
+        raise BadRequest()
+    
+    server.start_action('REBOOT')
+    rapi.RebootInstance(server.backend_id, type.lower())
+    return HttpResponse(status=202)
+
+@server_action('start')
+def start(server, args):
+    # Normal Response Code: 202
+    # Error Response Codes: serviceUnavailable (503), itemNotFound (404)
+
+    server.start_action('START')
+    rapi.StartupInstance(server.backend_id)
+    return HttpResponse(status=202)
+
+@server_action('shutdown')
+def shutdown(server, args):
+    # Normal Response Code: 202
+    # Error Response Codes: serviceUnavailable (503), itemNotFound (404)
+    
+    server.start_action('STOP')
+    rapi.ShutdownInstance(server.backend_id)
+    return HttpResponse(status=202)
+
+@server_action('rebuild')
+def rebuild(server, args):
+    # Normal Response Code: 202
+    # Error Response Codes: computeFault (400, 500),
+    #                       serviceUnavailable (503),
+    #                       unauthorized (401),
+    #                       badRequest (400),
+    #                       badMediaType(415),
+    #                       itemNotFound (404),
+    #                       buildInProgress (409),
+    #                       serverCapacityUnavailable (503),
+    #                       overLimit (413)
+
+    raise ServiceUnavailable()
+
+@server_action('resize')
+def resize(server, args):
+    # Normal Response Code: 202
+    # Error Response Codes: computeFault (400, 500),
+    #                       serviceUnavailable (503),
+    #                       unauthorized (401),
+    #                       badRequest (400),
+    #                       badMediaType(415),
+    #                       itemNotFound (404),
+    #                       buildInProgress (409),
+    #                       serverCapacityUnavailable (503),
+    #                       overLimit (413),
+    #                       resizeNotAllowed (403)
+    
+    raise ResizeNotAllowed()
+
+@server_action('confirmResize')
+def confirm_resize(server, args):
+    # Normal Response Code: 204
+    # Error Response Codes: computeFault (400, 500),
+    #                       serviceUnavailable (503),
+    #                       unauthorized (401),
+    #                       badRequest (400),
+    #                       badMediaType(415),
+    #                       itemNotFound (404),
+    #                       buildInProgress (409),
+    #                       serverCapacityUnavailable (503),
+    #                       overLimit (413),
+    #                       resizeNotAllowed (403)
+    
+    raise ResizeNotAllowed()
+
+@server_action('revertResize')
+def revert_resize(server, args):
+    # Normal Response Code: 202
+    # Error Response Codes: computeFault (400, 500),
+    #                       serviceUnavailable (503),
+    #                       unauthorized (401),
+    #                       badRequest (400),
+    #                       badMediaType(415),
+    #                       itemNotFound (404),
+    #                       buildInProgress (409),
+    #                       serverCapacityUnavailable (503),
+    #                       overLimit (413),
+    #                       resizeNotAllowed (403)
+
+    raise ResizeNotAllowed()
diff --git a/api/errors.py b/api/errors.py
index 67896f2..395a669 100644
--- a/api/errors.py
+++ b/api/errors.py
@@ -19,6 +19,9 @@ class BadRequest(Fault):
 class Unauthorized(Fault):
     code = 401
 
+class ResizeNotAllowed(Fault):
+    code = 403
+
 class ItemNotFound(Fault):
     code = 404
 
diff --git a/api/servers.py b/api/servers.py
index 4bd68ef..5f9e49a 100644
--- a/api/servers.py
+++ b/api/servers.py
@@ -2,11 +2,13 @@
 # Copyright (c) 2010 Greek Research and Technology Network
 #
 
+from synnefo.api.actions import server_actions
 from synnefo.api.errors import *
 from synnefo.api.util import *
 from synnefo.db.models import *
 from synnefo.util.rapi import GanetiRapiClient
 
+from django.conf import settings
 from django.conf.urls.defaults import *
 from django.http import HttpResponse
 from django.template.loader import render_to_string
@@ -23,6 +25,9 @@ urlpatterns = patterns('synnefo.api.servers',
     (r'^(?:/|.json|.xml)?$', 'demux'),
     (r'^/detail(?:.json|.xml)?$', 'list_servers', {'detail': True}),
     (r'^/(\d+)(?:.json|.xml)?$', 'server_demux'),
+    (r'^/(\d+)/action(?:.json|.xml)?$', 'server_action'),
+    (r'^/(\d+)/ips(?:.json|.xml)?$', 'list_addresses'),
+    (r'^/(\d+)/ips/(.+?)(?:.json|.xml)?$', 'list_addresses_by_network'),
 )
 
 
@@ -47,6 +52,10 @@ def server_demux(request, server_id):
         return render_fault(request, fault)
 
 
+def address_to_dict(ipfour, ipsix):
+    return {'id': 'public',
+            'values': [{'version': 4, 'addr': ipfour}, {'version': 6, 'addr': ipsix}]}
+
 def server_to_dict(server, detail=False):
     d = dict(id=server.id, name=server.name)
     if detail:
@@ -64,9 +73,7 @@ def server_to_dict(server, detail=False):
         if metadata:
             d['metadata'] = dict(values=metadata)
         
-        public_addrs = [dict(version=4, addr=server.ipfour), dict(version=6, addr=server.ipsix)]
-        d['addresses'] = {'values': []}
-        d['addresses']['values'].append({'id': 'public', 'values': public_addrs})
+        d['addresses'] = [address_to_dict(server.ipfour, server.ipsix)]
     return d
 
 def render_server(request, serverdict, status=200):
@@ -227,3 +234,73 @@ def delete_server(request, server_id):
     server.start_action('DESTROY')
     rapi.DeleteInstance(server.backend_id)
     return HttpResponse(status=204)
+
+@api_method('POST')
+def server_action(request, server_id):
+    try:
+        server_id = int(server_id)
+        server = VirtualMachine.objects.get(id=server_id)
+    except VirtualMachine.DoesNotExist:
+        raise ItemNotFound
+
+    req = get_request_dict(request)
+    if len(req) != 1:
+        raise BadRequest
+    
+    key = req.keys()[0]
+    if key not in server_actions:
+        raise BadRequest
+    
+    return server_actions[key](server, req[key])
+
+@api_method('GET')
+def list_addresses(request, server_id):
+    # Normal Response Codes: 200, 203
+    # Error Response Codes: computeFault (400, 500),
+    #                       serviceUnavailable (503),
+    #                       unauthorized (401),
+    #                       badRequest (400),
+    #                       overLimit (413)
+    
+    try:
+        server_id = int(server_id)
+        server = VirtualMachine.objects.get(id=server_id)
+    except VirtualMachine.DoesNotExist:
+        raise ItemNotFound
+    
+    addresses = [address_to_dict(server.ipfour, server.ipsix)]
+    
+    if request.type == 'xml':
+        data = render_to_string('list_addresses.xml', {'addresses': addresses})
+    else:
+        data = json.dumps({'addresses': {'values': addresses}})
+    
+    return HttpResponse(data, status=200)
+
+@api_method('GET')
+def list_addresses_by_network(request, server_id, network_id):
+    # Normal Response Codes: 200, 203
+    # Error Response Codes: computeFault (400, 500),
+    #                       serviceUnavailable (503),
+    #                       unauthorized (401),
+    #                       badRequest (400),
+    #                       itemNotFound (404),
+    #                       overLimit (413)
+    
+    try:
+        server_id = int(server_id)
+        server = VirtualMachine.objects.get(id=server_id)
+    except VirtualMachine.DoesNotExist:
+        raise ItemNotFound
+    
+    if network_id != 'public':
+        raise ItemNotFound
+    
+    address = address_to_dict(server.ipfour, server.ipsix)
+    
+    if request.type == 'xml':
+        data = render_to_string('address.xml', {'address': address})
+    else:
+        data = json.dumps({'network': address})
+    
+    return HttpResponse(data, status=200)
diff --git a/api/templates/address.xml b/api/templates/address.xml
new file mode 100644
index 0000000..6b78e4e
--- /dev/null
+++ b/api/templates/address.xml
@@ -0,0 +1,8 @@
+{% spaceless %}
+<?xml version="1.0" encoding="UTF-8"?>
+<network xmlns="http://docs.openstack.org/compute/api/v1.1" id="{{ address.id }}">
+  {% for ip in address.values %}
+  <ip version="{{ ip.version }}" addr="{{ ip.addr }}"/>
+  {% endfor %}
+</network>
+{% endspaceless %}
diff --git a/api/templates/list_addresses.xml b/api/templates/list_addresses.xml
new file mode 100644
index 0000000..b2ba743
--- /dev/null
+++ b/api/templates/list_addresses.xml
@@ -0,0 +1,12 @@
+{% spaceless %}
+<?xml version="1.0" encoding="UTF-8"?>
+<addresses xmlns="http://docs.openstack.org/compute/api/v1.1">
+  {% for address in addresses %}
+  <network id="{{ address.id }}">
+    {% for ip in address.values %}
+    <ip version="{{ ip.version }}" addr="{{ ip.addr }}"/>
+    {% endfor %}
+  </network>
+  {% endfor %}
+</addresses>
+{% endspaceless %}
diff --git a/tools/list_servers b/tools/list_servers
index efdeeee..6e9d076 100755
--- a/tools/list_servers
+++ b/tools/list_servers
@@ -34,7 +34,7 @@ if resp.status != 200:
 buf = resp.read()
 reply = json.loads(buf)
 
-for server in reply['servers']:
+for server in reply['servers']['values']:
     id = server.pop('id')
     name = server.pop('name')
     if options.detail:
