commit 2e0916f21a1c8b16271113552fded472c5801a36
Author: Marios Kogias <marioskogias@grnet.gr>
Date:   Sat Sep 28 16:17:32 2013 +0300

    Start router api

diff --git a/snf-cyclades-app/synnefo/db/migrations/0073_auto__add_field_virtualmachine_router.py b/snf-cyclades-app/synnefo/db/migrations/0073_auto__add_field_virtualmachine_router.py
new file mode 100644
index 0000000..7e5be5d
--- /dev/null
+++ b/snf-cyclades-app/synnefo/db/migrations/0073_auto__add_field_virtualmachine_router.py
@@ -0,0 +1,202 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding field 'VirtualMachine.router'
+        db.add_column('db_virtualmachine', 'router',
+                      self.gf('django.db.models.fields.BooleanField')(default=False),
+                      keep_default=False)
+
+
+    def backwards(self, orm):
+        # Deleting field 'VirtualMachine.router'
+        db.delete_column('db_virtualmachine', 'router')
+
+
+    models = {
+        'db.backend': {
+            'Meta': {'ordering': "['clustername']", 'object_name': 'Backend'},
+            'clustername': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+            'ctotal': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'dfree': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'drained': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'dtotal': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'hash': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'hypervisor': ('django.db.models.fields.CharField', [], {'default': "'kvm'", 'max_length': '32'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'index': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'unique': 'True'}),
+            'mfree': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'mtotal': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'offline': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'password_hash': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'pinst_cnt': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'port': ('django.db.models.fields.PositiveIntegerField', [], {'default': '5080'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'})
+        },
+        'db.backendnetwork': {
+            'Meta': {'unique_together': "(('network', 'backend'),)", 'object_name': 'BackendNetwork'},
+            'backend': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'networks'", 'to': "orm['db.Backend']"}),
+            'backendjobid': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'backendjobstatus': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
+            'backendlogmsg': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'backendopcode': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
+            'backendtime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1, 1, 1, 0, 0)'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'mac_prefix': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'network': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'backend_networks'", 'to': "orm['db.Network']"}),
+            'operstate': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '30'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+        },
+        'db.bridgepooltable': {
+            'Meta': {'object_name': 'BridgePoolTable'},
+            'available_map': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            'base': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'offset': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'reserved_map': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            'size': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'db.flavor': {
+            'Meta': {'unique_together': "(('cpu', 'ram', 'disk', 'disk_template'),)", 'object_name': 'Flavor'},
+            'cpu': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'disk': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'disk_template': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ram': ('django.db.models.fields.IntegerField', [], {'default': '0'})
+        },
+        'db.floatingip': {
+            'Meta': {'object_name': 'FloatingIP'},
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ipv4': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15', 'db_index': 'True'}),
+            'machine': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'floating_ips'", 'null': 'True', 'to': "orm['db.VirtualMachine']"}),
+            'network': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'floating_ips'", 'to': "orm['db.Network']"}),
+            'serial': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'floating_ips'", 'null': 'True', 'to': "orm['db.QuotaHolderSerial']"}),
+            'userid': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+        },
+        'db.ippooltable': {
+            'Meta': {'object_name': 'IPPoolTable'},
+            'available_map': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            'base': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'offset': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'reserved_map': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            'size': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'db.macprefixpooltable': {
+            'Meta': {'object_name': 'MacPrefixPoolTable'},
+            'available_map': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            'base': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'offset': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'reserved_map': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            'size': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'db.network': {
+            'Meta': {'object_name': 'Network'},
+            'action': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '32', 'null': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'dhcp': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'drained': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'flavor': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'floating_ip_pool': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'gateway': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'gateway6': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'link': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'mac_prefix': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'machines': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['db.VirtualMachine']", 'through': "orm['db.NetworkInterface']", 'symmetrical': 'False'}),
+            'mode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'pool': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'network'", 'unique': 'True', 'null': 'True', 'to': "orm['db.IPPoolTable']"}),
+            'public': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'serial': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'network'", 'null': 'True', 'to': "orm['db.QuotaHolderSerial']"}),
+            'state': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '32'}),
+            'subnet': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'subnet6': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'tags': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'userid': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'db_index': 'True'})
+        },
+        'db.networkinterface': {
+            'Meta': {'object_name': 'NetworkInterface'},
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'dirty': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'firewall_profile': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'index': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'ipv4': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True'}),
+            'ipv6': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}),
+            'mac': ('django.db.models.fields.CharField', [], {'max_length': '32', 'unique': 'True', 'null': 'True'}),
+            'machine': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nics'", 'to': "orm['db.VirtualMachine']"}),
+            'network': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nics'", 'to': "orm['db.Network']"}),
+            'state': ('django.db.models.fields.CharField', [], {'default': "'ACTIVE'", 'max_length': '32'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+        },
+        'db.quotaholderserial': {
+            'Meta': {'ordering': "['serial']", 'object_name': 'QuotaHolderSerial'},
+            'accept': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'pending': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+            'resolved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'serial': ('django.db.models.fields.BigIntegerField', [], {'primary_key': 'True', 'db_index': 'True'})
+        },
+        'db.virtualmachine': {
+            'Meta': {'object_name': 'VirtualMachine'},
+            'action': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '30', 'null': 'True'}),
+            'backend': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'virtual_machines'", 'null': 'True', 'to': "orm['db.Backend']"}),
+            'backend_hash': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+            'backendjobid': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'backendjobstatus': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
+            'backendlogmsg': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'backendopcode': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
+            'backendtime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1, 1, 1, 0, 0)'}),
+            'buildpercentage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'flavor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.Flavor']"}),
+            'hostid': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'imageid': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'operstate': ('django.db.models.fields.CharField', [], {'default': "'BUILD'", 'max_length': '30'}),
+            'router': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'serial': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'virtual_machine'", 'null': 'True', 'to': "orm['db.QuotaHolderSerial']"}),
+            'suspended': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'task': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'task_job_id': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'userid': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'})
+        },
+        'db.virtualmachinediagnostic': {
+            'Meta': {'ordering': "['-created']", 'object_name': 'VirtualMachineDiagnostic'},
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'details': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'level': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'machine': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'diagnostics'", 'to': "orm['db.VirtualMachine']"}),
+            'message': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'source': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'source_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'})
+        },
+        'db.virtualmachinemetadata': {
+            'Meta': {'unique_together': "(('meta_key', 'vm'),)", 'object_name': 'VirtualMachineMetadata'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'meta_key': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'meta_value': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
+            'vm': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'metadata'", 'to': "orm['db.VirtualMachine']"})
+        }
+    }
+
+    complete_apps = ['db']
\ No newline at end of file
diff --git a/snf-cyclades-app/synnefo/db/models.py b/snf-cyclades-app/synnefo/db/models.py
index 71d5971..b973af8 100644
--- a/snf-cyclades-app/synnefo/db/models.py
+++ b/snf-cyclades-app/synnefo/db/models.py
@@ -359,6 +359,9 @@ class VirtualMachine(models.Model):
     task = models.CharField(max_length=64, null=True)
     task_job_id = models.BigIntegerField(null=True)
 
