commit fd65ab416f24dfbd1bb96f2a9f042d586c4dc18c
Author: Christos Stavrakakis <cstavr@grnet.gr>
Date:   Wed Jun 20 12:03:20 2012 +0300

    Implement BackendNetwork model
    
    Create a new model, to represent the state of each network in each
    backend.

diff --git a/snf-cyclades-app/synnefo/db/migrations/0039_auto__add_backendnetwork__del_field_network_backendtime__del_field_net.py b/snf-cyclades-app/synnefo/db/migrations/0039_auto__add_backendnetwork__del_field_network_backendtime__del_field_net.py
new file mode 100644
index 0000000..048f469
--- /dev/null
+++ b/snf-cyclades-app/synnefo/db/migrations/0039_auto__add_backendnetwork__del_field_network_backendtime__del_field_net.py
@@ -0,0 +1,204 @@
+# encoding: 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 model 'BackendNetwork'
+        db.create_table('db_backendnetwork', (
+            ('backendtime', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(1, 1, 1, 0, 0))),
+            ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
+            ('network', self.gf('django.db.models.fields.related.ForeignKey')(related_name='backend_networks', to=orm['db.Network'])),
+            ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+            ('deleted', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),
+            ('backendopcode', self.gf('django.db.models.fields.CharField')(max_length=30, null=True)),
+            ('operstate', self.gf('django.db.models.fields.CharField')(default='PENDING', max_length=30)),
+            ('backendjobid', self.gf('django.db.models.fields.PositiveIntegerField')(null=True)),
+            ('backendlogmsg', self.gf('django.db.models.fields.TextField')(null=True)),
+            ('backendjobstatus', self.gf('django.db.models.fields.CharField')(max_length=30, null=True)),
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('backend', self.gf('django.db.models.fields.related.ForeignKey')(related_name='networks', to=orm['db.Backend'])),
+        ))
+        db.send_create_signal('db', ['BackendNetwork'])
+
+        # Deleting field 'Network.backendtime'
+        db.delete_column('db_network', 'backendtime')
+
+        # Deleting field 'Network.backendopcode'
+        db.delete_column('db_network', 'backendopcode')
+
+        # Deleting field 'Network.backendlogmsg'
+        db.delete_column('db_network', 'backendlogmsg')
+
+        # Deleting field 'Network.backendjobid'
+        db.delete_column('db_network', 'backendjobid')
+
+        # Deleting field 'Network.backendjobstatus'
+        db.delete_column('db_network', 'backendjobstatus')
+
+        # Changing field 'Network.mac_prefix'
+        db.alter_column('db_network', 'mac_prefix', self.gf('django.db.models.fields.CharField')(max_length=32, null=True))
+
+        # Changing field 'Network.state'
+        db.alter_column('db_network', 'state', self.gf('django.db.models.fields.CharField')(max_length=32))
+
+        # Changing field 'Network.action'
+        db.alter_column('db_network', 'action', self.gf('django.db.models.fields.CharField')(max_length=32, null=True))
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting model 'BackendNetwork'
+        db.delete_table('db_backendnetwork')
+
+        # Adding field 'Network.backendtime'
+        db.add_column('db_network', 'backendtime', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(1, 1, 1, 0, 0)), keep_default=False)
+
+        # Adding field 'Network.backendopcode'
+        db.add_column('db_network', 'backendopcode', self.gf('django.db.models.fields.CharField')(max_length=30, null=True), keep_default=False)
+
+        # Adding field 'Network.backendlogmsg'
+        db.add_column('db_network', 'backendlogmsg', self.gf('django.db.models.fields.TextField')(null=True), keep_default=False)
+
+        # Adding field 'Network.backendjobid'
+        db.add_column('db_network', 'backendjobid', self.gf('django.db.models.fields.PositiveIntegerField')(null=True), keep_default=False)
+
+        # Adding field 'Network.backendjobstatus'
+        db.add_column('db_network', 'backendjobstatus', self.gf('django.db.models.fields.CharField')(max_length=30, null=True), keep_default=False)
+
+        # Changing field 'Network.mac_prefix'
+        db.alter_column('db_network', 'mac_prefix', self.gf('django.db.models.fields.CharField')(max_length=128, null=True))
+
+        # Changing field 'Network.state'
+        db.alter_column('db_network', 'state', self.gf('django.db.models.fields.CharField')(max_length=30))
+
+        # Changing field 'Network.action'
+        db.alter_column('db_network', 'action', self.gf('django.db.models.fields.CharField')(max_length=30, null=True))
+    
+    
+    models = {
+        'db.backend': {
+            'Meta': {'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', 'blank': 'True'}),
+            'dtotal': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'hash': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '64', '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': {'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', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            '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.bridgepool': {
+            'Meta': {'object_name': 'BridgePool'},
+            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'index': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}),
+            'value': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+        },
+        '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', 'blank': 'True'}),
+            'disk': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'disk_template': ('django.db.models.fields.CharField', [], {'default': "'drbd'", 'max_length': '32'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ram': ('django.db.models.fields.IntegerField', [], {'default': '0'})
+        },
+        'db.macprefixpool': {
+            'Meta': {'object_name': 'MacPrefixPool'},
+            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'index': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}),
+            'value': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+        },
+        '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', 'blank': 'True'}),
+            'dhcp': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'gateway': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'link': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+            'mac_prefix': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'machines': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['db.VirtualMachine']", 'through': "orm['db.NetworkInterface']", 'symmetrical': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'public': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'state': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '32'}),
+            'subnet': ('django.db.models.fields.CharField', [], {'default': "'10.0.0.0/24'", 'max_length': '32'}),
+            'type': ('django.db.models.fields.CharField', [], {'default': "'PRIVATE_VLAN'", 'max_length': '50'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'userid': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'})
+        },
+        'db.networkinterface': {
+            'Meta': {'object_name': 'NetworkInterface'},
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            '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': '17', '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']"}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+        },
+        'db.virtualmachine': {
+            'Meta': {'object_name': 'VirtualMachine'},
+            'action': ('django.db.models.fields.CharField', [], {'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', 'blank': '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', [], {'max_length': '30', 'null': 'True'}),
+            'suspended': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'userid': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        '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']
diff --git a/snf-cyclades-app/synnefo/db/models.py b/snf-cyclades-app/synnefo/db/models.py
index 31d801b..9124916 100644
--- a/snf-cyclades-app/synnefo/db/models.py
+++ b/snf-cyclades-app/synnefo/db/models.py
@@ -184,10 +184,22 @@ class Backend(models.Model):
                                  "with backend: %s" % self)
         else:
             # ON_DELETE = SET NULL
-            self.virtual_machines.all().backend=None
+            self.virtual_machines.all().backend = None
             super(Backend, self).delete(*args, **kwargs)
 
 
+# A backend job may be in one of the following possible states
+BACKEND_STATUSES = (
+    ('queued', 'request queued'),
+    ('waiting', 'request waiting for locks'),
+    ('canceling', 'request being canceled'),
+    ('running', 'request running'),
+    ('canceled', 'request canceled'),
+    ('success', 'request completed successfully'),
+    ('error', 'request returned error')
+)
+
+
 class VirtualMachine(models.Model):
     # The list of possible actions for a VM
     ACTIONS = (
@@ -230,16 +242,6 @@ class VirtualMachine(models.Model):
         ('OP_INSTANCE_FAILOVER', 'Failover Instance')
     )
 
-    # A backend job may be in one of the following possible states
-    BACKEND_STATUSES = (
-        ('queued', 'request queued'),
-        ('waiting', 'request waiting for locks'),
-        ('canceling', 'request being canceled'),
-        ('running', 'request running'),
-        ('canceled', 'request canceled'),
-        ('success', 'request completed successfully'),
-        ('error', 'request returned error')
-    )
 
     # The operating state of a VM,
     # upon the successful completion of a backend operation.
@@ -406,31 +408,6 @@ class Network(models.Model):
        ('DESTROY', 'Destroy Network'),
     )
 