+    #is this virtualmachine a router
+    router = models.BooleanField('router', default=False)
+
     objects = ForUpdateManager()
 
     def get_client(self):
diff --git a/snf-cyclades-app/synnefo/db/models_factory.py b/snf-cyclades-app/synnefo/db/models_factory.py
index 1fbfcca..60403ab 100644
--- a/snf-cyclades-app/synnefo/db/models_factory.py
+++ b/snf-cyclades-app/synnefo/db/models_factory.py
@@ -101,7 +101,6 @@ class VirtualMachineFactory(factory.DjangoModelFactory):
     suspended = False
     operstate = factory.Sequence(round_seq_first(FACTORY_FOR.OPER_STATES))
 
-
 class DeletedVirtualMachine(VirtualMachineFactory):
     deleted = True
 
diff --git a/snf-cyclades-app/synnefo/neutron/models.py b/snf-cyclades-app/synnefo/neutron/models.py
index 399c58a..ff3c44b 100644
--- a/snf-cyclades-app/synnefo/neutron/models.py
+++ b/snf-cyclades-app/synnefo/neutron/models.py
@@ -124,7 +124,7 @@ class Network(models.Model):
                              default='PENDING')
     machines = models.ManyToManyField(VirtualMachine,
                                       through='NetworkInterface',
-                                      related_name='neutron_machines')
+                                      related_name='neutron_networks')
     action = models.CharField(choices=ACTIONS, max_length=32, null=True,
                               default=None)
     drained = models.BooleanField("Drained", default=False, null=False)
@@ -134,84 +134,9 @@ class Network(models.Model):
     #serial = models.ForeignKey(QuotaHolderSerial, related_name='network',
     #                           null=True)
 
+    public = models.BooleanField('public', default=True)
     objects = ForUpdateManager()
 
-    def __unicode__(self):
-        return "<Network: %s>" % str(self.id)
-
-    @property
-    def backend_id(self):
-        """Return the backend id by prepending backend-prefix."""
-        if not self.id:
-            raise Network.InvalidBackendIdError("self.id is None")
-        return "%snet-%s" % (settings.BACKEND_PREFIX_ID, str(self.id))
-
-    @property
-    def backend_tag(self):
-        """Return the network tag to be used in backend
-
-        """
-        if self.tags:
-            return self.tags.split(',')
-        else:
-            return []
-
-    def create_backend_network(self, backend=None):
-        """Create corresponding BackendNetwork entries."""
-
-        backends = [backend] if backend else\
-            Backend.objects.filter(offline=False)
-        for backend in backends:
-            backend_exists =\
-                BackendNetwork.objects.filter(backend=backend, network=self)\
-                                      .exists()
-            if not backend_exists:
-                BackendNetwork.objects.create(backend=backend, network=self)
-
-    def get_pool(self, with_lock=True):
-        if not self.pool_id:
-            self.pool = IPPoolTable.objects.create(available_map='',
-                                                   reserved_map='',
-                                                   size=0)
-            self.save()
-        objects = IPPoolTable.objects
-        if with_lock:
-            objects = objects.select_for_update()
-        return objects.get(id=self.pool_id).pool
-
-    def reserve_address(self, address):
-        pool = self.get_pool()
-        pool.reserve(address)
-        pool.save()
-
-    def release_address(self, address):
-        pool = self.get_pool()
-        pool.put(address)
-        pool.save()
-
-    class InvalidBackendIdError(Exception):
-        def __init__(self, value):
-            self.value = value
-
-        def __str__(self):
-            return repr(self.value)
-
-    class InvalidBackendMsgError(Exception):
-        def __init__(self, opcode, status):
-            self.opcode = opcode
-            self.status = status
-
-        def __str__(self):
-            return repr('<opcode: %s, status: %s>'
-                        % (self.opcode, self.status))
-
-    class InvalidActionError(Exception):
-        def __init__(self, action):
-            self._action = action
-
-        def __str__(self):
-            return repr(str(self._action))
-
 
 class Subnet(models.Model):
     SUBNET_NAME_LENGTH = 128
@@ -243,9 +168,10 @@ class NetworkInterface(models.Model):
         ("BUILDING", "Building"),
     )
 
-    name = models.CharField('Network Name', max_length=128)
+    name = models.CharField('nic name', max_length=128)
     machine = models.ForeignKey(VirtualMachine, related_name='neutron_nics')
     network = models.ForeignKey(Network, related_name='neutron_nics')