-    # The list of possible operations on the backend
-    BACKEND_OPCODES = (
-        ('OP_NETWORK_ADD', 'Create Network'),
-        ('OP_NETWORK_CONNECT', 'Activate Network'),
-        ('OP_NETWORK_DISCONNECT', 'Deactivate Network'),
-        ('OP_NETWORK_REMOVE', 'Remove Network')
-    )
-
-    # A backend job may be in one of the following possible states
-    BACKEND_STATUSES = (
-        ('success', 'request completed successfully'),
-        ('error', 'request returned error')
-    )
-
-    # The operating state of a Network,
-    # upon the successful completion of a backend operation.
-    # IMPORTANT: Make sure all keys have a corresponding
-    # entry in BACKEND_OPCODES if you update this field, see #1035, #1111.
-    OPER_STATE_FROM_OPCODE = {
-        'OP_NETWORK_ADD': 'PENDING',
-        'OP_NETWORK_CONNECT': 'ACTIVE',
-        'OP_NETWORK_DISCONNECT': 'PENDING',
-        'OP_NETWORK_REMOVE': 'DELETED'
-    }
-
     RSAPI_STATE_FROM_OPER_STATE = {
         'PENDING': 'PENDING',
         'ACTIVE': 'ACTIVE',
@@ -445,9 +422,9 @@ class Network(models.Model):
     )
 
     NETWORK_TAGS = {
-        'PUBLIC_ROUTED': ['public-routed'],
-        'PRIVATE_VLAN': ['private-vlan'],
-        'PRIVATE_FILTERED': ['private-filtered']
+        'PUBLIC_ROUTED': ['ip-less-routed'],
+        'PRIVATE_VLAN': ['physical-vlan'],
+        'PRIVATE_FILTERED': ['mac-filtered']
     }
 
     name = models.CharField('Network Name', max_length=128)