+    subnet = models.ForeignKey(Subnet, related_names='neutron_nics')
     created = models.DateTimeField(auto_now_add=True)
     updated = models.DateTimeField(auto_now=True)
     index = models.IntegerField(null=True)
@@ -271,3 +197,6 @@ class NetworkInterface(models.Model):
             return network.floating_ips.filter(machine=self.machine,
                                                ipv4=self.ipv4,
                                                deleted=False).exists()
+
+
+
diff --git a/snf-cyclades-app/synnefo/neutron/network_views.py b/snf-cyclades-app/synnefo/neutron/network_views.py
index 6573652..3729149 100644
--- a/snf-cyclades-app/synnefo/neutron/network_views.py
+++ b/snf-cyclades-app/synnefo/neutron/network_views.py
@@ -60,7 +60,7 @@ def list_networks(request, detail=False):
 
     if request.serialization == 'xml':
         data = render_to_string('list_networks.xml', {
-            'networks': networks})
+            "networks": networks})
     else:
         data = json.dumps({'networks': networks})
 
@@ -212,7 +212,7 @@ def update_network(request, network_id):
     updatable = set(["name", "admin_state_up"])
     try:
         new_vals = info["network"]
-    except Keyerror:
+    except KeyError:
         raise api.faults.BadRequest("Malformed request")
 
     for key, val in new_vals.iteritems():
diff --git a/snf-cyclades-app/synnefo/neutron/router_views.py b/snf-cyclades-app/synnefo/neutron/router_views.py
new file mode 100644
index 0000000..eccc5ef
--- /dev/null
+++ b/snf-cyclades-app/synnefo/neutron/router_views.py
@@ -0,0 +1,339 @@
+from django.http import HttpResponse
+from django.utils import simplejson as json
+from django.db import transaction
+from django.db.models import Q
+from synnefo.db.pools import EmptyPool
+from synnefo.db.utils import validate_mac
+from django.conf import settings
+from snf_django.lib import api
+from snf_django.lib.api import utils
+from synnefo.logic import backend
+from django.template.loader import render_to_string
+from synnefo.api import util
+
+from synnefo.db.models import VirtualMachine
+import models
+from logging import getLogger
+
+import network_views as nv
+#from synnefo.logic import servers
+
+log = getLogger(__name__)
+
+
+def demux(request):
+    if request.method == 'GET':
+        #return HttpResponse("list routers")
+        return list_routers(request)
+    elif request.method == 'POST':
+        #return create_router(request)
+        return HttpResponse("create router")
+    else:
+        return api.api_method_not_allowed(request)
+
+
+def router_demux(request, offset):
+    if request.method == 'GET':
+        #return HttpResponse("get single router")
+        return get_router(request,  offset)
+    elif request.method == 'DELETE':
+        #return HttpResponse("delete router")
+        return delete_router(request, offset)
+    elif request.method == 'PUT':
+        #return HttpResponse("put router")
+        return update_router(request,offset)
+    else:
+        return api.api_method_not_allowed(request)
+
+
+def rinterface_demux(request, offset1, offset2):
+    if request.method == 'PUT':
+        if offset2 == "add_router_interface":
+            #return HttpResponse("add interface")
+            return add_interface(request, offset2)
+        elif offset2 == "remove_router_interface":
+            #return HttpResponse("remove interface")
+            return remove_interface(request, offset22)
+    else:
+        return api.api_method_not_allowed(request)
+
+
+@api.api_method(http_method='GET', user_required=True, logger=log)
+def list_routers(request):
+    log.debug('list_routers')
+
+    user_routers = VirtualMachine.objects.filter(userid=request.user_uniq, router=True)
+
+    user_routers = utils.filter_modified_since(request, objects=user_routers)
+
+    routers = [router_to_dict(router)
+               for router in user_routers.order_by('id')]
+
+    if request.serialization == 'xml':
+        data = render_to_string('list_routers.xml', {
+            'routers': routers})
+    else:
+        data = json.dumps({"routers": routers})
+
+    return HttpResponse(data, status=200)
+
+'''
+@api.api_method(http_method='POST', user_required=True, logger=log)
+@transaction.commit_manually
+def create_router(request):
+    user_id = request.user_uniq
+    req = utils.get_request_dict(request)
+
+    try:
+        server = req['router']
+        name = server['name']
+    except (KeyError, AssertionError):
+        raise api.faults.BadRequest("Malformed request")
+    metadata = server.get('metadata', {})
+    assert isinstance(metadata, dict)
+    image_id = server['imageRef']
+    flavor_id = server['flavorRef']
+    personality = server.get('personality', [])
+    assert isinstance(personality, list)
+    private_networks = server.get("networks", [])
+    assert isinstance(private_networks, list)
+    floating_ips = server.get("floating_ips", [])
+    assert isinstance(floating_ips, list)
+    # Verify that personalities are well-formed
+    util.verify_personality(personality)
+    # Get image information
+    image = util.get_image_dict(image_id, user_id)
+    # Get flavor (ensure it is active)
+    flavor = util.get_flavor(flavor_id, include_deleted=False)
+    # Generate password
+    password = util.random_password()
+    vm = servers.create(user_id,
+                        name,
+                        #password,
+                        #flavor,
+                        #image,
+                        #metadata=metadata,
+                        #personality=personality,
+                        #private_networks=private_networks,
+                        #floating_ips=floating_ips
+                        )
+
+    server = router_to_dict(vm, detail=True)
+    server['status'] = 'BUILD'
+    server['adminPass'] = password
+
+    response = render_router(request, server, status=202)
+
+    return response
+
+
+'''
+@api.api_method(http_method='GET', user_required=True, logger=log)
+def get_router(request, router_id):
+    log.debug('get_router_details %s', router_id)
+    router = util.get_vm(router_id, request.user_uniq)
+
+    router_dict = router_to_dict(router)
+    return render_router(request, router_dict)
+
+
+@api.api_method(http_method='DELETE', user_required=True, logger=log)
+@transaction.commit_on_success
+def delete_router(request, router_id):
+    log.debug('delete router  %s', router_id)
+    router = util.get_vm(router_id, request.user_uniq)
+
+    if router.neutron_networks.filter(public=False):
+        return HttpResponse("There are internal interfaces on the router", status=409)
+
+    #servers.destroy(router)
+
+    router.deleted = True
+    router.save()
+    return HttpResponse(status=204)
+
+
+@api.api_method(http_method='PUT', user_required=True, logger=log)
+def update_router(request, router_id):
+    log.debug('update router %s', router_id)
+    router = util.get_vm(router_id, request.user_uniq)
+
+    req = utils.get_request_dict(request)
+    try:
+        server = req['router']
+    except (KeyError, AssertionError):
+        raise api.faults.BadRequest("Malformed request")
+
+    try:
+        name = server['name']
+        router.name = name
+    except KeyError:
+        pass
+
+    try:
+        net = server['external_gateway_info']['network_id']
+
+        #find the new network
+        new_net = nv.get_network_fromdb(net, request.user_uniq)
+
+        #disconnect from the old net
+        external_net = router.neutron_networks.filter(public=True)
+        if external_net:
+            #servers.disconnect(router, external_net[0])
+            mock_disconnect(router, external_net[0])
+
+        #connect to the new net
+        #servers.connect(router, new_net)
+        mock_connect(router, new_net)
+    except KeyError:
+        pass
+
+    router.save()
+    routerdict = router_to_dict(router)
+    return render_router(request, routerdict, 200)
+
+
+@api.api_method(http_method='PUT', user_required=True, logger=log)
+def add_interface(request, router_id):
+    log.debug('add interface to router %s', router_id)
+    router = util.get_vm(router_id, request.user_uniq)
+
+    router_info = utils.get_request_dict(request)
+
+    subnet = False
+    try:
+        subnet_id = router_info["subnet_id"]
+        subnet = True
+        subnet_obj = subnet_views.get_subnet_fromdb(subnet_id,
+                                        request.user_unique)
+    except KeyError:
+        pass
+
+    try:
+        port_id = router_info['port_id']
+        if subnet:
+            raise api.faults.BadRequest("Both subnet and port provided")
+        try:
+            nic_obj = models.NetworkInteface.objects.get(id=port_id)
+        except (ValueError, models.NetworkInteraface.DoesNotExist):
+            raise api.faults.ItemNotFound('Port not found')
+        if not nic_obj.ipv4:
+            raise api.faults.BadRequest("No ip-address")
+        if nic_obj.machine:
+            return HttpResponse(status=409)
+    except KeyError:
+        if not subnet:
+            raise api.faults.BadRequest("Malformed request")
+
+    if subnet:
+        #create nic with the subnet and add to the vm
+        nic = models.NetworkInteface(machine=router,
+                                    subnet=subnet_obj,
+                                    network=subnet_obj.network
+                                    ipv4=subnet.gateway
+                                    )
+        nic.save()
+        port_id = nic.id
+    else:
+        #connect the nic
+        nic_obj.machine = router
+        nic_obj.save()
+        subnet_id = nic.subnet.id
+
+    res = {"port_id":port_db.id,
+            "subnet_id":subnet_db.id
+          }
+
+    data = json.dumps(res)
+    return HttpResponse(data, status=200)
+
+@api.api_method(http_method='PUT', user_required=True, logger=log)
+def remove_interface(request, router_id):
+
+    log.debug('remove interface from router %s', router_id)
+    router = util.get_vm(router_id, request.user_uniq)
+
+    info = utils.get_request_dict(request)
+
+    subnet = False
+    try:
+        subnet_id = info["subnet_id"]
+        subnet = True
+        subnet_db = subnet_views.get_subnet_fromdb(subnet_id,
+                                request.user_unique)
+    except KeyError:
+        pass
+
+    port = False
+    try:
+        port_id = info['port_id']
+        port = True
+        try:
+            nic_obj = models.NetworkInteface.objects.get(id=port_id)
+        except (ValueError, models.NetworkInteraface.DoesNotExist):
+            raise api.faults.ItemNotFound('Port not found')
+    except KeyError:
+        pass
+
+    if port and subnet:
+        #validate port-subnet combination else return 409
+        #subnet must be the first of the port
+        if not port_db is subnet_db:
+            return HttpResponse(status=409)
+
+    if subnet:
+        #get the port
+        if not port_db:
+            port_db = subnet_db.neutron_nics.get(machine=router)
+    elif port:
+        #get the subnet
+        if not subnet_db:
+            subnet_db = port_db.subnet
+    else:
+        raise api.faults.BadRequest("Malformed request")
+
+    res = {"id":str(router.id),
+            "tenant_id":request.user_uniq,
+            "port_id":port_db.id,
+            "subnet_id":subnet_db.id
+          }
+
+    port_db.delete()  # need to add backend stuff
+    data = json.dumps(res)
+    return HttpResponse(data, status=200)
+
+
+# util functions
+
+
+def router_to_dict(router):
+    d = {'id': str(router.id), 'name': router.name}
+    d['user_id'] = router.userid
+    d['tenant_id'] = router.userid
+    d['admin_state_up'] = True
+    external_net = router.neutron_networks.filter(public=True)
+    if external_net:
+        external_net = external_net[0]
+        d['external_gateway_info'] = {'network_id': external_net.id}
+    else:
+        d['external_gateway_info'] = None
+    return d
+
+
+def render_router(request, routerdict, status=200):
+    if request.serialization == 'xml':
+        data = render_to_string('router.xml', {'router': routerdict})
+    else:
+        data = json.dumps({'router': routerdict})
+    return HttpResponse(data, status=status)
+
+
+# mock functions
+
+def mock_disconnect(router, net):
+    nic = models.NetworkInterface.objects.get(network=net, machine=router)
+    nic.delete()
+
+def mock_connect(router, net):
+    nic = models.NetworkInterface(network=net, machine=router)
+    nic.save()
diff --git a/snf-cyclades-app/synnefo/neutron/subnet_views.py b/snf-cyclades-app/synnefo/neutron/subnet_views.py
index 803da77..4328471 100644
--- a/snf-cyclades-app/synnefo/neutron/subnet_views.py
+++ b/snf-cyclades-app/synnefo/neutron/subnet_views.py
@@ -162,11 +162,15 @@ def delete_subnet(request, sub_id):
     raise api.faults.BadRequest("Deletion of a subnet is not supported")
 
 