@@ -457,62 +434,45 @@ class Network(models.Model):
     dhcp = models.BooleanField('DHCP', default=True)
     type = models.CharField(choices=NETWORK_TYPES, max_length=50, default='PRIVATE_VLAN')
     link = models.CharField('Network Link', max_length=128, null=True)
-    mac_prefix = models.CharField('MAC Prefix', max_length=128, null=True)
+    mac_prefix = models.CharField('MAC Prefix', max_length=32, null=True)
     public = models.BooleanField(default=False)
     created = models.DateTimeField(auto_now_add=True)
     updated = models.DateTimeField(auto_now=True)
     deleted = models.BooleanField('Deleted', default=False)
-    state = models.CharField(choices=OPER_STATES, max_length=30, default='PENDING')
+    state = models.CharField(choices=OPER_STATES, max_length=32,
+                             default='PENDING')
     machines = models.ManyToManyField(VirtualMachine,
                                       through='NetworkInterface')
-
-    action = models.CharField(choices=ACTIONS, max_length=30, null=True)
-    backendjobid = models.PositiveIntegerField(null=True)
-    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30,
-                                     null=True)
-    backendjobstatus = models.CharField(choices=BACKEND_STATUSES,
-                                        max_length=30, null=True)
-    backendlogmsg = models.TextField(null=True)
-    backendtime = models.DateTimeField(default=datetime.datetime.min)
-
+    action = models.CharField(choices=ACTIONS, max_length=32, null=True,
+                              default=None)
 
     class InvalidBackendIdError(Exception):
-         def __init__(self, value):
+        def __init__(self, value):
             self.value = value
-         def __str__(self):
+
+        def __str__(self):
             return repr(self.value)
 
 
     class InvalidBackendMsgError(Exception):
-         def __init__(self, opcode, status):
+        def __init__(self, opcode, status):
             self.opcode = opcode
             self.status = status
-         def __str__(self):
+
+        def __str__(self):
             return repr('<opcode: %s, status: %s>' % (self.opcode,
                     self.status))
 
     class InvalidActionError(Exception):
-         def __init__(self, action):
+        def __init__(self, action):
             self._action = action
-         def __str__(self):
-            return repr(str(self._action))
 
-    def __init__(self, *args, **kw):
-        """Initialize state for just created Network instances."""
-        super(Network, self).__init__(*args, **kw)
-        # This gets called BEFORE an instance gets save()d for
-        # the first time.
-        if not self.pk:
-            self.action = None
-            self.backendjobid = None
-            self.backendjobstatus = None
-            self.backendopcode = None
-            self.backendlogmsg = None
-            self.backendtime = datetime.datetime.min
+        def __str__(self):
+            return repr(str(self._action))
 
     @property
     def backend_id(self):
-        """Returns the backend id for this Network by prepending backend-prefix."""
+        """Return the backend id by prepending backend-prefix."""
         if not self.id:
             raise Network.InvalidBackendIdError("self.id is None")
         return '%s%s' % (settings.BACKEND_PREFIX_ID, self.id)
@@ -527,6 +487,96 @@ class Network(models.Model):
     def __unicode__(self):
         return self.name
 