+
 @api.api_method(http_method='PUT', user_required=True, logger=log)
 def update_subnet(request, sub_id):
     '''Update info of a subnet'''
 
 
+# util functions
+
+
 def subnet_to_dict(subnet):
     '''Returns a dictionary containing the info of a subnet'''
     # FIX ME, allocation pools
@@ -184,3 +188,18 @@ def check_number_of_subnets(network, version):
     if network.subnet_set.filter(ipversion=version):
         raise api.faults.BadRequest("Only one subnet of IPv4/IPv6 per "
                                     "network is allowed")
+
+def get_subnet_fromdb(subnet_id, user_id, for_update=False):
+    """
+    Return a Subnet instance or raise ItemNotFound.
+    This is the same as util.get_network
+    """
+    try:
+        subnet_id = int(subnet_id)
+        objects = Subnet.objects
+        if for_update:
+            objects = objects.select_for_update()
+        return objects.get(userid=user_id, id=subnet_id)
+    except (ValueError, Subnet.DoesNotExist):
+        raise api.faults.ItemNotFound('Network not found.')
+
diff --git a/snf-cyclades-app/synnefo/neutron/tests/api.py b/snf-cyclades-app/synnefo/neutron/tests/api.py
index c81e40e..1e0609f 100644
--- a/snf-cyclades-app/synnefo/neutron/tests/api.py
+++ b/snf-cyclades-app/synnefo/neutron/tests/api.py
@@ -10,7 +10,7 @@ import synnefo.db.models_factory as dbmf
 NEUTRON_URL = get_service_path(cyclades_services, "neutron", "v2.0")
 NETWORKS_URL = join_urls(NEUTRON_URL, "networks/")
 SUBNETS_URL = join_urls(NEUTRON_URL, "subnets/")