+    def update_oper_state(self):
+        """Update operstate of the Network.
+
+        Update the operstate of the Network depending on the related
+        backend_networks. When backend networks do not have the same operstate,
+        the Network's operstate is PENDING. Otherwise it is the same with
+        the BackendNetworks operstate.
+
+        """
+        backend_states = [s.operstate for s in self.backend_networks.all()]
+        if not backend_states:
+            self.operstate = 'PENDING'
+            self.save()
+            return
+
+        all_equal = len(set(backend_states)) <= 1
+        self.operstate = all_equal and backend_states[0] or 'PENDING'
+
+        if self.operstate == 'DELETED':
+            self.deleted = True
+
+        self.save()
+
+    def save(self, *args, **kwargs):
+        pk = self.pk
+        super(Network, self).save(*args, **kwargs)
+        if not pk:
+            # In case of a new Network, corresponding BackendNetwork's must
+            # be created!
+            for back in Backend.objects.all():
+                BackendNetwork.objects.create(backend=back, network=self)
+
+
+class BackendNetwork(models.Model):
+    OPER_STATES = (
+        ('PENDING', 'Pending'),
+        ('ACTIVE', 'Active'),
+        ('DELETED', 'Deleted'),
+        ('ERROR', 'Error')
+    )
+
+    # The list of possible operations on the backend
+    BACKEND_OPCODES = (
+        ('OP_NETWORK_ADD', 'Create Network'),
+        ('OP_NETWORK_CONNECT', 'Activate Network'),
+        ('OP_NETWORK_DISCONNECT', 'Deactivate Network'),
+        ('OP_NETWORK_REMOVE', 'Remove Network'),
+        # These are listed here for completeness,
+        # and are ignored for the time being
+        ('OP_NETWORK_SET_PARAMS', 'Set Network Parameters'),
+        ('OP_NETWORK_QUERY_DATA', 'Query Network Data')
+    )
+
+    # The operating state of a Netowork,
+    # upon the successful completion of a backend operation.
+    # IMPORTANT: Make sure all keys have a corresponding
+    # entry in BACKEND_OPCODES if you update this field, see #1035, #1111.
+    OPER_STATE_FROM_OPCODE = {
+        'OP_NETWORK_ADD': 'PENDING',
+        'OP_NETWORK_CONNECT': 'ACTIVE',
+        'OP_NETWORK_DISCONNECT': 'PENDING',
+        'OP_NETWORK_REMOVE': 'DELETED',
+        'OP_NETWORK_SET_PARAMS': None,
+        'OP_NETWORK_QUERY_DATA': None
+    }
+
+    network = models.ForeignKey(Network, related_name='backend_networks')
+    backend = models.ForeignKey(Backend, related_name='networks')
+    created = models.DateTimeField(auto_now_add=True)
+    updated = models.DateTimeField(auto_now=True)
+    deleted = models.BooleanField('Deleted', default=False)
+    operstate = models.CharField(choices=OPER_STATES, max_length=30,
+                                 default='PENDING')
+    backendjobid = models.PositiveIntegerField(null=True)
+    backendopcode = models.CharField(choices=BACKEND_OPCODES, max_length=30,
+                                     null=True)
+    backendjobstatus = models.CharField(choices=BACKEND_STATUSES,
+                                        max_length=30, null=True)
+    backendlogmsg = models.TextField(null=True)
+    backendtime = models.DateTimeField(null=False,
+                                       default=datetime.datetime.min)
+
+    def save(self, *args, **kwargs):
+        super(BackendNetwork, self).save(*args, **kwargs)
+        self.network.update_oper_state()
+
+    def delete(self, *args, **kwargs):
+        super(BackendNetwork, self).delete(*args, **kwargs)
+        self.network.update_oper_state()
+
 
 class NetworkInterface(models.Model):
     FIREWALL_PROFILES = (
diff --git a/snf-cyclades-app/synnefo/logic/backend.py b/snf-cyclades-app/synnefo/logic/backend.py
index 77a031b..7b8ebf3 100644
--- a/snf-cyclades-app/synnefo/logic/backend.py
+++ b/snf-cyclades-app/synnefo/logic/backend.py
@@ -38,7 +38,8 @@ from django.conf import settings
 from django.db import transaction
 from datetime import datetime
 
-from synnefo.db.models import (Backend, VirtualMachine, Network, NetworkLink)
+from synnefo.db.models import (Backend, VirtualMachine, Network, NetworkLink,
+                               BACKEND_STATUSES)
 from synnefo.logic import utils
 from synnefo.util.rapi import GanetiRapiClient
 
@@ -69,7 +70,7 @@ def process_op_status(vm, etime, jobid, opcode, status, logmsg):
     """
     # See #1492, #1031, #1111 why this line has been removed
     #if (opcode not in [x[0] for x in VirtualMachine.BACKEND_OPCODES] or
-    if status not in [x[0] for x in VirtualMachine.BACKEND_STATUSES]:
+    if status not in [x[0] for x in BACKEND_STATUSES]:
         raise VirtualMachine.InvalidBackendMsgError(opcode, status)
 
     vm.backendjobid = jobid