-
+ROUTERS_URL = join_urls(NEUTRON_URL, "routers/")
 
 class NetworkTest(BaseAPITest):
 
@@ -18,7 +18,7 @@ class NetworkTest(BaseAPITest):
         response = self.get(NETWORKS_URL)
         self.assertSuccess(response)
         networks = json.loads(response.content)
-        self.assertEqual(networks, {'networks': []})
+        self.assertEqual(networks, {"networks": []})
 
     def test_create_network(self):
         request = {
@@ -99,7 +99,7 @@ class NetworkTest(BaseAPITest):
         url = join_urls(NETWORKS_URL, str(test_net.id))
         request = {
             "network": {
-                "wronng_field": "new_name"}
+                "wrong_field": "new_name"}
         }
         response = self.put(url, params=json.dumps(request),
                             user=test_net.userid)
@@ -112,6 +112,69 @@ class NetworkTest(BaseAPITest):
         self.assertEqual(response.status_code, 400)
 
 
+class RouterTest(BaseAPITest):
+    def test_list_empty_routers(self):
+        response = self.get(ROUTERS_URL)
+        self.assertSuccess(response)
+        routers = json.loads(response.content)
+        self.assertEqual(routers, {"routers": []})
+
+    def test_create_router(self):
+        pass
+
+    def test_get_router(self):
+        router = dbmf.VirtualMachineFactory.create(router=True)
+        net = mf.NetworkFactory.create(public=True)
+        nic = mf.NetworkInterfaceFactory.create(network=net, machine=router)
+        response = self.get(ROUTERS_URL, user=router.userid)
+        self.assertSuccess(response)
+
+    def test_delete_router(self):
+        router = dbmf.VirtualMachineFactory.create(router=True)
+        url = join_urls(ROUTERS_URL, str(router.id))
+        response = self.delete(url, user=router.userid)
+        print response.content
+        self.assertEqual(response.status_code, 204)
+
+    def test_delete_router_with_private_net(self):
+        router = dbmf.VirtualMachineFactory.create(router=True)
+        net = mf.NetworkFactory.create(public=False)
+        nic = mf.NetworkInterfaceFactory.create(network=net, machine=router)
+        url = join_urls(ROUTERS_URL, str(router.id))
+        response = self.delete(url, user=router.userid)
+        print response.content
+        self.assertEqual(response.status_code, 409)
+
+    def test_update_router(self):
+        router = dbmf.VirtualMachineFactory.create(router=True)
+        net1 = mf.NetworkFactory.create(public=True)
+        net2 = mf.NetworkFactory.create(public=True)
+        nic = mf.NetworkInterfaceFactory.create(network=net1, machine=router)
+        request = {"router":{
+                        "name": "new_name",
+                        "external_gateway_info":{
+                           "network_id": net2.id
+                           }
+                    }
+            }
+        url = join_urls(ROUTERS_URL, str(router.id))
+        response = self.put(url, params=json.dumps(request), user=router.userid)
+        info = json.loads(response.content)
+        self.assertEqual(info['router']['external_gateway_info']['network_id'], net2.id)
+        self.assertEqual(info['router']['name'], "new_name")
+
+    def test_remove_interface(self):
+        url = join_urls(join_urls(ROUTERS_URL,"123"), "remove_router_interface")
+        response = self.put(url, params="")
+        print response.content
+
+    def test_add_interface(self):
+        url = join_urls(join_urls(ROUTERS_URL,"123"), "add_router_interface")
+        request =  {"port_id": "a2f1f29d-571b-4533-907f-5803ab96ead1","subnet_id": "a2f1f29d-571b-4533-907f-5803ab96ead1"}
+        print json.dumps(request)
+        response = self.put(url, params=json.dumps(request))
+        print response.content
+
 class SubnetTest(BaseAPITest):
     def test_list_subnets(self):
         '''Test list subnets without data'''
diff --git a/snf-cyclades-app/synnefo/neutron/urls.py b/snf-cyclades-app/synnefo/neutron/urls.py
index 6bf047e..c7ba9b6 100644
--- a/snf-cyclades-app/synnefo/neutron/urls.py
+++ b/snf-cyclades-app/synnefo/neutron/urls.py
@@ -45,7 +45,10 @@ neutron_v2_patterns = patterns(
     (r'^subnets(?:/|.json|.xml)?$', 'subnet_views.demux'),
     (r'^subnets/(\w+)(?:/|.json|.xml)?$', 'subnet_views.subnet_demux'),
     (r'^ports(?:/|.json|.xml)?$', 'port_views.demux'),
-    (r'^ports/(\w+)(?:/|.json|.xml)?$', 'port_views.port_demux'))
+    (r'^ports/(\w+)(?:/|.json|.xml)?$', 'port_views.port_demux'),
+    (r'^routers(?:/|.json|.xml)?$', 'router_views.demux'),
+    (r'^routers/(\w+)(?:/|.json|.xml)?$', 'router_views.router_demux'),
+    (r'^routers/(\w+)/(remove_router_interface|add_router_interface)$', 'router_views.rinterface_demux'))
 
 urlpatterns = patterns(
     '',
