Base net-virt CLI files on top of which ONOS specific changes will be done
diff --git a/cli/sdncon/controller/__init__.py b/cli/sdncon/controller/__init__.py
new file mode 100755
index 0000000..ce48615
--- /dev/null
+++ b/cli/sdncon/controller/__init__.py
@@ -0,0 +1,21 @@
+#
+# Copyright (c) 2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+#      http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+
+from sdncon.controller.notification import init_notifications
+from sdncon.controller.config import init_config
+
+init_notifications()
+init_config()
diff --git a/cli/sdncon/controller/admin.py b/cli/sdncon/controller/admin.py
new file mode 100755
index 0000000..7f8aebf
--- /dev/null
+++ b/cli/sdncon/controller/admin.py
@@ -0,0 +1,22 @@
+#
+# Copyright (c) 2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+#      http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+
+from django.contrib import admin
+from .models import Switch, StaticFlowTableEntry, HostConfig
+
+#admin.site.register(Switch)
+#admin.site.register(StaticFlowTableEntry)
+#admin.site.register(Host)
diff --git a/cli/sdncon/controller/bsn-tacplus-envfilter b/cli/sdncon/controller/bsn-tacplus-envfilter
new file mode 100755
index 0000000..e8671b3
--- /dev/null
+++ b/cli/sdncon/controller/bsn-tacplus-envfilter
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+
+import os, sys
+
+PAM_SUCCESS = 0  # Successful function return
+PAM_SYSTEM_ERR = 4  # System error
+PAM_AUTH_ERR = 7 # Authentication failure
+
+# test AV pairs in os.environ here
+
+sys.exit(PAM_SUCCESS)
diff --git a/cli/sdncon/controller/config.py b/cli/sdncon/controller/config.py
new file mode 100755
index 0000000..36dcd16
--- /dev/null
+++ b/cli/sdncon/controller/config.py
@@ -0,0 +1,284 @@
+#
+# Copyright (c) 2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+#      http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+
+from sdncon.rest.config import add_config_handler
+from sdncon.controller.models import Feature, GlobalConfig, Controller, \
+    ControllerInterface, ControllerDomainNameServer, \
+    FirewallRule, ControllerAlias, SnmpServerConfig, ImageDropUser
+from sdncon.controller.models import TacacsPlusConfig, TacacsPlusHost
+from oswrapper import exec_os_wrapper
+import os
+import re
+import sdncon
+from django.core import serializers
+
+# FIXME: Can probably get rid of default_id when the rest of the code is
+# in place for supporting multiple controller IDs. But what about
+# unit tests where we shouldn't rely on the boot-config file existing
+def get_local_controller_id(default_id='localhost'):
+    local_controller_id = default_id
+    f = None
+    try:
+        f = open("%s/run/boot-config" % sdncon.SDN_ROOT, 'r')
+        data = f.read()
+        match = re.search("^controller-id=([0-9a-zA-Z\-]*)$", data, re.MULTILINE)
+        if match:
+            local_controller_id = match.group(1)
+    except Exception, _e:
+        # If there was any error, then just leave the controller ID as the
+        # default value.
+        pass
+    finally:
+        if f:
+            f.close()
+    return local_controller_id
+
+
+# Add the config handlers here. Check the comments for add_config_handler in rest/config.py
+# for a description of the calling conventions for config handlers.
+
+def network_config_handler(op, old_instance, new_instance, modified_fields):
+    valid_instance = old_instance if (op == 'DELETE') else new_instance
+    if isinstance(valid_instance, Controller):
+        controller_node = valid_instance
+        controller_id = controller_node.id
+        if op == 'DELETE':
+            # no further configuration here
+            return
+    elif isinstance(valid_instance, ControllerDomainNameServer) \
+      or isinstance(valid_instance, ControllerInterface):
+        controller_id = valid_instance.controller_id
+        try:
+            controller_node = Controller.objects.get(pk=controller_id)
+        except Exception, _e:
+            # unknown controller node during delete, no need to
+            # do anything with any of these interfaces
+            return
+    else:
+        raise Exception('Unknown model change trigger network config handler')
+
+    if controller_id != get_local_controller_id():
+        return
+
+    if op == 'DELETE':
+        # don't reconfigure the interfaces during delete, since
+        # for deletes, the values of ip/netmask don't get updated
+        dns = ControllerDomainNameServer.objects.filter(
+                        controller=controller_node).order_by('timestamp')
+        exec_os_wrapper('NetworkConfig', 'set', 
+                        [serializers.serialize("json", [controller_node]),
+                         serializers.serialize("json", dns)])
+    else:
+        # op != 'DELETE'
+        #
+        # XXX what about HA?
+        # 'ifs' below isn't filtered by controller, the NetConfig
+        # target will only select interfaces assocaited with the
+        # controller-node 'localhost.
+        dns = ControllerDomainNameServer.objects.filter(
+                        controller=controller_node).order_by('-priority')
+        ifs = ControllerInterface.objects.filter(controller=controller_node)
+        exec_os_wrapper('NetworkConfig', 'set', 
+                        [serializers.serialize("json", [controller_node]),
+                         serializers.serialize("json", dns),
+                         serializers.serialize("json", ifs)])
+
+def firewall_entry_handler(op, old_instance, new_instance, modified_fields=None):
+    #allow in on eth0 proto tcp from any to any port 80
+    print "XXX: firewall handler called-1" 
+    command = ""
+    if op == "DELETE" and str(old_instance.interface.controller) == get_local_controller_id():
+        command += "delete "
+        instance = old_instance
+    elif (op == "INSERT" or op == "UPDATE") and str(new_instance.interface.controller) == get_local_controller_id():
+        instance = new_instance
+    else:
+        return
+
+    print instance.action
+    print instance.proto
+    print instance.port
+    print instance.src_ip
+    print instance.vrrp_ip
+    print "XXX: firewall handler called-2" 
+    controller_interface = instance.interface
+    eth = 'eth' + str(controller_interface.number) #LOOK! Hardcoded to eth interface
+    proto_str = ""
+    if instance.proto != '' and instance.proto != 'vrrp':
+        proto_str = " proto " + instance.proto
+    action_str = instance.action
+    src_str = " from any"
+    if instance.src_ip != '':
+        src_str = " from " + instance.src_ip
+    dst_str = " to any"
+    if instance.vrrp_ip != '':
+        dst_str = " to " + instance.vrrp_ip 
+    print "dst_str = ", dst_str
+    port_str = ""
+    if instance.port != 0:
+        port_str = " port " + str(instance.port)
+
+    command += (action_str + " in on " + eth + proto_str + src_str + dst_str + port_str)
+    print command
+
+    exec_os_wrapper('ExecuteUfwCommand', 'set', [command])
+    if instance.port == 6633 and action_str == 'reject' and op != 'DELETE':
+        exec_os_wrapper('RestartSDNPlatform', 'set', [])
+
+def ntp_config_handler(op, old_instance, new_instance, modified_fields=None):
+    if new_instance != None:
+        exec_os_wrapper("SetNtpServer", 'set', [new_instance.ntp_server])
+
+def tz_config_handler(op, old_instance, new_instance, modified_fields=None):
+    if op == "DELETE":
+        if str(old_instance.id) != get_local_controller_id():
+            return
+        exec_os_wrapper("UnsetTimezone", 'set')
+    elif op == "INSERT" or op == "UPDATE":
+        if str(new_instance.id) != get_local_controller_id():
+            return
+        if new_instance.time_zone != None and str(new_instance.time_zone) != "":
+            exec_os_wrapper("SetTimezone", 'set', [new_instance.time_zone])
+
+def logging_server_config_handler(op, old_instance, new_instance, modified_fields=None): 
+    if op == "DELETE":
+        if str(old_instance.id) != get_local_controller_id():
+            return
+        exec_os_wrapper("UnsetSyslogServer", 'set',
+                        [old_instance.logging_server, old_instance.logging_level])
+    elif op == "INSERT" or op == "UPDATE":
+        if str(new_instance.id) != get_local_controller_id():
+            return
+        if new_instance.logging_server != "" and new_instance.logging_enabled:
+            exec_os_wrapper("SetSyslogServer", 'set',
+                            [new_instance.logging_server, new_instance.logging_level])
+        else:
+            exec_os_wrapper("UnsetSyslogServer", 'set',
+                            [new_instance.logging_server, new_instance.logging_level])
+
+def vrrp_virtual_router_id_config_handle(op, old_instance, new_instance, modified_fields=None):
+    if op == "INSERT" or op == "UPDATE":
+        exec_os_wrapper("SetVrrpVirtualRouterId", 'set',
+                        [new_instance.cluster_number])
+    
+def netvirt_feature_config_handler(op, old_instance, new_instance, modified_fields=None):
+    if op == "INSERT" or op == "UPDATE":
+        if new_instance.netvirt_feature:
+            exec_os_wrapper("SetDefaultConfig", 'set', [new_instance.netvirt_feature])
+        else:
+            exec_os_wrapper("SetStaticFlowOnlyConfig", 'set', [new_instance.netvirt_feature])
+
+def controller_alias_config_handler(op, old_instance, new_instance, modified_fields=None):
+    if op == 'INSERT' or op == 'UPDATE':
+        if str(new_instance.controller) == get_local_controller_id():
+            exec_os_wrapper("SetHostname", 'set', [new_instance.alias])
+
+def tacacs_plus_config_handler(op, old_instance, new_instance, modified_fields=None):
+
+    if isinstance(old_instance, TacacsPlusConfig):
+        if op == 'DELETE':
+            # deleting the config singleton (presumably during shutdown?)
+            return
+
+    if isinstance(old_instance, TacacsPlusConfig):
+        config_id = old_instance.id
+    else:
+        config_id = 'tacacs'
+    try:
+        config = TacacsPlusConfig.objects.get(pk=config_id)
+    except TacacsPlusConfig.DoesNotExist:
+        # cons up a dummy config object, not necessary to save it
+        config = TacacsPlusConfig()
+
+    # get current list of hosts (op==DELETE ignored here)
+    ##hosts = TacacsPlusHost.objects.order_by('timestamp')
+    def timestampSort(h1, h2):
+        return cmp(h1.timestamp, h2.timestamp)
+    hosts = sorted(TacacsPlusHost.objects.all(), timestampSort)
+
+    # XXX roth -- config is passed as-is, not as a single-element list
+    cj = serializers.serialize("json", [config])
+    hj = serializers.serialize("json", hosts)
+    print "Calling oswrapper with:", [cj, hj]
+    exec_os_wrapper('TacacsPlusConfig', 'set', [cj, hj])
+        
+def snmp_server_config_handler(op, old_instance, new_instance, modified_fields=None):
+    if op == 'DELETE':
+        exec_os_wrapper('UnsetSnmpServerConfig', 'set', [])
+    elif op == 'INSERT' or op == 'UPDATE':
+        # enable_changed is true if operation is INSERT, else compare with old instance
+        if (op == 'INSERT'):
+            enable_changed = (new_instance.server_enable is True) #since default is False
+            print 'operation= insert, enable_changed = ', enable_changed
+        else:
+            enable_changed = (new_instance.server_enable != old_instance.server_enable)
+        server_enable = new_instance.server_enable 
+        community  = '' if new_instance.community is None else new_instance.community 
+        location = '' if new_instance.location is None else new_instance.location 
+        contact  = '' if new_instance.contact is None else new_instance.contact
+
+        print "Calling oswrapper with:", [server_enable, community, location, contact, enable_changed]
+        exec_os_wrapper('SetSnmpServerConfig', 'set',
+                        [server_enable, community, location, contact, enable_changed])
+
+def test_config_handler(op, old_instance, new_instance, modified_fields=None):
+    pass
+
+def images_user_ssh_key_config_handler(op, old_instance, new_instance, modified_fields=None):
+    if op == 'INSERT' or op == 'UPDATE':
+        sshkey = "\"" + str(new_instance.images_user_ssh_key) + "\""
+        exec_os_wrapper('SetImagesUserSSHKey', 'set', [sshkey])
+
+def init_config():
+    # 
+    # Associate the config handlers with specific callout for each of the fields
+    #  Keep in mind that these are the django names, NOT the rest api names,
+    #
+    disabled_by_shell_variable = os.environ.get('SDNCON_CONFIG_HANDLERS_DISABLED', False)
+    disabled_by_file = os.path.exists("%s/sdncon_config_handlers_disabled" % sdncon.SDN_ROOT)
+    if not disabled_by_shell_variable and not disabled_by_file:
+        add_config_handler({Controller: ['ntp_server']}, ntp_config_handler)
+        add_config_handler({Controller: ['time_zone']}, tz_config_handler)
+        add_config_handler(
+            {
+                Controller: ['domain_lookups_enabled', 'domain_name', 'default_gateway'],
+                ControllerDomainNameServer: None,
+                ControllerInterface: ['ip', 'netmask', 'mode'],
+            }, network_config_handler)
+        add_config_handler({ControllerAlias: ['alias']}, controller_alias_config_handler)
+        add_config_handler({Controller: ['logging_enabled', 'logging_server', 'logging_level']}, logging_server_config_handler)
+        add_config_handler({Feature: ['netvirt_feature']}, netvirt_feature_config_handler)
+        add_config_handler({FirewallRule: None}, firewall_entry_handler)
+        add_config_handler({GlobalConfig: ['cluster_number']}, vrrp_virtual_router_id_config_handle)
+        add_config_handler({ TacacsPlusConfig: ["tacacs_plus_authn", "tacacs_plus_authz", "tacacs_plus_acct",
+                                                "local_authn", "local_authz",
+                                                "timeout", "key",],
+                             TacacsPlusHost: ['ip', 'key'],
+                             },
+                           tacacs_plus_config_handler)
+        add_config_handler({SnmpServerConfig: ['server_enable', 'community', 'location', 'contact']}, snmp_server_config_handler)
+        add_config_handler({ImageDropUser: ['images_user_ssh_key']}, images_user_ssh_key_config_handler)
+    else:
+        add_config_handler(
+            {
+                Controller: ['domain_lookups_enabled', 'domain_name', 'default_gateway'],
+                ControllerDomainNameServer: None,
+                ControllerInterface: ['ip', 'netmask', 'mode'],
+                ControllerAlias: ['alias'],
+                FirewallRule: None,
+                Feature: None,
+                GlobalConfig: ['ha-enabled', 'cluster-number'],
+            }, test_config_handler)
diff --git a/cli/sdncon/controller/firewall.py b/cli/sdncon/controller/firewall.py
new file mode 100755
index 0000000..ccf3d9b
--- /dev/null
+++ b/cli/sdncon/controller/firewall.py
@@ -0,0 +1,68 @@
+#
+# Copyright (c) 2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+#      http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+
+#from sdncon.controller.models import ControllerAclEntry
+
+def map_controller_acl_entry_to_ufw_string(acl_entry, in_acl, interface=None, delete=False):
+    # TODO optimize this method by building an array and then joining it
+    command = "ufw "
+    
+    if delete:
+        command += "delete "
+        
+    if acl_entry['action'] == "permit":
+        command += "allow "
+    else:
+        command += "deny "
+    
+    if in_acl:
+        command += "in "
+    else:
+        command += "out "
+        
+    command += ("on " + interface + " ")
+    
+    if acl_entry['type'] == 'ip':
+        pass
+    elif acl_entry['type'] == 'tcp' or acl_entry['type'] == 'udp':
+        command += ("proto " + acl_entry['type'] + " from ")
+        if acl_entry['src_ip'] != None: # TODO check none
+            command += acl_entry['src_ip']
+            if acl_entry['src_ip_mask'] != None:
+                command += ("/" + acl_entry['src_ip_mask'] + " ")
+            else:
+                command += " "
+        else:
+            command += "any "
+            
+        if acl_entry['src_tp_port_op'] == 'eq':
+            command += ("port " + acl_entry['src_tp_port'] + " ")
+
+        command += "to "
+        if acl_entry['dst_ip'] != None: #TODO check none
+            command += acl_entry['dst_ip']
+            if acl_entry['dst_ip_mask'] != None:
+                command += ("/" + acl_entry['dst_ip_mask'] + " ")
+            else:
+                command += " "
+        else:
+            command += "any "
+            
+        if acl_entry['dst_tp_port_op'] == 'eq':
+            command += ("port " + acl_entry['dst_tp_port'] + " ")
+    return command
+    
+        
diff --git a/cli/sdncon/controller/models.py b/cli/sdncon/controller/models.py
new file mode 100755
index 0000000..c6dcc9f
--- /dev/null
+++ b/cli/sdncon/controller/models.py
@@ -0,0 +1,3225 @@
+#
+# Copyright (c) 2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+#      http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+
+from django.db import models
+from django.contrib.auth.models import User
+
+from django.forms import ValidationError
+from django.core.validators import MaxValueValidator
+from sdncon.rest.validators import *
+
+import sys # for sys.maxint
+import time
+
+#
+# controller/models.py
+# ------------------------------------------------------------
+#
+# model description of the tables used to configure and 
+# view the controller state.
+#
+# these tables are used both by sdnplatform and the rest api.
+# the cli uses the rest api to read and write these tables,
+# while sdnplatform/sdnplatform has a more direct interface to the
+# tables.
+#
+# for the cli, the names of the fields are described in the
+# 'class Rest' section of each of the tables.
+#
+# for sdnplatform the names of fields have a relationship to their
+# type for sdnplatform.   If a field is a foreign key, then an '_id'
+# is appended to the name for the same field within sdnplatform.  This
+# means if a field is promoted or demoted to/from a foreign key,
+# changes need to be made in the sdnplatform code to use the updated
+# name.
+#
+# Make sure to include the nested Rest class or else they won't be
+# accessible using the REST API.
+
+#
+#
+# ------------------------------------------------------------
+
+def get_timestamp():
+    """
+    For us in models where the method of ordering the row values
+    is based on the order of creation.  
+    
+    The field is exposed to the rest api so that the value can
+    be queries, and so that the rest api may choose to order the
+    row values using its own strategy by setting the field directly
+    """
+    return int(time.time()*1000000)
+
+
+#
+# ------------------------------------------------------------
+
+class Feature(models.Model):
+
+    #
+    # fields ----------------------------------------
+
+    id = models.CharField(
+        primary_key=True,
+        verbose_name='feature',
+        default='feature',
+        max_length=16)
+
+    netvirt_feature = models.BooleanField(
+        verbose_name='Network virtualization Feature Enabled',
+        help_text='Enable network virtualization feature by setting to true',
+        default=True
+        )
+
+    static_flow_pusher_feature = models.BooleanField(
+        verbose_name='Static Flow Pusher Feature Enabled',
+        help_text='Enable Static Flow Pusher feature by setting to true',
+        default=True
+        )
+    
+    performance_monitor_feature = models.BooleanField(
+        verbose_name='Performance Monitor',
+        help_text="Enable performance monitoring feature by setting to true",
+        default=False
+        )
+    
+    #
+    # end fields ----------------------------------------
+    
+    def __unicode__(self):
+        return "%s" % (self.id,)
+
+    def validate_unique(self, exclude = None):
+        if self.id != 'feature':
+            raise ValidationError("Only single feature record exists")
+
+    class Rest:
+        NAME = 'feature'
+        FIELD_INFO = (
+            {'name': 'netvirt_feature',                'rest_name': 'netvirt-feature'},
+            {'name': 'static_flow_pusher_feature', 'rest_name': 'static-flow-pusher-feature'},
+            {'name': 'performance_monitor_feature','rest_name': 'performance-monitor-feature'},
+            )
+
+#
+# ------------------------------------------------------------
+
+class GlobalConfig(models.Model):
+    #
+    # fields ----------------------------------------
+    
+    name = models.CharField(
+        primary_key=True,
+        verbose_name='Global Config Name',
+        help_text="Unique name for the global config; it's a singleton, "
+                  "so, by convention, the name should always be \"global\"",
+        default='global',
+        max_length=16)
+    
+    cluster_name = models.CharField(
+        verbose_name='Cluster Name',
+        help_text='Name for the cluster',
+        default='SDNCluster',
+        max_length=256)
+
+    cluster_number = models.IntegerField(
+        verbose_name='Cluster Number',
+        help_text='Small integral (1-255) identifier for the cluster',
+        default=0)
+
+    ha_enabled = models.BooleanField(
+        verbose_name='High Availability (HA) Enabled',
+        help_text='Is high availability (HA) enabled',
+        default=False)
+
+    #
+    # end fields ----------------------------------------
+
+    class Rest:
+        NAME = 'global-config'
+        FIELD_INFO = (
+            {'name': 'cluster_name',    'rest_name': 'cluster-name'},
+            {'name': 'cluster_number',  'rest_name': 'cluster-number'},
+            {'name': 'ha_enabled',      'rest_name': 'ha-enabled'},
+            )
+    
+#
+# ------------------------------------------------------------
+
+class TopologyConfig(models.Model):
+    #
+    # fields ----------------------------------------
+
+    id = models.CharField(
+        primary_key=True,
+        verbose_name='topology',
+        default='topology',
+        max_length=16)
+
+
+    autoportfast = models.BooleanField(
+        verbose_name='Auto PortFast',
+        help_text='Suppress sending LLDPs on fast ports.',
+        default=True
+        )
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return "%s" % (self.id,)
+
+    def validate_unique(self, exclude = None):
+        if self.id != 'topology':
+            raise ValidationError("Only single topology record exists")
+
+    class Rest:
+        NAME = 'topology-config'
+        FIELD_INFO = (
+        )
+
+
+#
+# ------------------------------------------------------------
+
+class ForwardingConfig(models.Model):
+    #
+    # fields ----------------------------------------
+
+    id = models.CharField(
+        primary_key=True,
+        verbose_name='forwarding',
+        default='forwarding',
+        max_length=16)
+
+    access_priority = models.IntegerField(
+        verbose_name='Access Flow Priority',
+        help_text='Priority for flows created by forwarding on access switches',
+        validators=[ RangeValidator(0,2**15-1) ],
+        default=10)
+
+    core_priority = models.IntegerField(
+        verbose_name='Core Flow Priority',
+        help_text='Priority for flows created by forwarding on core switches',
+        validators=[ RangeValidator(0,2**15-1) ],
+        default=20)
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return "%s" % (self.id,)
+
+    def validate_unique(self, exclude = None):
+        if self.id != 'forwarding':
+            raise ValidationError(
+                "Only a single forwarding configuration record is allowed"
+        )
+
+    class Rest:
+        NAME = 'forwarding-config'
+        FIELD_INFO = (
+            {'name': 'access_priority',    'rest_name': 'access-priority'},
+            {'name': 'core_priority',      'rest_name': 'core-priority'},
+        )
+
+
+#
+# ------------------------------------------------------------
+
+class Controller(models.Model):
+    #
+    # fields ----------------------------------------
+    #
+    # Note: This model should only contain config state for the controller VM,
+    # not any operational state.
+    #
+    # Note: The convention used to be that the controller ID was
+    # the IP address and port that the OpenFlow controller was listening
+    # on. In practice SDNPlatform listened on all interfaces and it's logic
+    # for determining its real IP address was unreliable, so it was
+    # changed/simplified to just always use "localhost" for the 
+    # IP address. So the controller ID was pretty much always
+    # "localhost:6633". The use of "controller here caused some
+    # confusion about whether "controller" meant the
+    # OpenFlow controller (i.e. SDNPlatform/SDNPlatform) vs. the controller VM.
+    # In the old controller model most/all of the settings concerned the
+    # OpenFlow controller not the VM>
+    # Most of the settings we now want to associate with the controller are
+    # controller VM settings (e.g. IP address, DNS, time zone) and not
+    # OpenFlow controller settings. So we're repurposing the Controller
+    # model to be the controller VM config state and not the OpenFlow
+    # controller discovered state (which was sort of broken to begin with).
+    # Currently (as of 10/2011), since we only support single node operation
+    # the controller ID is hard-coded to be localhost (probably should be
+    # something different, e.g. "default", because localhost implies a
+    # an interface which is really something separate), but eventually for
+    # multi-node operation we'll need to have unique ids for each controller
+    # node in the cluster. The easiest way would be to have the user enter
+    # something at first time config. Or possibly we could do something
+    # with GUIDs. Needs more thought...
+    id = models.CharField(
+        primary_key=True,
+        verbose_name='Controller ID',
+        help_text='Unique identifier string for the controller node',
+        validators=[ validate_controller_id ],
+        max_length=256)
+    
+    status = models.CharField(
+        verbose_name='Status',
+        help_text='cluster status of node',
+        max_length=256,
+        default='Provisioned',
+        )
+
+    domain_lookups_enabled = models.BooleanField(
+        verbose_name='Domain Lookups Enabled',
+        help_text='If domain lookups are enabled (default is True)',
+        default=True
+        )
+
+    domain_name = models.CharField(
+        verbose_name='Domain Name',
+        help_text='Domain name of the network',
+        max_length=256,
+        validators=[ DomainNameValidator() ],
+        default='',
+        blank=True)
+
+    default_gateway = models.CharField(
+        verbose_name='Default Gateway',
+        help_text='Default gateway',
+        max_length=256,
+        validators=[ IpValidator() ],
+        default='',
+        blank=True)
+    
+    ntp_server = models.CharField(
+        verbose_name='NTP Server',
+        help_text='NTP server',
+        max_length=256,
+        validators=[ IpOrDomainNameValidator() ],
+        default='',
+        blank=True,
+        null=True)
+
+    time_zone = models.CharField(
+        verbose_name='Time Zone',
+        help_text='Time zone (e.g. America/Los_Angeles)',
+        max_length=256,
+        validators=[ TimeZoneValidator() ],
+        default='UTC')
+
+    logging_enabled = models.BooleanField(
+        verbose_name='Logging Enabled',
+        help_text='Logging enabled',
+        default=False
+    )
+    
+    logging_server = models.CharField(
+        verbose_name='Logging Server',
+        help_text='Logging server',
+        max_length=256,
+        validators=[ IpOrDomainNameValidator() ],
+        default='',
+        blank=True,
+        null=True)
+    
+    logging_level = models.CharField(
+        verbose_name='Logging Level',
+        help_text='Logging level',
+        max_length=16,
+        validators=[ EnumerationValidator(('emerg', 'alert', 'crit', 'err',
+            'warning', 'notice', 'info', 'debug', '0', '1', '2', '3', '4',
+            '5', '6', '7'))],
+        default='notice'
+        )
+    
+    # IOS let's you specify the domain name(s) of the network in two
+    # ways. You can specify a single domain name with the "ip domain name <domain-name>"
+    # command. You can specify multiple domain names with multiple
+    # "ip domain list <domain-name>" commands. The domain names specified with
+    # "ip domain list" commands take precedence over the domain name specified with
+    # the "ip domain name" command, so the single "ip domain name" is only
+    # used if the domain name list is unspecified/empty/null. Kind of messy, but
+    # if we want to emulate IOS behavior I think we'll need to represent that in the
+    # model. But to simplify things for now we'll just support the single domain name.
+    
+    #domain_name_list = models.TextField(
+    #    verbose_name='Domain Name List',
+    #    help_text='List of domain names for the network, one per line',
+    #    null=True
+    #    )
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return "%s" % (self.id,)
+
+    class Rest:
+        NAME = 'controller-node'
+        FIELD_INFO = (
+            {'name': 'domain_lookups_enabled',  'rest_name': 'domain-lookups-enabled'},
+            {'name': 'domain_name',             'rest_name': 'domain-name'},
+            {'name': 'default_gateway',         'rest_name': 'default-gateway'},
+            {'name': 'ntp_server',              'rest_name': 'ntp-server'},
+            {'name': 'time_zone',               'rest_name': 'time-zone'},
+            {'name': 'logging_enabled',         'rest_name': 'logging-enabled'},
+            {'name': 'logging_server',          'rest_name': 'logging-server'},
+            {'name': 'logging_level',           'rest_name': 'logging-level'},
+            )
+
+
+#
+# ------------------------------------------------------------
+
+class ControllerAlias(models.Model):
+    
+    #
+    # fields ----------------------------------------
+
+    alias = models.CharField(
+        primary_key=True,
+        max_length=255,
+        help_text = "alias for controller",
+        verbose_name='alias',
+        validators=[SwitchAliasValidator()])
+    
+    controller = models.ForeignKey(
+        Controller,
+        verbose_name='Controller')
+
+    #
+    # end fields ----------------------------------------
+
+    class Rest:
+        NAME = 'controller-alias'
+
+#
+# ------------------------------------------------------------
+
+class ControllerInterface(models.Model):
+    
+    #
+    # fields ----------------------------------------
+    
+    controller = models.ForeignKey(
+        Controller,
+        verbose_name="Controller ID")
+    
+    type = models.CharField(
+        verbose_name='Interface Type',
+        help_text='Type of interface (e.g. "Ethernet")',
+        max_length=256,
+        default='Ethernet'
+        )
+    
+    number = models.IntegerField(
+        verbose_name="Interface Number",
+        help_text='Number of interface (non-negative integer)',
+        default=0)
+    
+    mode = models.CharField(
+        verbose_name='Mode',
+        help_text='Mode of configuring IP address ("dhcp" or "static")',
+        validators=[ EnumerationValidator(('dhcp', 'static'))],
+        max_length=32,
+        default='static')
+    
+    ip = models.CharField(
+        verbose_name='IP Address',
+        help_text='IP Address for interface',
+        validators=[ IpValidator() ],
+        max_length=15,
+        default='',
+        blank=True)
+    
+    netmask = models.CharField(
+        verbose_name='Netmask',
+        help_text='Netmask',
+        validators=[ IpValidator() ],
+        max_length=15,
+        default='',
+        blank=True)
+    
+    # provide a link between the underlying interface layer
+    # and this model's 'index number' to help manage discovered_ip
+    mac = models.CharField(
+        verbose_name="MAC Address",
+        help_text="MAC Address",
+        max_length=17, 
+        validators = [validate_mac_address],
+        blank=True,
+        null=True)
+
+    discovered_ip = models.CharField(
+        verbose_name='Discovered IP Address',
+        help_text='Discovered IP Address for interface',
+        validators=[ IpValidator() ],
+        max_length=15,
+        default='',
+        blank=True)
+    
+
+#    in_acl = models.ForeignKey(
+#        ControllerAcl,
+#        verbose_name = 'Controller input acl',
+#        blank=True,
+#        null=True)
+
+#    out_acl = models.ForeignKey(
+#        ControllerAcl,
+#        verbose_name = 'Controller output acl',
+#        blank=True,
+#        null=True)
+        
+    #
+    # end fields ----------------------------------------
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('controller', 'type', 'number')
+    
+    class Rest:
+        NAME = 'controller-interface'
+        FIELD_INFO = (
+             {'name': 'discovered_ip', 'rest_name': 'discovered-ip'},
+#            {'name': 'in_acl',        'rest_name': 'in-acl'},
+#            {'name': 'out_acl',       'rest_name': 'out-acl'},
+             )
+        
+
+#
+# ------------------------------------------------------------
+
+class FirewallRule(models.Model):
+
+    #
+    # fields ----------------------------------------
+    
+    interface = models.ForeignKey(
+        ControllerInterface,
+        verbose_name="Controller Interface")
+
+    action = models.CharField(
+        max_length=8,
+        validators=[ EnumerationValidator(('allow', 'deny', 'reject'))],
+        default='allow',
+        blank=True)
+
+    src_ip = models.CharField(
+        verbose_name='Source IP',
+        help_text='IP Address to allow traffic from',
+        validators=[ IpValidator() ],
+        max_length=15,
+        default='',
+        blank=True)
+    
+    vrrp_ip = models.CharField(
+        verbose_name='Local IP',
+        help_text='(Local) IP Address to allow traffic to',
+        validators=[ IpValidator() ],
+        max_length=15,
+        default='',
+        blank=True)
+    
+    port = models.IntegerField(
+        verbose_name='Port Number',
+        help_text='Port Number',
+        validators=[ RangeValidator(0,2**16-1) ],
+        default=0,
+        blank=True)
+    
+    proto = models.CharField(
+        verbose_name="ip proto",
+        help_text="ip protocol (tcp, udp or vrrp)", #TODO validator
+        validators=[ UfwProtocolValditor() ],
+        max_length=4,
+        default='',
+        blank=True)
+    
+    #
+    # end fields ----------------------------------------
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('interface', 'src_ip', 'vrrp_ip', 'port', 'proto')
+        
+    class Rest:
+        NAME = 'firewall-rule'
+        FIELD_INFO = (
+             {'name': 'src_ip', 'rest_name': 'src-ip'},
+             {'name': 'vrrp_ip', 'rest_name': 'vrrp-ip'},
+        )
+
+#
+# ------------------------------------------------------------
+
+class ControllerDomainNameServer(models.Model):
+    
+    controller = models.ForeignKey(
+        Controller,
+        verbose_name="Controller ID",
+        default=None,
+        null=True)
+    
+    ip = models.CharField(
+        verbose_name='IP Address',
+        help_text='IP Address of domain name server',
+        validators=[ IpValidator() ],
+        max_length=15,
+        default='')
+
+    timestamp = models.IntegerField(
+        verbose_name='timestamp',
+        help_text='Timestamp to determine order of domain name servers',
+        default = get_timestamp,
+        null=True,
+        blank=True,
+        )
+
+    #
+    # end fields ----------------------------------------
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('controller', 'ip')
+        
+    def validate_unique(self, exclude = None):
+        try:
+            exists = ControllerDomainNameServer.objects.get(controller = self.controller,
+                                                        ip = self.ip)
+            if exists.timestamp != self.timestamp:
+                print 'SHAZAM', self.timestamp
+                self.timestamp = exists.timestamp
+        except:
+            pass
+
+    class Rest:
+        NAME = 'controller-domain-name-server'
+        FIELD_INFO = (
+            )
+
+
+#
+# ------------------------------------------------------------
+
+class Switch(models.Model):
+    switch_id_length = 23
+    #
+    # fields ----------------------------------------
+
+    dpid = models.CharField(
+        primary_key=True,
+        verbose_name='Switch DPID',
+        help_text='Switch DPID - 64-bit hex separated by :',
+        max_length=switch_id_length, 
+        validators=[ validate_dpid ])
+
+    controller = models.ForeignKey(
+        Controller,
+        verbose_name='Controller ID',
+        blank=True,                           
+        null=True)
+    
+    socket_address = models.CharField(
+        verbose_name='Socket Address',
+        help_text='Socket address used for connection to controller',
+        max_length=64,
+        blank=True,
+        null=True)
+    
+    ip = models.CharField(
+        verbose_name='IP Address',
+        help_text='IP Address used for connection from controller',
+        validators=[ IpValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+
+    active = models.BooleanField(
+        verbose_name='Active',
+        help_text='Switch is active (i.e. connected to a controller)',
+        default=False)
+
+    connected_since = models.DateTimeField(
+        verbose_name='Last Connect Time',
+        help_text='Time when the switch connected to the controller',
+        blank=True,
+        null=True)
+    
+    dp_desc = models.CharField(
+        verbose_name='Description',
+        max_length=256,
+        blank=True,
+        null=True)
+
+    hw_desc = models.CharField(
+        verbose_name='Hardware Description',
+        max_length=256,
+        blank=True,
+        null=True)
+
+    sw_desc = models.CharField(
+        verbose_name='Software Description',
+        max_length=256,
+        blank=True,
+        null=True)
+
+    serial_num = models.CharField(
+        verbose_name='Serial Number',
+        max_length=32,
+        blank=True,
+        null=True)
+
+    capabilities = models.IntegerField(
+        verbose_name='Capabilities',
+        help_text='Openflow switch capabilities',
+        validators=[ RangeValidator(0,2**32-1) ],
+        default=0)
+
+    tunneling_supported = models.BooleanField(
+        default=False,
+        )
+
+    buffers = models.IntegerField(
+        verbose_name='Max Packets',
+        help_text='Maximum number of packets buffered by the switch',
+        validators=[ RangeValidator(0,2**32-1) ],
+        blank=True,
+        null=True)
+
+    tables = models.IntegerField(
+        verbose_name='Max Tables',
+        help_text='Number of tables supported by the switch',
+        validators=[ RangeValidator(0,2**8-1) ],
+        blank=True,
+        null=True)
+
+    actions = models.IntegerField(
+        verbose_name='Actions',
+        help_text='Actions supported by the switch',
+        validators=[ RangeValidator(0,2**32-1) ],
+        default=0)
+    
+    #
+    # end fields ----------------------------------------
+
+    # LOOK! we should have an origin field to distinguish
+    # between user-specified switches and 'discovered' switches
+
+    def __unicode__(self):
+        return "%s" % self.dpid
+
+    class Rest:
+        NAME = 'switch'
+        FIELD_INFO = (
+            {'name': 'socket_address',    'rest_name': 'socket-address'},
+            {'name': 'ip',                'rest_name': 'ip-address'},
+            {'name': 'connected_since',   'rest_name': 'connected-since'},
+            {'name': 'dp_desc',           'rest_name': 'dp-desc'},
+            {'name': 'hw_desc',           'rest_name': 'hw-desc'},
+            {'name': 'sw_desc',           'rest_name': 'sw-desc'},
+            {'name': 'serial_num',        'rest_name': 'serial-num'},
+            {'name': 'tunneling_supported', 'rest_name': 'tunnel-supported'},
+            )
+
+#
+# ------------------------------------------------------------
+# SwitchConfig
+#  Any 'configured' (non-discovered) state associated with
+#  a switch.
+#
+#  tunnel_termination; when enabled, tunnels are constructed
+#   to any other tunnel_termination switch, building a mesh
+#   of open-flow enabled switches.  Typicall Used in virtualized
+#   environments, where openflow switches are not intended to
+#   exist in the path.
+#
+
+class SwitchConfig(models.Model):
+    switch_id_length = 23
+    #
+    # fields ----------------------------------------
+
+    dpid = models.CharField(
+        primary_key=True,
+        verbose_name='Switch DPID',
+        help_text='Switch DPID - 64-bit hex separated by :',
+        max_length=switch_id_length, 
+        validators=[ validate_dpid ])
+
+    core_switch = models.BooleanField(
+        default=False,
+        help_text='Identify core switches'
+        )
+
+    tunnel_termination = models.CharField(
+        verbose_name='Tunnel Termination',
+        help_text='Tunnel Termination ("enabled" "disabled" "default")',
+        validators=[ EnumerationValidator(('enabled', 'disabled', 'default'))],
+        default = 'default',
+        max_length=16)
+
+    #
+    # end fields ----------------------------------------
+
+
+    def validate_unique(self, exclude = None):
+        self.dpid = self.dpid.lower()
+
+    class Rest:
+        NAME = 'switch-config'
+        FIELD_INFO = (
+            {'name': 'tunnel_termination', 'rest_name': 'tunnel-termination'},
+            {'name': 'core_switch',       'rest_name': 'core-switch'},
+            )
+
+
+#
+# ------------------------------------------------------------
+
+class SwitchAlias(models.Model):
+    
+    #
+    # fields ----------------------------------------
+
+    id = models.CharField(
+        primary_key=True,
+        max_length=255,
+        help_text = "alias for switch",
+        verbose_name='alias',
+        validators=[SwitchAliasValidator()])
+    
+    switch = models.ForeignKey(
+        SwitchConfig,
+        verbose_name='Switch DPID')
+
+    #
+    # end fields ----------------------------------------
+
+    class Rest:
+        NAME = 'switch-alias'
+
+
+#
+# ------------------------------------------------------------
+
+class Port(models.Model):
+    #
+    # fields ----------------------------------------
+    #
+    # This table isn't intended to be updated via the rest api,
+    #  sdnplatform writes the table to describe a switch.
+    #
+    # The 'number' in the port model is the openflow port number,
+    #  which is a value used in setting up flow entries.  This is
+    #  not an interface name;  the 'interface name' is the 'name'
+    #  field below.  This table provides a mapping from the switch
+    #  dpid and port number to an 'interface name'.
+    #
+    # Since interface names can be configured on demand by the
+    #  switch, for example to name a tunnel, its not easy to
+    #  "guess" interface names without the switch reporting 
+    #  what interface names exist.  This leads to difficulty
+    #  in preconfiguring any associations with <switch, interface name>
+    #
+
+    # Unique identifier for the port
+    # combination of the switch DPID and the port number
+    id = models.CharField(
+        primary_key=True,
+        verbose_name='Port ID',
+        help_text = '#|switch|number',
+        max_length=48)
+    
+    switch = models.ForeignKey(
+        Switch,
+        verbose_name='Switch DPID')
+
+    number = models.IntegerField(
+        verbose_name='OF #',
+        help_text="Port open flow number",
+        validators=[ RangeValidator(0,2**16-1) ])
+    
+    hardware_address = models.CharField(
+        verbose_name="MAC Address",
+        help_text="Port MAC Address",
+        max_length=17, 
+        validators = [validate_mac_address],
+        blank=True,
+        null=True)
+    
+    name = models.CharField(
+        verbose_name='Name',
+        help_text="Port name",
+        max_length=32,
+        blank=True,
+        null=True)
+
+    config = models.IntegerField(
+        verbose_name='Configuration',
+        help_text='Configuration Flags',
+        validators=[ RangeValidator(0,2**32-1) ],
+        default=0)
+
+    state = models.IntegerField(
+        verbose_name="State",
+        help_text="State Flags",
+        validators=[ RangeValidator(0,2**32-1) ],
+        default=0)
+    
+    current_features = models.IntegerField(
+        verbose_name='Current',
+        help_text='Current Features',
+        validators=[ RangeValidator(0,2**32-1) ],
+        default=0)
+
+    advertised_features = models.IntegerField(
+        verbose_name='Advertised',
+        help_text='Advertised Features',
+        validators=[ RangeValidator(0,2**32-1) ],
+        default=0)
+
+    supported_features = models.IntegerField(
+        verbose_name='Supported',
+        help_text='Supported Features',
+        validators=[ RangeValidator(0,2**32-1) ],
+        default=0)
+
+    peer_features = models.IntegerField(
+        verbose_name='Peer',
+        help_text='Peer Features',
+        validators=[ RangeValidator(0,2**32-1) ],
+        default=0)
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return "%s" % (self.id,)
+
+    class Rest:
+        NAME = 'port'
+        FIELD_INFO = (
+            {'name': 'hardware_address',     'rest_name': 'hardware-address'},
+            {'name': 'current_features',     'rest_name': 'current-features'},
+            {'name': 'advertised_features',  'rest_name': 'advertised-features'},
+            {'name': 'supported_features',   'rest_name': 'supported-features'},
+            {'name': 'peer_features',        'rest_name': 'peer-features'},
+            )
+
+#
+# ------------------------------------------------------------
+
+class PortAlias(models.Model):
+    
+    #
+    # fields ----------------------------------------
+
+    id = models.CharField(
+        primary_key=True,
+        max_length=255,
+        help_text = "alias for port",
+        verbose_name='alias',
+        validators=[PortAliasValidator()])
+    
+    port = models.ForeignKey(
+        Port,
+        help_text = "foreign key for port alias",
+        verbose_name='Port ID')
+
+    #
+    # end fields ----------------------------------------
+
+    class Rest:
+        NAME = 'port-alias'
+#
+# ------------------------------------------------------------
+
+class SwitchInterfaceConfig(models.Model):
+    if_name_len = 32
+    #
+    # fields ----------------------------------------
+
+    switch = models.ForeignKey(
+        SwitchConfig,
+        verbose_name='Switch')
+
+    if_name = models.CharField(
+        verbose_name='If Name',
+        validators=[ SafeForPrimaryKeyValidator() ],
+        max_length=32)
+
+    mode = models.CharField(
+        verbose_name='Mode',
+        help_text='Interface Mode ("external", "default")',
+        validators=[ EnumerationValidator(('external','default'))],
+        max_length=32,
+        default='default')
+    
+    broadcast = models.BooleanField(
+        default=False,
+        verbose_name='Broadcast',
+        help_text='True when interfaces is an uplink to a legacy net',
+        )
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return "%s" % (self.id)
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('switch', 'if_name')
+
+    def validate_unique(self, exclude = None):
+        self.broadcast = False
+        if self.mode == 'external':
+            self.broadcast = True
+
+    class Rest:
+        NAME = 'switch-interface-config'
+        FIELD_INFO = (
+            # By using 'name' here, the 'name' from the port is identified
+            #  in the complete-from-another procedure
+            {'name': 'if_name',             'rest_name': 'name'},
+            )
+
+
+#
+# ------------------------------------------------------------
+
+class SwitchInterfaceAlias(models.Model):
+    switch_interface_alias_length = 255
+    #
+    # fields ----------------------------------------
+
+    id = models.CharField(
+        primary_key = True,
+        verbose_name = 'Switch Interface Alias',
+        help_text = 'alias',
+        max_length = switch_interface_alias_length,
+        validators=[HostAliasValidator()])
+
+    switch_interface = models.ForeignKey(
+        SwitchInterfaceConfig,
+        verbose_name='Switch Interface',
+        )
+
+    #
+    # end fields ----------------------------------------
+
+    class Rest:
+        NAME = 'switch-interface-alias'
+        FIELD_INFO = (
+            {'name': 'switch_interface', 'rest_name': 'switch-interface'},
+        )
+#
+# ------------------------------------------------------------
+
+class StaticFlowTableEntry(models.Model):
+    #
+    # fields ----------------------------------------
+
+    name = models.CharField(
+        primary_key=True,
+        verbose_name='Name', 
+        max_length=80)
+
+    switch = models.ForeignKey(
+        SwitchConfig, 
+        verbose_name="Switch DPID")
+
+    active = models.BooleanField(
+        default=False)
+    
+    # LOOK! we should hide the timeout values for static flow entry
+    # definition as they are overwritten (unless we allow these to
+    # overwrite the default that static flow pusher uses)
+    idle_timeout = models.IntegerField(
+        verbose_name="Idle Timeout", 
+        help_text="Expire flow after this many seconds of inactivity - default: 60",
+        default=60,
+        validators=[ RangeValidator(0, 2**16-1) ])
+                                       
+    hard_timeout = models.IntegerField(
+        verbose_name="Hard Timeout",
+        help_text="Seconds to expire flow, regardless of activity - default: 0",
+        default=0,
+        validators=[ RangeValidator(0, 2**16-1) ])
+
+    priority = models.IntegerField(
+        verbose_name="Priority", 
+        help_text="Priority of the flow entry",
+        default=32768,
+        validators=[ RangeValidator(0, 2**16-1) ])
+
+    cookie = models.IntegerField(
+        verbose_name="Cookie",
+        default=0) # 64-bit
+
+    #
+    # match fields
+    #
+
+    # LOOK! Need a file of python openflow constants 
+
+    # LOOK! we should hide this also or at least
+    #       for static flow entries say it is ignored
+    wildcards = models.IntegerField(
+        verbose_name="Wildcards",
+        default=0,
+        validators=[ RangeValidator(0,2**32-1) ])
+
+    in_port = models.IntegerField(
+            verbose_name="Ingress Port", 
+            blank=True, 
+            help_text="Open flow port number of ingress port",
+            null=True, 
+            validators = [ RangeValidator(0, 2**16-1) ] ) 
+
+    dl_src = models.CharField(
+            verbose_name="Src MAC", 
+            help_text="This is a 48-bit quantity specified in xx:xx:xx:xx:xx:xx format", 
+            max_length=17, 
+            blank=True, 
+            null=True,
+            validators = [ validate_mac_address ] )
+
+    dl_dst = models.CharField(
+            verbose_name="Dst MAC", 
+            help_text="Destination MAC address in the frames",
+            max_length=17, 
+            blank=True, 
+            null=True, 
+            validators = [ validate_mac_address ] )
+
+    dl_vlan = models.IntegerField(
+            verbose_name="VLAN ID",
+            help_text="VLAN ID in the frames",
+            blank=True, 
+            null=True, 
+            validators = [ RangeValidator(0, 2**12-1) ]) 
+
+    dl_vlan_pcp = models.IntegerField(
+            verbose_name="VLAN Priority", 
+            help_text="VLAN ID in the frames",
+            blank=True, 
+            null=True, 
+            validators = [ RangeValidator(0, 2**3-1) ]) 
+
+    dl_type = models.IntegerField(
+            verbose_name="Ether Type", 
+            help_text="Ether(L3) type",
+            blank=True, 
+            null=True, 
+            validators = [ RangeValidator(0, 2**16-1) ]) 
+
+    nw_tos = models.IntegerField(
+            verbose_name="TOS Bits",
+            help_text="TOS bits in the frame",
+            blank=True,
+            null=True,
+            validators = [ RangeValidator(0, 2**6-1) ]) # 6-bit DSCP value
+
+    nw_proto = models.IntegerField(
+            verbose_name="Protocol", 
+            help_text="IP (L4) protocol in the packets",
+            blank=True,
+            null=True, 
+            validators = [ RangeValidator(0, 2**8-1) ]) 
+
+    nw_src = models.CharField(
+            verbose_name="Src IP", 
+            help_text="IP v4 source address in dotted decimal a.b.c.d w/ optional mask (ex: /24)", 
+            max_length=18,
+            validators = [ CidrValidator(mask_required=False) ],
+            blank=True, 
+            null=True)
+
+    nw_dst = models.CharField(
+            verbose_name="Dst IP", 
+            help_text="IP v4 destination address in dotted decimal a.b.c.d w/ optional mask (ex: /24)", 
+            validators=[ CidrValidator(mask_required=False) ],
+            max_length=18,
+            blank=True, 
+            null=True )
+
+    tp_src = models.IntegerField(
+            verbose_name="Src Port", 
+            help_text="Source (TCP/UDP) port",
+            blank=True, 
+            null=True,
+            validators=[ RangeValidator(0, 2**16-1) ])
+
+    tp_dst = models.IntegerField(
+            verbose_name="Dst Port", 
+            help_text="Destination (TCP/UDP) port",
+            blank=True, 
+            null=True, 
+            validators=[ RangeValidator(0, 2**16-1) ])
+
+    # LOOK! have to figure out how to expose actions in the CLI - this is ugly/brittle
+    actions = models.CharField(
+            verbose_name = "Actions",
+            help_text="This is a comma-separated list of actions - a la dpctl", 
+            max_length=1024, 
+            blank=True, 
+            null=True)
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return self.name
+
+
+    class Rest:
+        NAME = 'flow-entry'
+        FIELD_INFO = (
+            {'name': 'idle_timeout',    'rest_name': 'idle-timeout'},
+            {'name': 'hard_timeout',    'rest_name': 'hard-timeout'},
+            {'name': 'in_port',         'rest_name': 'ingress-port'},
+            {'name': 'dl_src',          'rest_name': 'src-mac'},
+            {'name': 'dl_dst',          'rest_name': 'dst-mac'},
+            {'name': 'dl_vlan',         'rest_name': 'vlan-id'},
+            {'name': 'dl_vlan_pcp',     'rest_name': 'vlan-priority'},
+            {'name': 'dl_type',         'rest_name': 'ether-type'},
+            {'name': 'nw_tos',          'rest_name': 'tos-bits'},
+            {'name': 'nw_proto',        'rest_name': 'protocol'},
+            {'name': 'nw_src',          'rest_name': 'src-ip'},
+            {'name': 'nw_dst',          'rest_name': 'dst-ip'},
+            {'name': 'tp_src',          'rest_name': 'src-port'},
+            {'name': 'tp_dst',          'rest_name': 'dst-port'},
+            {'name': 'actions',         'rest_name': 'actions'},
+            )
+
+
+#
+# ------------------------------------------------------------
+
+class Link(models.Model):
+    #
+    # fields ----------------------------------------
+
+    id = models.CharField(
+        primary_key=True,
+        verbose_name='Link ID',
+        max_length=64)
+    
+    src_switch = models.ForeignKey(
+        Switch,
+        verbose_name='Src Switch DPID',
+        related_name='src_link_set')
+    #src_switch_id = models.CharField(
+    #    verbose_name='Src Switch ID',
+    #    max_length=32)
+    
+    name = models.CharField(
+        verbose_name='Name',
+        help_text="Link name",
+        max_length=32,
+        blank=True,
+        null=True)
+    
+    src_port = models.IntegerField(
+        verbose_name="Src Port",
+        validators=[ RangeValidator(0, 2**16-1) ])
+
+    src_port_state = models.IntegerField(
+        verbose_name="Src Port State",
+        help_text="Source Port State Flags",
+        validators=[ RangeValidator(0,2**32-1) ],
+        default=0)
+
+    dst_switch = models.ForeignKey(
+        Switch,
+        verbose_name='Dst Switch DPID',
+        related_name='dst_link_set')
+    #dst_switch_id = models.CharField(
+    #    verbose_name='Dst Switch ID',
+    #    max_length=32)
+   
+    dst_port = models.IntegerField(
+        verbose_name="Dst Port",
+        help_text="Destination Port",
+        validators=[ RangeValidator(0, 2**16-1) ])
+
+    dst_port_state = models.IntegerField(
+        verbose_name="Dst Port State",
+        help_text="Destination Port State Flags",
+        validators=[ RangeValidator(0,2**32-1) ],
+        default=0)
+
+    link_type = models.CharField(
+                            verbose_name='Link Type',
+                            help_text="Link type",
+                            max_length=10,
+                            blank=True,
+                            null=True)
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return "%s" % self.id
+
+    class Rest:
+        NAME = 'link'
+        FIELD_INFO = (
+            {'name': 'src_switch',      'rest_name': 'src-switch'},
+            {'name': 'src_port',        'rest_name': 'src-port'},
+            {'name': 'src_port_state',  'rest_name': 'src-port-state'},
+            {'name': 'dst_switch',      'rest_name': 'dst-switch'},
+            {'name': 'dst_port',        'rest_name': 'dst-port'},
+            {'name': 'dst_port_state',  'rest_name': 'dst-port-state'},
+            {'name': 'link_type',       'rest_name': 'link-type'}
+            )
+            
+#
+# ------------------------------------------------------------
+# An address-space separation
+
+class AddressSpace (models.Model):
+
+    id_max_length = 64
+
+    #
+    # fields ----------------------------------------
+
+    #
+    # Unique name of the address-space
+    #
+    name = models.CharField(
+        primary_key  = True,
+        verbose_name = 'Address Space Name',
+        help_text    = 'A unique name for an Address Space Seperation',
+        validators   = [ AddressSpaceNameValidator() ],
+        max_length   = id_max_length)
+
+    #
+    # Verbose description of this rule.
+    #
+    description = models.CharField(
+        verbose_name = 'Description',
+        help_text    = "Description of the address-space",
+        max_length   = 128,
+        blank        = True,
+        null         = True)
+   
+    #
+    # Whether the configuration is active ? By default, it is active
+    # Used to disable the configuration without having to delete the entire
+    # address-space configuration construct.
+    #
+    active = models.BooleanField(
+        verbose_name = 'Active',
+        help_text    = 'Is this Address Space active (default is True)',
+        default      = True)
+
+    #
+    # Priority of this address-space during device matching when
+    # compared to other address-spaces. Those at the same priority
+    # are used in a determinisitc alphanetical order.
+    #
+    priority = models.IntegerField(
+        verbose_name = 'Priority',
+        help_text    = 'Priority for this Address Space ' +
+                       '(higher numbers are higher priority)',
+        default      = 1000,
+        validators   = [ RangeValidator(0, 65535) ])
+
+    #
+    # Seperator tag of this address-space in the data plane, such as vlan id.
+    #
+    vlan_tag_on_egress = models.IntegerField(
+        verbose_name = 'Egress VLAN tag',
+        help_text    = 'Vlan Tag value used for this Address Space separation',
+        default      = None,
+        blank        = True,
+        null         = True,
+        validators   = [ RangeValidator(0, 4095) ])
+
+    #
+    # Origin of this configured item
+    # 
+    origin = models.CharField(
+        verbose_name = "Origin",
+        help_text    = "Values: cli, rest, other packages",
+        max_length   = 64, # in future we might use SW GUIDs for this field
+        blank        = True,
+        null         = True)
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__ (self):
+        return self.name
+
+    class Rest:
+        NAME       = 'address-space'
+        FIELD_INFO = (
+            {'name': 'vlan_tag_on_egress', 'rest_name':'vlan-tag-on-egress'},
+        )
+
+#
+# ------------------------------------------------------------
+# An identifier rule in address-space separation
+
+class AddressSpaceIdentifierRule (models.Model):
+    rule_max_length = 32 
+
+    #
+    # fields ----------------------------------------
+
+    #
+    # Create a reference to the enclosing address-space construct.
+    #
+    address_space = models.ForeignKey(
+        AddressSpace,
+        verbose_name = 'Address Space Identifier')
+
+    #
+    # Unique rule identifier name under this address-space.
+    #
+    rule = models.CharField(
+        verbose_name = 'Address Space Rule Identifier',
+        max_length   = rule_max_length)
+
+    #
+    # Verbose description of this rule.
+    #
+    description = models.CharField(
+        verbose_name = 'Description',
+        help_text    = "Description of rule",
+        max_length   = 128,
+        blank        = True,
+        null         = True)
+
+    #
+    # Whether the configuration is active ? By default, it is active
+    # Used to disable the configuration without having to delete the entire
+    # address-space identifier-rule configuration construct.
+    #
+    active = models.BooleanField(
+        verbose_name = 'Active',
+        help_text    = 'If this interface is active (default is True)',
+        default      = True)
+
+    #
+    # Priority of this address-space during device matching when
+    # compared to other address-spaces. Those at the same priority
+    # are used in a determinisitc alphanetical order.
+    #
+    priority = models.IntegerField(
+        verbose_name = 'Priority',
+        help_text    = 'Priority for this interface rule ' +
+                       '(higher numbers are higher priority)',
+        default      = 32768,
+        validators   = [ RangeValidator(0, 65535) ])
+
+    #
+    # DPID of the Switch which sent the packet
+    #
+    switch = models.CharField(
+        verbose_name = 'Switch DPID',
+        max_length   = Switch.switch_id_length,
+        help_text    = 'Switch DPID or switch alias',
+        validators   = [ validate_dpid ],
+        null         = True,
+        blank        = True)
+
+    #
+    # Range of ports in which the packet came from
+    #
+    ports = models.CharField(
+        verbose_name = "Port Range Spec",
+        help_text    = 'Port range (e.g. C12 or B1,A22-25)',
+        max_length   = 256,
+        validators   = [ PortRangeSpecValidator() ],
+        blank        = True,
+        null         = True)
+    
+    #
+    # Range of VLAN tags
+    #
+    vlans = models.CharField(
+        verbose_name = "VLAN Range Spec",
+        help_text    = "VLAN(s) (e.g. 5 or 5-10,4010-4050)",
+        max_length   = 256,
+        validators   = [ VLANRangeSpecValidator() ],
+        blank        = True,
+        null         = True)
+
+    #
+    # NameValue pair of tags
+    #
+    tag = models.CharField(
+        verbose_name = "Tag Spec",
+        help_text    = "Tag values (e.g. namespace.tagname=value)",
+        max_length   = 256,
+        validators   = [ TagSpecValidator() ],
+        blank        = True,
+        null         = True)
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__ (self):
+        return self.id
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('address_space', 'rule')
+
+    class Rest:
+        NAME = 'address-space-identifier-rule'
+        FIELD_INFO = (
+            {'name': 'description',     'rest_name': 'description'},
+            {'name': 'address_space',   'rest_name': 'address-space'},
+            {'name': 'active',          'rest_name': 'active'},
+            {'name': 'priority',        'rest_name': 'priority'},
+            {'name': 'switch',          'rest_name': 'switch'},
+            {'name': 'ports',           'rest_name': 'ports'},
+            {'name': 'vlans',           'rest_name': 'vlans'},
+            {'name': 'tag',             'rest_name': 'tag'},
+            )
+
+#
+# ------------------------------------------------------------
+
+class HostConfig(models.Model):
+    host_id_length = 17
+    #
+    # fields ----------------------------------------
+
+    address_space = models.ForeignKey(
+        AddressSpace,
+        verbose_name = "Address space name")
+
+    mac = models.CharField(
+        verbose_name="MAC Address",
+        max_length=host_id_length, 
+        validators = [validate_mac_address])
+
+    vlan = models.CharField(
+        verbose_name='VLAN',
+        help_text='VLAN Associated with host',
+        max_length=4,
+        validators=[RangeValidator(1, 4095)],
+        blank=True,
+        default='')
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        self.mac = self.mac.lower()
+        return "%s::%s" % (self.addressSpace, self.mac)
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('address_space', 'vlan', 'mac')
+
+    def validate_unique(self, exclude = None):
+        # Invoke the default validator; error out if the vns already exists
+        super(HostConfig, self).validate_unique(exclude)
+        if self.vlan and str(self.address_space) != 'default': 
+            raise ValidationError('host: vlan configured for '
+                                  'address-space other than "default" %s' % self.address_space)
+
+    class Rest:
+        NAME = 'host-config'
+        FIELD_INFO = (
+            {'name': 'address_space', 'rest_name': 'address-space'},
+            )
+
+#
+# ------------------------------------------------------------
+
+class HostSecurityIpAddress(models.Model):
+    host = models.ForeignKey(
+        HostConfig,
+        verbose_name='Host ID')
+
+    ip = models.CharField(
+        verbose_name='IP Address',
+        help_text='IP Address used to associate with host',
+        validators=[ IpValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return self.id
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('host', 'ip')
+
+    class Rest:
+        NAME = 'host-security-ip-address'
+        FIELD_INFO = (
+            {'name': 'ip', 'rest_name': 'ip-address'},
+            )
+
+
+#
+# ------------------------------------------------------------
+
+class HostSecurityAttachmentPoint(models.Model):
+    host = models.ForeignKey(
+        HostConfig,
+        verbose_name='Host ID')
+
+    dpid = models.CharField(
+        verbose_name = 'Switch DPID',
+        max_length   = Switch.switch_id_length,
+        help_text    = 'Switch DPID or switch alias',
+        validators   = [ validate_dpid ],
+        null         = True,
+        blank        = True)
+
+    if_name_regex = models.CharField(
+        verbose_name='If Name Regex',
+        help_text='Interface name regular expression',
+        max_length=64,
+        validators = [SafeForPrimaryKeyValidator(), IsRegexValidator()],
+        blank = True,
+        null = False,
+        )
+
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return self.id
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('host', 'dpid', 'if_name_regex')
+
+    class Rest:
+        NAME = 'host-security-attachment-point'
+        FIELD_INFO = (
+            {'name': 'if_name_regex', 'rest_name': 'if-name-regex'},
+            )
+
+#
+# ------------------------------------------------------------
+
+class HostAlias(models.Model):
+    host_alias_length = 255
+    #
+    # fields ----------------------------------------
+
+    id = models.CharField(
+        primary_key = True,
+        verbose_name = 'Host Alias',
+        help_text = 'alias',
+        max_length = host_alias_length,
+        validators=[HostAliasValidator()])
+
+    host = models.ForeignKey(
+        HostConfig,
+        verbose_name='Host ID')
+
+    #
+    # end fields ----------------------------------------
+
+    class Rest:
+        NAME = 'host-alias'
+
+
+class VlanConfig(models.Model):
+    #
+    # fields ----------------------------------------    
+    vlan = models.IntegerField(
+        primary_key = True,                       
+        verbose_name='VLAN',
+        help_text='VLAN Number',
+        validators=[RangeValidator(0, 4095)],
+        )
+    
+    #
+    # end fields ----------------------------------------
+    
+    def __unicode__(self):
+        if self.vlan:
+            return "%s vlan %s" % (self.vlan)
+        else:
+            return "%s vlan %s" % (self.vlan)
+    
+    class Rest:
+        NAME = 'vlan-config'
+
+#
+# ------------------------------------------------------------
+# A Static ARP table separation
+
+class StaticArp (models.Model):
+
+    id_max_length = 64
+    #
+    # fields ----------------------------------------
+    ip = models.CharField(
+        primary_key=True,
+        verbose_name='IP Address',
+        validators=[ IpValidator() ],
+        max_length=15)
+    mac = models.CharField(
+        verbose_name="MAC Address",
+        max_length=17, 
+        validators = [validate_mac_address]) 
+    #
+    # Origin of this configured item
+    # 
+    origin = models.CharField(
+        verbose_name = "Origin",
+        help_text    = "Values: cli, rest, other packages",
+        max_length   = 64, # in future we might use SW GUIDs for this field
+        blank        = True,
+        null         = True)
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__ (self):
+        return self.id
+    
+    class Rest:
+        NAME = 'static-arp'
+        
+
+
+#
+# ------------------------------------------------------------
+# A Tenant separation
+
+class Tenant (models.Model):
+
+    id_max_length = 64
+    #
+    # fields ----------------------------------------
+
+    #
+    # Unique name of the tenant
+    #
+    name = models.CharField(
+        primary_key  = True,
+        verbose_name = 'Tenant Name',
+        help_text    = 'A unique name for an Tenant',
+        validators   = [ TenantNameValidator() ],
+        max_length   = id_max_length)
+
+    #
+    # Verbose description of this tenant.
+    #
+    description = models.CharField(
+        verbose_name = 'Description',
+        help_text    = "Description of the tenant",
+        max_length   = 128,
+        blank        = True,
+        null         = True)
+   
+    #
+    # Whether the configuration is active ? By default, it is active
+    # Used to disable the configuration without having to delete the entire
+    # tenant configuration construct.
+    #
+    active = models.BooleanField(
+        verbose_name = 'Active',
+        help_text    = 'Is this Tenant active (default is True)',
+        default      = True)
+
+    #
+    # Origin of this configured item
+    # 
+    origin = models.CharField(
+        verbose_name = "Origin",
+        help_text    = "Values: cli, rest, other packages",
+        max_length   = 64, # in future we might use SW GUIDs for this field
+        blank        = True,
+        null         = True)
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__ (self):
+        return self.name
+    
+    def delete(self):
+        if self.name=='default' or self.name =='system' or self.name =='external':
+            raise ValidationError("Default/External/System Tenant can't be deleted")
+        super(Tenant, self).delete()
+    class Rest:
+        NAME = 'tenant'
+        
+
+#
+# ------------------------------------------------------------
+# A virtual router separation
+
+class VirtualRouter (models.Model):
+
+    id_max_length = 64
+    #
+    # fields ----------------------------------------
+    vrname = models.CharField(
+        verbose_name = 'Virtual Router Name',
+        help_text    = 'A unique name for a virtual router',
+        validators   = [ GeneralNameValidator() ],
+        max_length   = id_max_length
+        )
+    
+    tenant = models.ForeignKey(
+        Tenant,
+        verbose_name='Tenant Name',
+        )
+    description = models.CharField(
+        verbose_name = 'Description',
+        help_text    = "Description of the virtual router",
+        max_length   = 128,
+        blank        = True,
+        null         = True,
+        )
+   
+    #
+    # Origin of this configured item
+    # 
+    origin = models.CharField(
+        verbose_name = "Origin",
+        help_text    = "Values: cli, rest, other packages",
+        max_length   = 64, # in future we might use SW GUIDs for this field
+        blank        = True,
+        null         = True)
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__ (self):
+        return self.id
+    
+    def validate_unique(self, exclude = None):
+        # in fat tire, only one router per tenant can be defined. this can be removed later.
+        error=False
+        try:
+            exists = VirtualRouter.objects.get(tenant = self.tenant)
+            if exists.vrname !=self.vrname:
+                error=True
+        except:
+            pass
+        if error:
+            raise ValidationError(" Virtual router %s has been defined for tenant %s, only one virtual router per tenant supported" % (exists.vrname,self.tenant))
+    
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('tenant', 'vrname')
+
+    class Rest:
+        NAME = 'virtualrouter'
+
+#
+# ------------------------------------------------------------
+# A virtual network segment
+
+class VNS(models.Model):
+    id_max_length = 64
+    #
+    # fields ----------------------------------------
+    vnsname = models.CharField(
+        verbose_name='VNS ID',
+        help_text='A unique name for a Virtual Network Segment',
+        validators=[GeneralNameValidator()],
+        max_length=id_max_length)
+    tenant=models.ForeignKey(
+        Tenant,
+        verbose_name='Tenant ID',
+        default='default')
+    #
+    # Verbose description of this rule.
+    #
+    description = models.CharField(
+        verbose_name = 'Description',
+        help_text    = "Description of the VNS",
+        max_length   = 128,
+        blank        = True,
+        null         = True)
+
+    #
+    # Reference to the address-space item. By default, we 
+    # implicitly use 'default' if this is not explicitly provided.
+    #
+    vns_address_space = models.ForeignKey(
+        AddressSpace,
+        verbose_name='Address Space Association',
+        blank=True,
+        null=True)
+
+    active = models.BooleanField(
+        verbose_name='Active',
+        help_text='If this VNS is active (default is True)',
+        default=True)
+
+    priority = models.IntegerField(
+        verbose_name='Priority',
+        help_text='Priority for this VNS (higher numbers are higher priority)',
+        default = 1000,
+        validators=[RangeValidator(0, 65535)])
+ 
+    origin = models.CharField(
+        verbose_name = "The origin/creator interface for this VNS",
+        help_text="Values: cli, rest, other packages",
+        max_length=64, # in future we might use SW GUIDs for this field
+        blank=True,
+        null=True)
+
+    arp_mode = models.CharField(
+        verbose_name = "ARP Manager Config Mode",
+        help_text="Values: always-flood, flood-if-unknown, drop-if-unknown", 
+        max_length=32, 
+        validators=[VnsArpModeValidator()],
+        default='flood-if-unknown')
+
+    dhcp_mode = models.CharField(
+        verbose_name = "DHCP Manager Config Mode",
+        help_text = "Values: always-flood, flood-if-unknown, static",
+        max_length = 20,
+        validators=[VnsDhcpModeValidator()],
+        default='flood-if-unknown')
+    
+    dhcp_ip = models.CharField(
+        verbose_name='DHCP IP Address',
+        help_text='IP Address of DHCP Server',
+        validators=[ IpValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+
+    broadcast = models.CharField(
+        verbose_name = "Broadcast (non ARP/DHCP) Config Mode",
+        help_text = "Values: always-flood, forward-to-known, drop",
+        max_length = 20,
+        validators=[VnsBroadcastModeValidator()],
+        default='forward-to-known')
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return self.id
+
+    def delete(self):
+        #default VNS can't be deleted
+        if self.id=='default|default':
+            raise ValidationError("Default VNS can't be deleted")
+        #while address space still exist, address space default vns can't be deleted
+        #for fat tire, relationship between address space and tenant are unclear yet, the following part may need revisit
+        suffix = '-default'
+        if self.vnsname.endswith(suffix):
+            print self.vnsname
+            address_space_name = self.vnsname[:-len(suffix)]
+            error=False
+            try:
+                self.vns_address_space = AddressSpace.objects.get(name = address_space_name)
+                error=True
+            except Exception, e:
+                pass
+            if error:
+                raise ValidationError('vns %s is the default VNS of address space: %s, can not be deleted ' %
+                                         (self.vnsname,address_space_name))
+        super(VNS, self).delete()
+    # manage a magic association between vns names and 
+    # address space for vns's which end in -default
+    def validate_unique(self, exclude = None):
+        # Invoke the default validator; error out if the vns already exists
+        #for fat tire, relationship between address space and tenant are unclear yet, the following part may need revisit
+        super(VNS, self).validate_unique(exclude)
+        suffix = '-default'
+        if not 'vns_address_space' in exclude:
+            if self.vns_address_space:
+                if self.vnsname.endswith(suffix):
+                    if str(self.vns_address_space) != self.vnsname[:-len(suffix)]:
+                        raise ValidationError('vns names %s ending in -default '
+                                'must have address_space names with the same prefix: %s '
+                                % (self.vnsname, self.vns_address_space))
+            elif self.vnsname.endswith(suffix):
+                address_space_name = self.vnsname[:-len(suffix)]
+                try:
+                    self.vns_address_space = AddressSpace.objects.get(name = address_space_name)
+                except Exception, e:
+                    print e
+                    if self.vns_address_space == None:
+                        raise ValidationError('vns %s has no matching address-space %s ' %
+                                             (self.vnsname, address_space_name))
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('tenant', 'vnsname')
+
+    class Rest:
+        NAME = 'vns-definition'
+        FIELD_INFO = (
+            {'name': 'vns_address_space', 'rest_name': 'address-space'},
+            {'name': 'arp_mode',          'rest_name': 'arp-mode'},
+            {'name': 'dhcp_mode',         'rest_name': 'dhcp-mode'},
+            {'name': 'dhcp_ip',           'rest_name': 'dhcp-ip'},
+            )
+    
+#
+# ------------------------------------------------------------
+# An interface rule on a VNS
+
+class VNSInterfaceRule(models.Model):
+    rule_max_length = 32 
+
+    #
+    # fields ----------------------------------------
+
+    vns = models.ForeignKey(
+        VNS,
+        verbose_name='VNS ID')
+
+    rule = models.CharField(
+        verbose_name='VNS Rule ID',
+        max_length=rule_max_length)
+
+    description = models.CharField(
+        verbose_name='Description',
+        help_text="Description of rule",
+        max_length=128,
+        blank=True,
+        null=True)
+
+    vlan_tag_on_egress = models.BooleanField(
+        verbose_name='Egress Vlan Tagging',
+        help_text='Tag with VLAN at egress point (default is False)',
+        default=False)
+
+    allow_multiple = models.BooleanField(
+        verbose_name='Allow Multiple',
+        help_text='If this interface allows hosts to be on multiple VNS (default is False)',
+        default=False)
+
+    active = models.BooleanField(
+        verbose_name='Active',
+        help_text='If this interface is active (default is True)',
+        default=True)
+
+    priority = models.IntegerField(
+        verbose_name='Priority',
+        help_text='Priority for this interface rule (higher numbers are higher priority)',
+        default = 32768,
+        validators=[RangeValidator(0, 65535)])
+
+    mac = models.CharField(
+        verbose_name="MAC Address",
+        help_text='MAC Address or host alias',
+        max_length=17, 
+        validators = [validate_mac_address],
+        blank=True,
+        null=True)
+
+    ip_subnet = models.CharField(
+        verbose_name="IP Subnet",
+        help_text='IP address or subnet (e.g. 192.168.1.1 or 192.168.1.0/24)',
+        max_length=31,
+        validators = [CidrValidator(False)],
+        blank=True,
+        null=True)
+
+    switch = models.CharField(
+        verbose_name='Switch DPID',
+        max_length= Switch.switch_id_length,
+        help_text='Switch DPID or switch alias',
+        validators=[ validate_dpid ],
+        null=True,
+        blank=True)
+
+    ports = models.CharField(
+        verbose_name="Port Range Spec",
+        help_text='Port range (e.g. C12 or B1,A22-25)',
+        max_length=256,
+        validators = [PortRangeSpecValidator()],
+        blank=True,
+        null=True)
+
+    vlans = models.CharField(
+        verbose_name="VLAN Range Spec",
+        help_text="VLAN(s) (e.g. 5 or 5-10,4010-4050)",
+        max_length=256,
+        validators = [VLANRangeSpecValidator()],
+        blank=True,
+        null=True)
+
+    tags = models.CharField(
+        verbose_name="Tag Spec",
+        help_text="Tag values (e.g. namespace.tagname=value)",
+        max_length=256,
+        validators = [TagSpecValidator()],
+        blank=True,
+        null=True)
+
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return self.id
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('vns', 'rule')
+
+    class Rest:
+        NAME = 'vns-interface-rule'
+        FIELD_INFO = (
+            {'name': 'description',        'rest_name': 'description'},
+            {'name': 'allow_multiple',     'rest_name': 'allow-multiple'},
+            {'name': 'active',             'rest_name': 'active'},
+            {'name': 'priority',           'rest_name': 'priority'},
+            {'name': 'mac',                'rest_name': 'mac'},
+            {'name': 'ip_subnet',          'rest_name': 'ip-subnet'},
+            {'name': 'switch',             'rest_name': 'switch'},
+            {'name': 'ports',              'rest_name': 'ports'},
+            {'name': 'vlans',              'rest_name': 'vlans'},
+            {'name': 'tags',               'rest_name': 'tags'},
+            {'name': 'vlan_tag_on_egress', 'rest_name': 'vlan-tag-on-egress'},            
+            )
+
+#
+# ------------------------------------------------------------
+
+class VNSInterfaceConfig(models.Model):
+    name_max_length = 32
+    #
+    # fields ----------------------------------------
+
+    vns = models.ForeignKey(
+        VNS,
+        verbose_name='VNS ID')
+
+    interface = models.CharField(
+        verbose_name='VNS Interface Name',
+        max_length=name_max_length,
+        validators = [VnsInterfaceNameValidator()])
+
+    rule = models.ForeignKey(
+        VNSInterfaceRule,
+        verbose_name='VNS Rule ID',
+        blank=True,
+        null=True)
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return self.id
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('vns', 'interface')
+
+    class Rest:
+        NAME = 'vns-interface-config'
+        FIELD_INFO = (
+            {'name': 'rule',  'rest_name': 'rule'},
+            )
+
+
+#
+# ------------------------------------------------------------
+
+class VNSAcl(models.Model):
+    name_max_length=32
+    #
+    # fields ----------------------------------------
+
+    vns = models.ForeignKey(
+        VNS,
+        verbose_name='VNS ID')
+
+    name = models.CharField(
+        help_text='Acl Name',
+        validators=[VnsAclNameValidator()],
+        max_length=name_max_length)
+
+    priority = models.IntegerField(
+        verbose_name='Priority',
+        help_text='Priority for this ACL (higher numbers are higher priority)',
+        default = 32768,
+        validators=[RangeValidator(0, 65535)])
+
+    description = models.CharField(
+        verbose_name='Description',
+        help_text="Description of the ACL",
+        max_length=128,
+        blank=True,
+        null=True)
+
+    #
+    # end fields ----------------------------------------
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('vns', 'name')
+
+    def __unicode__(self):
+        return self.id
+
+    class Rest:
+        NAME = 'vns-access-list'
+        FIELD_INFO = (
+            {'name': 'name',         'rest_name': 'name'},
+            {'name': 'priority',     'rest_name': 'priority'},
+            {'name': 'description',  'rest_name': 'description'},
+            )
+
+#
+# ------------------------------------------------------------
+
+class VNSAclEntry(models.Model):
+    #
+    # fields ----------------------------------------
+
+    rule = models.CharField(
+        help_text='Rule ID',
+        validators=[VnsRuleNameValidator()],
+        max_length=15)
+
+    vns_acl = models.ForeignKey(
+        VNSAcl,
+        verbose_name='VNS Acl name')
+
+    action = models.CharField(
+        verbose_name='permit or deny',
+        help_text="'permit' or 'deny'",
+        max_length=16,
+        validators=[ VnsAclEntryActionValidator() ])
+
+    type = models.CharField(
+        verbose_name='mac/ip/<0-255>/udp/tcp/icmp',
+        help_text="ACLtype either mac or ip or udp or tcp or icmp or ip-protocol-type",
+        max_length=16)
+
+    src_ip = models.CharField(
+        verbose_name='Source IP',
+        help_text='Source IP Address to match',
+        validators=[ IpValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+
+    src_ip_mask = models.CharField(
+        verbose_name='Source IP Mask',
+        help_text='Mask to match source IP',
+        validators=[ IpValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+
+    src_tp_port_op = models.CharField(
+        verbose_name='Source Port comparison op',
+        help_text='Compare with tcp/udp port eq/neq/any',
+        max_length=5,
+        blank=True,
+        null=True)
+    
+    src_tp_port = models.IntegerField(
+        verbose_name='Source UDP/TCP Port',
+        help_text='Source port value to compare',
+        validators=[RangeValidator(0, 65535)],
+        blank=True,
+        null=True)
+   
+    dst_ip = models.CharField(
+        verbose_name='Destination IP',
+        help_text='Destination IP Address to match',
+        validators=[ IpValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+
+    dst_ip_mask = models.CharField(
+        verbose_name='Destination IP Mask',
+        help_text='Mask to match destination IP',
+        validators=[ IpValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+
+    dst_tp_port_op = models.CharField(
+        verbose_name='Destination Port comparison op',
+        help_text='Compare with tcp/udp port eq/neq/any',
+        max_length=3,
+        blank=True,
+        null=True)
+    
+    dst_tp_port = models.IntegerField(
+        verbose_name='Destination UDP/TCP Port',
+        help_text='Destination port value to compare',
+        validators=[RangeValidator(0, 65535)],
+        blank=True,
+        null=True)
+
+    icmp_type = models.IntegerField(
+        verbose_name='ICMP Type',
+        help_text='Matching ICMP type icmp (blank matches all)',
+        validators=[RangeValidator(0, 255)],
+        blank=True,
+        null=True)
+
+    src_mac = models.CharField(
+        verbose_name="Source MAC Address",
+        help_text="Colon separated hex string (blank matches all)",
+        max_length=17, 
+        validators = [validate_mac_address],
+        blank=True,
+        null=True)
+    
+    dst_mac = models.CharField(
+        verbose_name="Destination MAC Address",
+        help_text="Colon separated hex string (blank matches all)",
+        max_length=17, 
+        validators = [validate_mac_address],
+        blank=True,
+        null=True)
+    
+    ether_type = models.IntegerField(
+        verbose_name='Ethernet Packet Type',
+        help_text='Standard ether type (blank matches all)',
+        validators=[RangeValidator(1536, 65535)],
+        blank=True,
+        null=True)
+
+    vlan = models.IntegerField(
+        verbose_name="VLAN ID",
+        help_text='Standard ether type (blank matches all)',
+        blank=True, 
+        null=True, 
+        validators = [ RangeValidator(0, 4095) ]) 
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return self.id
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('vns_acl', 'rule')
+
+    def validate_unique(self, exclude = None):
+        #
+        # there are three types of entries:
+        # - mac based rules
+        # - ip based rules
+        # - tcp/udp based rules
+        # 
+        # verify that for each rules, unexpected fields are not
+        #  populated
+
+        if self.type == 'mac':
+            if self.src_ip or self.src_ip_mask:
+                raise ValidationError("vns-access-list-entry mac rule:"
+                                      " src-ip/src-ip-mask specified"
+                                      "(ought to be null)")
+            if self.dst_ip or self.dst_ip_mask:
+                raise ValidationError("vns-access-list-entry mac rule:"
+                                      " dst-ip/dst-ip-mask specified "
+                                      "(ought to be null)")
+            if self.src_tp_port_op or self.src_tp_port:
+                raise ValidationError("vns-access-list-entry mac rule:"
+                                      " src-tp-port-op/src-to-port specified "
+                                      "(ought to be null)")
+            if self.dst_tp_port_op or self.dst_tp_port:
+                raise ValidationError("vns-access-list-entry mac rule:"
+                                      " dst-tp-port-op/dst-to-port specified "
+                                      "(ought to be null)")
+            if self.icmp_type:
+                raise ValidationError("vns-access-list-entry mac rule:"
+                                      " icmp_type specified "
+                                      "(ought to be null)")
+        elif self.type == 'ip' or re.match('[\d]+', self.type) or \
+            self.type == 'icmp':
+            if (self.src_tp_port_op != None or self.src_tp_port != None) and \
+                ((self.src_tp_port_op == None) or (self.src_tp_port == None)):
+                raise ValidationError("vns-access-list-entry ip rule:"
+                                      " src-tp-port-op/src-to-port specified "
+                                      "(both or neither)")
+            if (self.dst_tp_port_op != None or self.dst_tp_port != None) and \
+                ((self.dst_tp_port_op == None) or (self.dst_tp_port == None)):
+                raise ValidationError("vns-access-list-entry ip rule:"
+                                      " dst-tp-port-op/dst-to-port specified "
+                                      "(both or neither)")
+            if self.src_mac or self.dst_mac:
+                raise ValidationError("vns-access-list-entry ip rule:"
+                                      " src_mac/dst_mac specified "
+                                      "(ought to be null)")
+            if self.ether_type or self.vlan:
+                raise ValidationError("vns-access-list-entry ip rule:"
+                                      " ether-type/vlan specified "
+                                      "(ought to be null)")
+
+        elif self.type == 'tcp' or self.type == 'udp':
+            if self.src_mac or self.dst_mac:
+                raise ValidationError("vns-access-list-entry ip rule:"
+                                      " src_mac/dst_mac specified "
+                                      "(ought to be null)")
+            if self.ether_type or self.vlan:
+                raise ValidationError("vns-access-list-entry ip rule:"
+                                      " ether-type/vlan specified "
+                                      "(ought to be null)")
+
+
+    class Rest:
+        NAME = 'vns-access-list-entry'
+        FIELD_INFO = (
+            {'name': 'vns_acl',        'rest_name': 'vns-access-list'},
+            {'name': 'src_ip',         'rest_name': 'src-ip'},
+            {'name': 'src_ip_mask',    'rest_name': 'src-ip-mask'},
+            {'name': 'dst_ip',         'rest_name': 'dst-ip'},
+            {'name': 'dst_ip_mask',    'rest_name': 'dst-ip-mask'},
+            {'name': 'src_tp_port_op', 'rest_name': 'src-tp-port-op'},
+            {'name': 'src_tp_port',    'rest_name': 'src-tp-port'},
+            {'name': 'dst_tp_port_op', 'rest_name': 'dst-tp-port-op'},
+            {'name': 'dst_tp_port',    'rest_name': 'dst-tp-port'},
+            {'name': 'icmp_type',      'rest_name': 'icmp-type'},
+            {'name': 'src_mac',        'rest_name': 'src-mac'},
+            {'name': 'dst_mac',        'rest_name': 'dst-mac'},
+            {'name': 'ether_type',     'rest_name': 'ether-type'},
+            )
+
+#
+# ------------------------------------------------------------
+
+class VNSInterfaceAcl(models.Model):
+    in_out_length = 4
+
+    #
+    # fields ----------------------------------------
+
+    vns_acl = models.ForeignKey(
+        VNSAcl,
+        verbose_name='VNS Acl name')
+
+    vns_interface = models.ForeignKey(
+        VNSInterfaceConfig,
+        verbose_name='VNS Interface ID',
+        help_text='[vns id]|[interface long name]')
+
+    in_out = models.CharField(
+        verbose_name='in/out',
+        help_text='Match on packet input or output',
+        validators=[VnsInterfaceAclInOutValidator()],
+        max_length=in_out_length,
+        )
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return self.id
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('vns_interface', 'vns_acl', 'in_out')
+
+    def validate_unique(self, exclude = None):
+        acl_parts = str(self.vns_acl).split('|')
+        intf_parts = str(self.vns_interface).split('|')
+        # validate two vns parts
+        if acl_parts[0] != intf_parts[0]:
+            raise ValidationError("acl's vns %s doen't match interface vns %s" %
+                                  (acl_parts[0], intf_parts[0]))
+        error = False
+        try:
+            exists = VNSInterfaceAcl.objects.get(vns_interface=self.vns_interface, in_out=self.in_out)
+            if exists:
+                if exists.vns_acl != self.vns_acl:
+                    error = True
+        except:
+            pass
+        if error:
+            raise ValidationError("Interface %s already has an ACL in the %s direction, only one ACL per direction allowed" % (self.vns_interface, self.in_out))
+
+    class Rest:
+        NAME = 'vns-interface-access-list'
+        FIELD_INFO = (
+            {'name': 'vns_acl',       'rest_name': 'vns-access-list'},
+            {'name': 'vns_interface', 'rest_name': 'vns-interface'},
+            {'name': 'in_out',        'rest_name': 'in-out'},
+            )
+
+#
+# ------------------------------------------------------------
+# A virtual router interface separation
+
+class VirtualRouterInterface (models.Model):
+
+    id_max_length = 64
+    #
+    # fields ----------------------------------------
+    virtual_router = models.ForeignKey(
+        VirtualRouter,
+        verbose_name='Virtual Router ID')
+    #
+    # Unique name of the interface
+    #
+    vriname = models.CharField(
+        verbose_name = 'Interface Name',
+        help_text    = 'A unique name for a virtual router interface',
+        validators   = [ GeneralNameValidator() ],
+        max_length   = id_max_length)
+    #
+    # Origin of this configured item
+    # 
+    origin = models.CharField(
+        verbose_name = "Origin",
+        help_text    = "Values: cli, rest, other packages",
+        max_length   = 64, # in future we might use SW GUIDs for this field
+        blank        = True,
+        null         = True)
+    #
+    # Whether the configuration is active ? By default, it is active
+    # Used to disable the configuration without having to delete the entire
+    # interface configuration construct.
+    #
+    active = models.BooleanField(
+        verbose_name = 'Active',
+        help_text    = 'Is this interface active (default is True)',
+        default      = True)
+    vns_connected = models.ForeignKey(
+        VNS,
+        verbose_name='VNS connected to',
+        blank       =True,
+        null        =True)
+    router_connected = models.ForeignKey(
+        VirtualRouter,
+        related_name='router_connected',
+        verbose_name='Virtual Router connected to',
+        blank       =True,
+        null        =True)
+    
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__ (self):
+        return self.id
+    
+    def validate_unique(self, exclude = None):
+        def is_set(value):
+            if value != None and value != '':
+                return True
+
+        # for vns connection, verify that only the VNSs under the same tenant can be connected
+        error=False
+        if not 'vns_connected' in exclude:
+            if is_set(self.vns_connected):
+                tenant_vns_parts = str(self.vns_connected).split('|')
+                tenant_router_parts = str(self.virtual_router).split('|')
+                if tenant_vns_parts[0] != tenant_router_parts[0]:
+                    raise ValidationError(" VNS %s belongs to tenant %s, doesn't match virtual router tenant %s" %
+                                      (tenant_vns_parts[1],tenant_vns_parts[0], tenant_router_parts[0]))
+                    # verify there can only be one connection for one VNS
+                try:
+                    exists = VirtualRouterInterface.objects.get(virtual_router = self.virtual_router, vns_connected=self.vns_connected)
+                    if exists:
+                        if exists.vriname!=self.vriname:
+                            error=True
+                except:
+                    pass
+                if error:
+                    raise ValidationError(" VNS %s has been connected, multiple connections is not allowed" % self.vns_connected)
+        error = False    
+        # for router connection, verify that the same virtual router as myself can't be connected        
+        if not 'router_connected' in exclude:
+            if is_set(self.router_connected):
+                tenant_router_parts = str(self.router_connected).split('|')
+                tenant_myrouter_parts = str(self.virtual_router).split('|')
+                if tenant_router_parts[0] == tenant_myrouter_parts[0]:
+                    raise ValidationError(" Local loop conncetion is not allowed.")
+            # verify there can only be one connection for one virtual router
+                try:
+                    exists = VirtualRouterInterface.objects.get(virtual_router = self.virtual_router,router_connected=self.router_connected)
+                    if exists:
+                        if exists.vriname!=self.vriname:
+                            error=True
+                except:
+                    pass
+                if error:
+                    raise ValidationError(" Virtual Router %s has been connected, multiple connections is not allowed" % self.router_connected)
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('virtual_router', 'vriname')
+
+    class Rest:
+        NAME = 'virtualrouter-interface'
+        FIELD_INFO = (
+                      {'name': 'vns_connected',           'rest_name': 'vns-connected'},
+                      {'name': 'router_connected',        'rest_name': 'router-connected'},
+                      {'name': 'virtual_router',          'rest_name': 'virtual-router'},
+                      
+            )
+
+#
+# ------------------------------------------------------------
+# A virtual router interface address pool separation
+
+class VRInterfaceIpAddressPool (models.Model):
+
+    id_max_length = 64
+    #
+    # fields ----------------------------------------
+    virtual_router_interface = models.ForeignKey(
+        VirtualRouterInterface,
+        verbose_name='Virtual Router Interface ID')
+    #
+    # Origin of this configured item
+    # 
+    origin = models.CharField(
+        verbose_name = "Origin",
+        help_text    = "Values: cli, rest, other packages",
+        max_length   = 64,
+        blank        = True,
+        null         = True)
+    ip_address = models.CharField(
+        verbose_name='Source IP',
+        help_text='Interface IP Address',
+        validators=[ IpValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+    subnet_mask = models.CharField(
+        verbose_name='Subnet IP Mask',
+        validators=[ IpMaskValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+    
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__ (self):
+        return self.id
+    
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('virtual_router_interface', 'ip_address')
+
+    class Rest:
+        NAME = 'interface-address-pool'
+        FIELD_INFO = (
+                      {'name': 'virtual_router_interface',           'rest_name': 'virtual-router-interface'},
+                      {'name': 'ip_address',                         'rest_name': 'ip-address'},
+                      {'name': 'subnet_mask',                        'rest_name': 'subnet-mask'}, 
+            )
+
+
+#
+# ------------------------------------------------------------
+
+class VirtualRouterGWPool (models.Model):
+
+    id_max_length = 64
+    #
+    # fields ----------------------------------------
+    virtual_router = models.ForeignKey(
+        VirtualRouter,
+        verbose_name='Virtual Router ID')
+    #
+    # Unique name of the gateway pool
+    #
+    vrgwname = models.CharField(
+        verbose_name = 'Gateway Pool Name',
+        help_text    = 'A unique name for a virtual router gateway pool',
+        validators   = [ GeneralNameValidator() ],
+        max_length   = id_max_length)
+    
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__ (self):
+        return self.id
+    
+    def validate_unique(self, exclude = None):
+        def is_set(value):
+            if value != None and value != '':
+                return True
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('virtual_router', 'vrgwname')
+
+    class Rest:
+        NAME = 'virtualrouter-gwpool'
+        FIELD_INFO = (
+                      {'name': 'virtual_router',          'rest_name': 'virtual-router'},
+            )
+
+#
+# ------------------------------------------------------------
+# A virtual router gateway address pool separation
+
+class VRGatewayIpAddressPool (models.Model):
+
+    id_max_length = 64
+    #
+    # fields ----------------------------------------
+    virtual_router_gwpool = models.ForeignKey(
+        VirtualRouterGWPool,
+        verbose_name='Virtual Router Gateway Pool ID')
+    ip_address = models.CharField(
+        verbose_name='Gateway IP',
+        help_text='Gateway IP Address',
+        validators=[ IpValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+    
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__ (self):
+        return self.id
+    
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('virtual_router_gwpool', 'ip_address')
+
+    class Rest:
+        NAME = 'gateway-address-pool'
+        FIELD_INFO = (
+                      {'name': 'virtual_router_gwpool', 'rest_name': 'virtual-router-gwpool'},
+                      {'name': 'ip_address', 'rest_name': 'ip-address'},
+            )
+
+class VirtualRoutingRule (models.Model):
+
+    id_max_length = 64
+    #
+    # fields ----------------------------------------
+    virtual_router = models.ForeignKey(
+        VirtualRouter,
+        verbose_name='Virtual Router ID')
+    #
+    # Origin of this configured item
+    # 
+    origin = models.CharField(
+        verbose_name = "Origin",
+        help_text    = "Values: cli, rest, other packages",
+        max_length   = 64,
+        blank        = True,
+        null         = True)
+    src_host = models.ForeignKey(
+        HostConfig,
+        verbose_name='source Host ID',
+        help_text='Source Host ID to match',
+        blank       =True,
+        null        =True)
+    src_tenant = models.ForeignKey(
+        Tenant,
+        verbose_name='source tenant ID',
+        help_text='Source tenant ID to match',
+        blank       =True,
+        null        =True)
+    src_vns = models.ForeignKey(
+        VNS,
+        verbose_name='source VNS ID',
+        help_text='Source VNS ID to match',
+        blank       =True,
+        null        =True)
+    src_ip = models.CharField(
+        verbose_name='Source IP',
+        help_text='Source IP Address to match',
+        validators=[ IpValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+    src_ip_mask = models.CharField(
+        verbose_name='Source IP Mask',
+        help_text='Mask to match source IP',
+        validators=[ IpMaskValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+    dst_host = models.ForeignKey(
+        HostConfig,
+        verbose_name='Destination Host ID',
+        help_text='Destination Host ID to match',
+        related_name='dest_host',
+        blank       =True,
+        null        =True)
+    dst_tenant = models.ForeignKey(
+        Tenant,
+        verbose_name='Destination tenant ID',
+        related_name='dest_tenant',
+        blank       =True,
+        null        =True)
+    dst_vns = models.ForeignKey(
+        VNS,
+        verbose_name='Destination VNS ID',
+        related_name='dest_vns',
+        blank       =True,
+        null        =True)
+    dst_ip = models.CharField(
+        verbose_name='Destination IP',
+        help_text='Destination IP Address to match',
+        validators=[ IpValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+    dst_ip_mask = models.CharField(
+        verbose_name='Destination IP Mask',
+        help_text='Mask to match destination IP',
+        validators=[ IpMaskValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+    outgoing_intf = models.ForeignKey(
+        VirtualRouterInterface,
+        verbose_name='Outgoing Interface',
+        blank       =True,
+        null        =True)
+    gateway_pool = models.ForeignKey(
+        VirtualRouterGWPool,
+        verbose_name='Gateway pool',
+        blank       =True,
+        null        =True)
+    nh_ip = models.CharField(
+        verbose_name='Next Hop IP',
+        help_text='Next Hop IP Address',
+        validators=[ IpValidator() ],
+        max_length=15,
+        blank=True,
+        null=True)
+    action = models.CharField(
+        verbose_name='permit or deny',
+        help_text="'permit' or 'deny'",
+        default='permit',
+        max_length=16,
+        validators=[ VnsAclEntryActionValidator() ])
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__ (self):
+        return self.id
+    
+    def validate_unique(self, exclude = None):
+        def is_set(value):
+            if value != None and value != '':
+                return True
+    #verify the outgoing interface can only be on the local virtual router interface
+        if not 'outgoing_intf' in exclude:
+            if is_set(self.outgoing_intf):
+                router_parts = str(self.outgoing_intf).split('|')
+                myrouter_parts = str(self.virtual_router).split('|')
+                if (router_parts[0] != myrouter_parts[0]) or (router_parts[1] != myrouter_parts[1]):
+                    raise ValidationError(" outgoing interface has to be local to virtual router: %s|%s" %
+                                  (myrouter_parts[0],myrouter_parts[1]))
+        #verify the gateway pool belongs to the local virtual router
+        if not 'gateway_pool' in exclude:
+            if is_set(self.gateway_pool):
+                router_parts = str(self.gateway_pool).split('|')
+                myrouter_parts = str(self.virtual_router).split('|')
+                if (router_parts[0] != myrouter_parts[0]) or (router_parts[1] != myrouter_parts[1]):
+                    raise ValidationError(" gateway pool has to be local to virtual router: %s|%s" %
+                                  (myrouter_parts[0],myrouter_parts[1]))
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('virtual_router', 'src_host', 'src_tenant','src_vns','src_ip','src_ip_mask', 'dst_host', 'dst_tenant','dst_vns','dst_ip','dst_ip_mask')
+
+    class Rest:
+        NAME = 'virtualrouter-routingrule'
+        FIELD_INFO = (
+                      {'name': 'virtual_router',           'rest_name': 'virtual-router'},
+                      {'name': 'src_tenant',               'rest_name': 'src-tenant'},
+                      {'name': 'dst_tenant',               'rest_name': 'dst-tenant'},
+                      {'name': 'src_vns',                  'rest_name': 'src-vns'},
+                      {'name': 'src_ip',                   'rest_name': 'src-ip'},
+                      {'name': 'src_ip_mask',              'rest_name': 'src-ip-mask'},
+                      {'name': 'dst_ip',                   'rest_name': 'dst-ip'},
+                      {'name': 'dst_ip_mask',              'rest_name': 'dst-ip-mask'},
+                      {'name': 'nh_ip',                    'rest_name': 'nh-ip'},
+                      {'name': 'outgoing_intf',            'rest_name': 'outgoing-intf'},
+                      {'name': 'dst_host',                 'rest_name': 'dst-host'},
+                      {'name': 'src_host',                 'rest_name': 'src-host'},   
+                      {'name': 'dst_vns',                  'rest_name': 'dst-vns'},
+                      {'name': 'gateway_pool',             'rest_name': 'gateway-pool'},
+            )
+
+#
+# ------------------------------------------------------------
+
+class Tag(models.Model):
+    namespace_length = 64
+    name_length = 64
+    value_length = 64
+
+    #
+    # fields ----------------------------------------
+
+    namespace = models.CharField(
+        verbose_name='Tag Namespace',
+        help_text="Namespace of the tag",
+        max_length=namespace_length)
+
+    name = models.CharField(
+        verbose_name='Tag Name',
+        help_text="Name of the tag",
+        max_length=name_length)
+
+    value = models.CharField(
+        verbose_name='Tag Value',
+        help_text="Value of the tag",
+        max_length=value_length)
+
+    persist = models.BooleanField(
+        verbose_name='persist',
+        help_text='For any cli configured tag, include in running-config',
+        default=True)
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return self.id
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('namespace', 'name', 'value')
+
+    class Rest:
+        NAME = 'tag'
+
+#
+# ------------------------------------------------------------
+
+class TagMapping(models.Model):
+    host_id_length = 17
+    switch_id_length = 23
+    if_name_len = 32
+    vlan_str_len = 4
+    #
+    # fields ----------------------------------------
+
+    tag = models.ForeignKey(
+            Tag)
+
+    mac = models.CharField(
+        verbose_name="MAC Address",
+        max_length=host_id_length, 
+        validators = [validate_mac_address],
+        default="",
+        blank=True)
+
+    vlan = models.CharField(
+        verbose_name='VLAN',
+        max_length=vlan_str_len, 
+        help_text='VLAN Number, in the range of 1-4095. 4095 means untagged',
+        validators=[VlanStringValidator()],
+        default="",
+        blank=True)
+    
+    dpid = models.CharField(
+        verbose_name='Switch DPID',
+        help_text='Switch DPID - 64-bit hex separated by :',
+        max_length=switch_id_length, 
+        validators=[ validate_dpid ],
+        default="",
+        blank=True)
+
+    ifname = models.CharField(
+        verbose_name='If Name regular expression',
+        max_length=if_name_len,
+        default="",
+        validators=[ SafeForPrimaryKeyValidator() ],
+        blank=True)
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return self.id
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('tag', 'mac', 'vlan', 'dpid', 'ifname')
+
+    def validate_unique(self, exclude = None):
+        if self.mac != '':
+            self.mac = self.mac.lower()
+
+        if self.dpid != '':
+            self.dpid = self.dpid.lower()
+
+        # don't allow all default values for the association
+        if self.mac == '' and self.vlan == '' and \
+            self.dpid == '' and self.ifname == '':
+           raise ValidationError("Match without any matching fields")
+
+    class Rest:
+        NAME = 'tag-mapping'
+
+#
+# ------------------------------------------------------------
+
+class TechSupportConf(models.Model):
+
+    #
+    # fields ----------------------------------------
+
+    cmd_type = models.CharField(
+        verbose_name='Type of command',
+        help_text='Enter cli or shell',
+        max_length=32)
+
+    cmd = models.CharField(
+        max_length=256,
+        verbose_name='Command name',
+        help_text = 'Command excuted by show tech-support')
+
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return self.id
+
+    class CassandraSettings:
+        COMPOUND_KEY_FIELDS = ('cmd_type', 'cmd')
+
+    class Rest:
+        NAME = 'tech-support-config'
+        FIELD_INFO = (
+            {'name': 'cmd_type',  'rest_name': 'cmd-type'},
+            )
+
+#
+# ------------------------------------------------------------
+
+class TacacsPlusConfig(models.Model):
+    #
+    # fields ----------------------------------------
+
+    id = models.CharField(
+        primary_key=True,
+        verbose_name='tacacs singleton',
+        default='tacacs',
+        max_length=16)
+
+    tacacs_plus_authn = models.BooleanField(
+        verbose_name='TACACS+ Authentication Enabled',
+        help_text='Enable TACACS+ authentication by setting to true',
+        default=False
+        )
+
+    tacacs_plus_authz = models.BooleanField(
+        verbose_name='TACACS+ Authorization Enabled',
+        help_text='Enable TACACS+ authorization by setting to true',
+        default=False
+        )
+
+    tacacs_plus_acct = models.BooleanField(
+        verbose_name='TACACS+ Accounting Enabled',
+        help_text='Enable TACACS+ accounting by setting to true',
+        default=False
+        )
+
+    local_authn = models.BooleanField(
+        verbose_name='Local Authentication Enabled',
+        help_text='Enable local authentication by setting to true',
+        default=True
+        )
+
+    local_authz = models.BooleanField(
+        verbose_name='Local Authorization Enabled',
+        help_text='Enable local authorization by setting to true',
+        default=True
+        )
+
+    timeout = models.IntegerField(
+        verbose_name="TACACS+ Server Timeout",
+        help_text='Set TACACS+ server timeout in seconds',
+        default=0,
+        validators=[ RangeValidator(0, 2**16-1) ])
+    
+    key = models.CharField(
+        verbose_name='TACACS+ Pre-shared Key',
+        help_text='pre-shared key to connect to TACACS+ server(s)',
+        max_length=256,
+        blank=True,
+        default="")    
+
+    #
+    # end fields ----------------------------------------
+
+    class Rest:
+        NAME = 'tacacs-plus-config'
+        FIELD_INFO = (
+            {'name': 'tacacs_plus_authn', 'rest_name': 'tacacs-plus-authn'},
+            {'name': 'tacacs_plus_authz', 'rest_name': 'tacacs-plus-authz'},
+            {'name': 'tacacs_plus_acct',  'rest_name': 'tacacs-plus-acct'},
+            {'name': 'local_authn',       'rest_name': 'local-authn'},
+            {'name': 'local_authz',       'rest_name': 'local-authz'},
+            )
+
+#
+# ------------------------------------------------------------
+
+class TacacsPlusHost(models.Model):
+    #
+    # fields ----------------------------------------
+
+    ip = models.CharField(
+        primary_key=True,
+        verbose_name='IP Address',
+        help_text='IP Address for TACACS+ server',
+        validators=[ IpValidator() ],
+        max_length=15)
+    
+    timestamp = models.PositiveIntegerField(
+        verbose_name='timestamp',
+        help_text='Timestamp to order the tacacs servers',
+        default = get_timestamp,
+        )
+    
+    key = models.CharField(
+        verbose_name='TACACS+ Per-host Pre-shared Key',
+        help_text='pre-shared key to connect to this TACACS+ server',
+        max_length=256,
+        blank=True,
+        default="")    
+
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return "%s" % self.ip
+
+    def validate_unique(self, exclude = None):
+        try:
+            exists = TacacsPlusHost.objects.get(ip = self.ip)
+
+            if exists.timestamp != self.timestamp:
+                self.timestamp = exists.timestamp
+        except:
+            pass
+
+    class Rest:
+        NAME = 'tacacs-plus-host'
+        FIELD_INFO = (
+            )
+
+#
+#---------------------------------------------------------
+
+class SnmpServerConfig(models.Model):
+    #
+    # fields ----------------------------------------
+
+    # we just have one row entry in the table to update, so static primary key
+    id = models.CharField(
+        primary_key=True,
+        verbose_name='snmp',
+        # default='snmp',
+        max_length=16)
+
+    #
+    # Enable state of the SNMP server/agent on the controller
+    #
+    server_enable = models.BooleanField(
+        verbose_name='SNMP Server enabled',
+        help_text='Enable SNMP server by setting to true',
+        default=False
+        )
+    #
+    # Community string for accessing the SNMP server on the controller
+    #
+    community = models.CharField(
+        verbose_name = 'Community String',
+        help_text    = "Community String to access SNMP data",
+        max_length   = 128,
+        null         = True,
+        blank        = True,
+        )
+    #
+    # Location string of the SNMP server on the controller
+    #
+    location = models.CharField(
+        verbose_name = 'System Location',
+        help_text    = "Location information for the controller appliance",
+        max_length   = 128,
+        null         = True,
+        blank        = True
+        )
+    #
+    # Contact string of the SNMP server on the controller
+    #
+    contact = models.CharField(
+        verbose_name = 'System Contact',
+        help_text    = "Contact information for the controller appliance",
+        max_length   = 128,
+        null         = True,
+        blank        = True,
+        )
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return self.id
+
+    def validate_unique(self, exclude = None):
+        if self.id != 'snmp':
+            raise ValidationError("Only single snmp record exists")
+
+    class Rest:
+        NAME = 'snmp-server-config'
+        FIELD_INFO = (
+            {'name': 'server_enable',       'rest_name': 'server-enable'},
+            )
+
+#
+# ------------------------------------------------------------
+
+class ImageDropUser(models.Model):
+    #
+    # fields ----------------------------------------
+
+    # we just have one row entry in the table to update, so static primary key
+    id = models.CharField(
+        primary_key=True,
+        verbose_name='imagedropuser',
+        default='imagedropuser',
+        max_length=16)
+
+    images_user_ssh_key = models.CharField(
+        verbose_name='Image drop user SSH public key',
+        help_text='The SSH public RSA key for the images user',
+        default='',
+        max_length=600
+        )
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__(self):
+        return self.id
+ 
+    def validate_unique(self, exclude = None):
+        if self.id != 'imagedropuser':
+            raise ValidationError("Only single ImageDropUser record exists")
+
+    class Rest:
+        NAME = 'image-drop-user'
+        FIELD_INFO = (
+            {'name': 'images_user_ssh_key', 'rest_name': 'images-user-ssh-key'},
+            )
+
+# robv: Commenting out for now. Doesn't work with Cassandra backend
+#class ConsoleUser(User):
+#
+#    class Meta:
+#        proxy = True
+#
+#    class Rest:
+#        NAME = 'user'
diff --git a/cli/sdncon/controller/notification.py b/cli/sdncon/controller/notification.py
new file mode 100755
index 0000000..ba9c97a
--- /dev/null
+++ b/cli/sdncon/controller/notification.py
@@ -0,0 +1,126 @@
+#
+# Copyright (c) 2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+#      http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+
+from django.db.models.signals import post_save, post_delete
+import urllib2
+import json
+
+# FIXME: It's not thread-safe to use globals here,
+# but we currently only allow a single thread per Django process,
+# so this shouldn't cause a problem. If/when we support multiple
+# threads we could fix this by using thread local variables.
+# To support true batch notifications across multiple REST calls
+# we'd need to actually store the batched up actions in the DB,
+# since the individual REST calls would be dispatched to
+# multiple processes, so we couldn't use in-memory batching of
+# the actions like we do now. The current batch support only
+# works for single REST updates/deletes with query parameters to
+# select the records that can affect multiple records.
+notifications_inited = False
+batch_level = 0
+actions = None
+
+
+def begin_batch_notification():
+    global batch_level
+    global actions
+    
+    if batch_level == 0:
+        actions = []
+    batch_level += 1
+
+
+# FIXME: Could generalize this so it's not hard-coded for SDNPlatform, but
+# since this is supposed to be a short-term mechanism for triggering the
+# storage notifications in SDNPlatform it's not clear if it makes sense to
+# invest time in cleaning this up.
+def end_batch_notification(reset=False):
+    global batch_level
+    global actions
+    
+    if reset:
+        batch_level = 0
+    elif batch_level > 0:
+        batch_level -= 1
+    
+    if batch_level == 0:
+        if actions:
+            url = 'http://localhost:8080/wm/storage/notify/json'
+            post_data = json.dumps(actions)
+            actions = None
+            request = urllib2.Request(url, post_data, {'Content-Type':'application/json'})
+            try:
+                response = urllib2.urlopen(request)
+                # FIXME: Log error if request had an error
+                _response_text = response.read() 
+            except Exception, _e: 
+                # SDNPlatform may not be running, but we don't want that to be a fatal
+                # error, so we just ignore the exception in that case.
+                pass
+        actions = None
+
+
+def do_action(sender, instance, action):
+    # If we're not already in a batch operation, start a local one
+    # for just this one change. This is so the code that actually
+    # sends the notifications to SDNPlatform can be bottle-necked through
+    # end_batch_notification
+    local_batch = batch_level == 0
+    if local_batch:
+        begin_batch_notification()  
+    
+    last_action = actions[-1] if actions else None
+    if (last_action is None or
+        # pylint: disable=W0212
+        last_action['tableName'] != sender._meta.db_table or
+        last_action['action'] != action):
+        # pylint: disable=W0212
+        last_action = {'tableName': sender._meta.db_table, 'action': action, 'keys': []}
+        actions.append(last_action)
+    
+    keys = last_action['keys']
+    if instance.pk not in keys:
+        keys.append(instance.pk)
+        
+    if local_batch:
+        end_batch_notification()
+
+
+def do_modify_notification(sender, instance):
+    do_action(sender, instance, 'MODIFY')
+    
+def do_delete_notification(sender, instance):
+    do_action(sender, instance, 'DELETE')
+
+def notification_post_save_handler(sender, **kwargs):
+    from sdncon.rest.config import is_config_model
+    if not kwargs['raw'] and (kwargs['using'] == "default") and not is_config_model(sender):
+        do_modify_notification(sender, kwargs['instance'])
+
+
+def notification_post_delete_handler(sender, **kwargs):
+    from sdncon.rest.config import is_config_model
+    if kwargs['using'] == 'default' and not is_config_model(sender):
+        do_delete_notification(sender, kwargs['instance'])
+
+
+def init_notifications():
+    global notifications_inited
+    if not notifications_inited:
+        post_save.connect(notification_post_save_handler)
+        post_delete.connect(notification_post_delete_handler)
+        notifications_inited = True
+
diff --git a/cli/sdncon/controller/oswrapper.py b/cli/sdncon/controller/oswrapper.py
new file mode 100755
index 0000000..73b54f8
--- /dev/null
+++ b/cli/sdncon/controller/oswrapper.py
@@ -0,0 +1,1515 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+#      http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+
+import os, atexit
+import glob
+from subprocess import Popen, PIPE, check_output, CalledProcessError
+import sys, traceback, socket
+from optparse import OptionParser
+from types import StringType
+import datetime
+import json
+import re
+import time
+import urllib2
+import httplib
+import fcntl, shutil
+import sys
+import tempfile
+import stat
+
+from string import Template
+from django.forms import ValidationError
+
+# Can't import from `import sdncon` -- causes circular dependency!
+SDN_ROOT = "/opt/sdnplatform" if not 'SDN_ROOT' in os.environ else os.environ['SDN_ROOT']
+
+# Big Switch Networks Enterprise OID
+BSN_ENTERPRISE_OID = '.1.3.6.1.4.1.37538'
+BSN_ENTERPRISE_OID_CONTROLLER = BSN_ENTERPRISE_OID + '.1'
+
+
+class OsWrapper():
+    """ This base class abstracts executing os binaries without using shell in a secure, hardened way.
+        Things to keep in mind when composing command templates - because we dont use shell, args to the 
+        binaries are presented as items in the list, so no need to de-specialize special characters , for 
+        example, if you want to echo something into a file, command is "echo -e abc\ndef\n" and not 
+        "echo -e \"abc\ndef\n\"", which would then result in the quotes also to be echo'ed.
+    """
+    name = "none"
+    cmds_lst_for_set = []
+    cmds_lst_for_get = []
+    sudo_required_for_set = True
+    sudo_required_for_get = False
+    def __init__(self, name, this_cmds_list_for_set, this_cmds_list_for_get, is_sudo_reqd_for_set=True, is_sudo_required_for_get=False):
+        self.name = name
+        self.cmds_lst_for_set = this_cmds_list_for_set
+        self.cmds_lst_for_get = this_cmds_list_for_get
+        self.sudo_required_for_set = is_sudo_reqd_for_set
+        self.sudo_required_for_get = is_sudo_required_for_get
+
+        self.IP_RE = re.compile(r'^(\d{1,3}\.){3}\d{1,3}$')
+        self.DomainName_RE = re.compile(r'^([a-zA-Z0-9-]+.?)+$')
+
+    def validate_ip(self, value):
+        if not self.IP_RE.match(value) or len([x for x in value.split('.') if int(x) < 256]) != 4:
+            return False, "IP must be in dotted decimal format, 234.0.59.1"
+        return True, ""
+
+    def validate_domain(self, value):
+        if not self.DomainName_RE.match(value):
+            return False, "Invalid domain name"
+        return True, ""
+
+    def exec_cmds(self, cmds_lst, cmds_args, stdout_file_lst):
+        # traverse through the list of commands needed to set this
+        # cmd_args is a list of lists of args for each of the commands in the set list.
+        ret_out_err = {'err': [], 'out': []}
+        if len(cmds_lst) != len(cmds_args):
+           # commands to args mismatch, error out and return
+           # possibly throw an exception here as well
+           ret_out_err['err'].append("Command and args mismatch")
+           return ret_out_err
+        for indx in range(len(cmds_lst)):
+            #if self.sudo_required_for_set:
+            #   cmd_string = "sudo "
+            #else:
+            #   cmd_string = "" 
+            cmd_string = ""
+            cmd_template = Template(cmd_string + cmds_lst[indx])
+            args_map = dict({}) 
+            for args_indx in range(len(cmds_args[indx])):
+                args_map["arg%d"%(args_indx+1)] = cmds_args[indx][args_indx]
+                cmd_string += cmds_args[indx][args_indx] + " "             
+            full_cmd_string = cmd_template.substitute(args_map)
+            file_for_stdout = PIPE
+            if stdout_file_lst != None and stdout_file_lst[indx] != "":
+                file_for_stdout = open(stdout_file_lst[indx], 'a')
+            sub_proc_output = Popen(full_cmd_string.rsplit(" "), shell=False, stdin=PIPE, stdout=file_for_stdout, stderr=PIPE, close_fds=True)
+            ret_out_err['err'].append([sub_proc_output.stderr.read()])
+            if file_for_stdout == PIPE:
+                ret_out_err['out'].append([sub_proc_output.stdout.read()])
+            else:
+                file_for_stdout.close()
+            # not fit for pipe io - os.system(cmd_string) # need to check for errors in commands.
+        return ret_out_err
+    
+    def exec_cmds_new(self, cmds_lst, cmds_args, useShell=False, appendStdOut=True):
+        # traverse through the list of commands needed to set this
+        # cmd_args is a list of lists of args for each of the commands in the set list.
+        ret_out_err = {'err': [], 'out': []}
+        if len(cmds_lst) != len(cmds_args):
+           # commands to args mismatch, error out and return
+           # possibly throw an exception here as wella
+           # cmds_list is a list of dict maps - [{'bin_name': <bin>, 'args_lst': <args-list>, 'stdoutfile':<filename>},...]
+           ret_out_err['err'].append("Command and args mismatch")
+           print 'cmds_lst:', cmds_lst
+           print 'cmd_args:', cmds_args
+           return ret_out_err
+        for indx in range(len(cmds_lst)):
+            #if self.sudo_required_for_set:
+            #   cmd_string = "sudo "
+            #else:
+            #   cmd_string = "" 
+            #cmd_string = ""
+            cmd_args_lst = [cmds_lst[indx]['bin_name']]
+            
+            args_map = dict({}) 
+            for args_indx in range(len(cmds_args[indx])):
+                args_map["arg%d"%(args_indx+1)] = cmds_args[indx][args_indx]
+                #cmd_string += cmds_args[indx][args_indx] + " "  
+            if 'args_lst' in cmds_lst[indx]:  
+                for args_indx in range(len(cmds_lst[indx]['args_lst'])):
+                    arg_template = Template(cmds_lst[indx]['args_lst'][args_indx])
+                    full_arg_string = arg_template.substitute(args_map)
+                    cmd_args_lst.append(full_arg_string)
+            file_for_stdout = PIPE
+            if 'stdoutfile' in cmds_lst[indx] and cmds_lst[indx]['stdoutfile'] != "":
+                fMode = 'a'
+                if not appendStdOut:
+                    fMode = 'r+'
+                file_for_stdout = open(cmds_lst[indx]['stdoutfile'] , fMode)
+            sub_proc_output = Popen(cmd_args_lst, shell=useShell, stdin=PIPE, stdout=file_for_stdout, stderr=PIPE, close_fds=True)
+            ret_out_err['err'].append(sub_proc_output.stderr.read())
+            if file_for_stdout == PIPE:
+                ret_out_err['out'].append(sub_proc_output.stdout.read())
+            else:
+                file_for_stdout.close()
+            # not fit for pipe io - os.system(cmd_string) # need to check for errors in commands.
+        return ret_out_err
+    
+    def set(self, cmds_args, stdout_file_lst):
+        return self.exec_cmds(self.cmds_lst_for_set, cmds_args, stdout_file_lst)
+    def get(self, cmds_args, stdout_file_lst):
+        return self.exec_cmds(self.cmds_lst_for_get, cmds_args, stdout_file_lst)
+    
+    def set_new(self, cmds_args_lst = [], cmds_args = [], useShell=False, appendStdOut = False):
+        if cmds_args_lst == []:
+            cmds_args_lst = self.cmds_lst_for_set
+        return self.exec_cmds_new(cmds_args_lst, cmds_args, useShell, appendStdOut)
+    def get_new(self, cmds_args_lst = [], cmds_args = [], useShell=False, appendStdOut = False):
+        if cmds_args_lst == []:
+            cmds_args_lst = self.cmds_lst_for_get 
+        return self.exec_cmds_new(cmds_args_lst, cmds_args, useShell, appendStdOut)
+    
+
+def validate_input1(validator, value): #temporarily disabling this - TBD
+    try:
+        validator(value)
+    except ValidationError, _err:
+        return False
+    return True
+
+
+def validate_input(validator, value):
+    a = True
+    #try:
+        #a, b = validator(value)
+    #except ValidationError, err:
+    #    return False
+    return a
+
+
+def dotted_decimal_to_int(ip):
+    """
+    Converts a dotted decimal IP address string to a 32 bit integer
+    """
+    bytes = ip.split('.')
+    ip_int = 0
+    for b in bytes:
+        ip_int = (ip_int << 8) + int(b)
+    return ip_int
+
+
+def same_subnet(ip1, ip2, netmask):
+    """
+    Checks whether the two ip addresses are on the same subnet as
+    determined by the netmask argument. All of the arguments are
+    dotted decimal IP address strings.
+    """
+    if ip1 == '' or ip2 == '' or netmask == '':
+        return False
+    ip1_int = dotted_decimal_to_int(ip1)
+    ip2_int = dotted_decimal_to_int(ip2)
+    netmask_int = dotted_decimal_to_int(netmask)
+    return (ip1_int & netmask_int) == (ip2_int & netmask_int)
+
+
+class NetworkConfig(OsWrapper):
+    def __init__(self, name="network_config"):
+        OsWrapper.__init__(self, name, [], [])
+
+
+    def rewrite_etc_network_interfaces(self, controller, interfaces, ret_result):
+        """
+        Return True when the /etc/network/interfaces is rewritten. Return False otherwise.
+        The file is rewritten only when the intended new contents is different from
+        the old contents, this is an attempt to not purturb the network unless something
+        really changed.
+        """
+
+        gateway = controller['fields']['default_gateway']
+        if (gateway != ''):
+            (r, m) = self.validate_ip(gateway)
+            if not r:
+                ret_result['err'].append("Default gateway: %s" % m)
+                gateway = ''
+
+        changed = False
+        new_conf = []
+        new_conf.append("# WARNING this file is automanaged by BSN controller\n")
+        new_conf.append("# DO NOT EDIT here, use CLI with 'configure'\n")
+        new_conf.append("auto lo\niface lo inet loopback\n\n")
+        for interface in interfaces:
+            if (interface['fields']['controller'] == controller['pk']):
+                num = interface['fields']['number']
+                new_conf.append("auto eth{0}\n".format(num))
+                if (interface['fields']['mode'] == 'dhcp'):
+                    new_conf.append("iface eth{0} inet dhcp\n".format(num))
+                else:
+                    ip = interface['fields']['ip']
+                    netmask = interface['fields']['netmask']
+                    if (ip != ""):
+                        (r, m) = self.validate_ip(ip)
+                        if not r:
+                            ret_result['err'].append(
+                                "Ethernet %s IP address %s: %s" % (num, ip, m))
+                            ip = ""
+                    if (netmask != ""):
+                        (r, m) = self.validate_ip(netmask)
+                        if not r:
+                            ret_result['err'].append(
+                                "Ethernet %s netmask %s: %s" % (num, netmask, m))
+                            netmask = ""
+        
+                    new_conf.append("iface eth{0} inet static\n".format(num))
+                    if (ip != ""):
+                        new_conf.append("    address {0}\n".format(ip))
+                    if (netmask != ""):
+                        new_conf.append("    netmask {0}\n".format(netmask))
+                    if same_subnet(gateway, ip, netmask):
+                        new_conf.append("    gateway {0}\n".format(gateway))
+                new_conf.append("\n")
+
+        f = open("/etc/network/interfaces", "r")
+        if (''.join(new_conf) != f.read()):
+            f.close()
+            f = open("/etc/network/interfaces", "w")
+            f.write(''.join(new_conf))
+            changed = True
+        f.close()
+        return changed
+
+    def rewrite_etc_resolve_conf(self, controller, dns_servers, ret_result):
+        """
+        Return True when the /etc/resolv.conf is rewritten. Return False otherwise.
+        The file is rewritten only when the intended new contents is different from
+        the old contents, this is an attempt to not purturb the network unless something
+        really changed.
+        """
+
+        changed = False
+        new_conf = []
+        domain_name = controller['fields']['domain_name']
+        if (domain_name != ""):
+            new_conf.append("domain {0}\nsearch {1}\n".format(domain_name,
+                                                              domain_name))
+        
+        if (controller['fields']['domain_lookups_enabled'] == True):
+            for dns in dns_servers:
+                if (dns['fields']['controller'] == controller['pk']):
+                    ip = dns['fields']['ip']
+                    if (ip != ""):
+                        (r, m) = self.validate_ip(ip)
+                        if not r:
+                            ret_result['err'].append("Name server %s: %s" % (ip, m))
+                            ip = ""
+                    if (ip != ""):
+                        new_conf.append("nameserver {0}\n".format(ip))
+        
+        f = open("/etc/resolv.conf", "r")
+        if (''.join(new_conf) != f.read()):
+            f.close()
+            f = open("/etc/resolv.conf", "w")
+            f.write(''.join(new_conf))
+            changed = True
+
+        f.close()
+        return changed
+
+    def set(self, args_list):
+        # args_list: [controllers, controlleDomainServers, controllerInterfaces]
+        # controllerInterfaces may be empty, which requess no rewrite
+        # of /etc/network/insterfaces
+        ifs_rewrite = False if len(args_list) < 3 else True
+
+        ret_result = {'err': [], 'out': []}
+        controller = json.loads(args_list[0])[0]
+        network_restart = True
+
+        rc_changed = False
+        ni_changed = False
+
+        try:
+            domain_name = controller['fields']['domain_name']
+            new_rc = True
+            if domain_name != "":
+                (r, m) = self.validate_domain(domain_name)
+                if not r:
+                    ret_result['err'].append("Search domain %s: %s"
+                                             % (domain_name, m))
+                    new_rc = False
+
+            if new_rc:
+                rc_changed = self.rewrite_etc_resolve_conf(controller,
+                                                           json.loads(args_list[1]),
+                                                           ret_result)
+            
+            if ifs_rewrite:
+                ni_changed = self.rewrite_etc_network_interfaces(controller,
+                                                                 json.loads(args_list[2]),
+                                                                 ret_result)
+        except Exception, _e:
+            network_restart = False
+            traceback.print_exc()
+
+        # don't restart the network config if resolv.conf was only updated
+        if network_restart and ni_changed:
+            # Kill any dhclients that might be hanging around.  When
+            # switching from dhcp to static config, networking restart
+            # won't kill these since the file has already been
+            # rewritten with a config that doesn't include DHCP.  A
+            # cleaner fix for this is to stop networking before
+            # writing the file and then start it after writing the
+            # file.  Longer-term, it might be better to use
+            # NetworkManager APIs for all of this rather than trying
+            # to manage this config file.
+            k = Popen(["/usr/bin/killall", "dhclient3"],
+                      stdout=PIPE, stderr=PIPE)
+            k.wait()
+            
+            p = Popen(["/usr/sbin/invoke-rc.d",
+                       "networking",
+                       "restart"],
+                       stdout=PIPE, stderr=PIPE)
+            p.wait()
+        
+            if (0 != p.returncode):
+                out = p.stdout.read()
+                ret_result['err'].append("Network restart failed:"
+                                         "%d: %s" % (p.returncode, out))
+
+            # Restart the discover-ip since the controller-interface table has been modified
+            ip = Popen(["/usr/sbin/service",
+                        "discover-ip",
+                        "restart"],
+                        stdout=PIPE, stderr=PIPE)
+
+            # not concerned with error messages from the requested command
+            
+        return ret_result
+
+class DirectNetworkConfig(NetworkConfig):
+    def __init__(self, name="direct_network_config"):
+        NetworkConfig.__init__(self, name)
+
+    def set(self, args_list):
+        gateway = ''
+        ip = ''
+        netmask = ''
+        domain_name = ''
+        dns1 = ''
+        dns2 = ''
+        domain_lookups_enabled = False
+        if args_list[0] == 'static':
+            ip = args_list[1]
+            netmask = args_list[2]
+            gateway = args_list[3]
+            if len(args_list) >= 5:
+                domain_name = args_list[4]
+                if len(args_list) >= 6:
+                    domain_lookups_enabled = True
+                    dns1 = args_list[5]
+                    if len(args_list) >= 7:
+                        dns2 = args_list[6]
+        else:
+            if len(args_list) >= 2:
+                domain_name = args_list[1]
+                if len(args_list) >= 3:
+                    domain_lookups_enabled = True
+                    dns1 = args_list[2]
+                    if len(args_list) >= 4:
+                        dns2 = args_list[3]
+
+        controller_interface=[{'fields' : {'id' : "localhost|ethernet|0", 
+                                          'type' : "ethernet", 'number' : 0, 
+                                          'mode' : args_list[0], 'ip' : ip, 
+                                          'netmask' : netmask, 'controller' : 'localhost'}}]
+        nameservers = []
+        if dns1 != '':
+            nameservers.append({ 'fields' : {'controller' : "localhost", 'priority' : 1, 'ip' : dns1}})
+            if dns2 != '':
+                nameservers.append({ 'fields' : {'controller' : "localhost", 'priority' : 2, 'ip' : dns2}})
+        controller=[{'fields' : {'id' : "localhost", 'domain_name' : domain_name,
+                                 'default_gateway' : gateway, 
+                                 'domain_lookups_enabled' : domain_lookups_enabled}, 'pk' : 'localhost'}]
+        return NetworkConfig.set(self, [json.dumps(controller), json.dumps(nameservers), 
+                              json.dumps(controller_interface)])               
+
+
+class UfwCommand(OsWrapper):
+    def __init__(self, name = "executeufwcommand"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, arg_list):
+        args = arg_list[0].split(" ")
+        self.cmds_lst_for_set = [{'bin_name' : '/usr/sbin/ufw', 'args_lst' : args}]
+        return OsWrapper.set_new(self, [], [[]])
+
+NTP_CONF = """tinker panic 0
+driftfile /var/lib/ntp/ntp.drift
+
+# Enable this if you want statistics to be logged.
+#statsdir /var/log/ntpstats/
+
+statistics loopstats peerstats clockstats
+filegen loopstats file loopstats type day enable
+filegen peerstats file peerstats type day enable
+filegen clockstats file clockstats type day enable
+
+# Specify one or more NTP servers.
+
+# Use servers from the NTP Pool Project. Approved by Ubuntu Technical Board
+# on 2011-02-08 (LP: #104525). See http://www.pool.ntp.org/join.html for
+# more information.
+server %s
+
+# Access control configuration; see /usr/share/doc/ntp-doc/html/accopt.html for
+# details.  The web page <http://support.ntp.org/bin/view/Support/AccessRestrictions>
+# might also be helpful.
+#
+# Note that "restrict" applies to both servers and clients, so a configuration
+# that might be intended to block requests from certain clients could also end
+# up blocking replies from your own upstream servers.
+
+# By default, exchange time with everybody, but don't allow configuration.
+restrict -4 default kod notrap nomodify nopeer noquery
+restrict -6 default kod notrap nomodify nopeer noquery
+
+# Local users may interrogate the ntp server more closely.
+restrict 127.0.0.1
+restrict ::1
+
+# Clients from this (example!) subnet have unlimited access, but only if
+# cryptographically authenticated.
+#restrict 192.168.123.0 mask 255.255.255.0 notrust
+
+
+# If you want to provide time to your local subnet, change the next line.
+# (Again, the address is an example only.)
+#broadcast 192.168.123.255
+
+# If you want to listen to time broadcasts on your local subnet, de-comment the
+# next lines.  Please do this only if you trust everybody on the network!
+#disable auth
+#broadcastclient
+"""
+
+class SetNtpServer(OsWrapper):
+    def __init__(self, name = "setntpserver"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        ret_result = {'err': [], 'out': []}
+        server = "127.127.1.0"
+        if (len(args_list) > 0 and 
+            args_list[0] is not None and 
+            args_list[0] != ""):
+            server = str(args_list[0])
+        ntpconf = NTP_CONF % server
+        changed = False
+
+        f = open("/etc/ntp.conf", "r")
+        if (''.join(ntpconf) != f.read()):
+            f.close()
+            f = open("/etc/ntp.conf", "w")
+            f.write(''.join(ntpconf))
+            changed = True
+
+        if changed:
+            ntp = Popen(["/usr/sbin/service",
+                        "ntp",
+                        "restart"],
+                        stdout=PIPE, stderr=PIPE)
+        return ret_result
+
+
+class SetTimezone(OsWrapper):
+    def __init__(self, name = "settimezone"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/bash',
+             'args_lst' : ['-c', 'echo "%s" >/etc/timezone' % (str(args_list[0]), )]},
+            {'bin_name' : '/bin/bash',
+             'args_lst' : ['-c', '/usr/sbin/dpkg-reconfigure -f noninteractive tzdata 2>&1']},
+            {'bin_name' : '/sbin/initctl',
+             'args_lst' : ['restart', 'rsyslog', ]},
+        ]
+        return OsWrapper.set_new(self, [], [[], [], []])
+
+
+class UnsetTimezone(OsWrapper):
+    def __init__(self, name = "unsettimezone"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/bash',
+             'args_lst' : ['-c', 'echo "Etc/UTC" >/etc/timezone']},
+            {'bin_name' : '/bin/bash',
+             'args_lst' : ['-c', '/usr/sbin/dpkg-reconfigure -f noninteractive tzdata 2>&1']},
+        ]
+        return OsWrapper.set_new(self, [], [[], []])
+
+class SetSyslogServer(OsWrapper):
+    def __init__(self, name = "setsyslogserver"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/sed',
+             'args_lst' : ['-i', '/^*.* @/d', '/etc/rsyslog.conf']},
+            {'bin_name' : '/bin/echo',
+             'args_lst' : ['*.' + str(args_list[1]) + ' @' + str(args_list[0])], 'stdoutfile' : '/etc/rsyslog.conf'},
+            {'bin_name' : '/sbin/initctl',
+             'args_lst' : ['restart', 'rsyslog']},
+        ]
+        return OsWrapper.set_new(self, [], [[], [], []])
+
+class UnsetSyslogServer(OsWrapper):
+    def __init__(self, name = "unsetsyslogserver"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/sed',
+             'args_lst' : ['-i', '/^*./d', '/etc/rsyslog.conf']},
+            {'bin_name' : '/sbin/initctl',
+             'args_lst' : ['restart', 'rsyslog']}
+        ]
+        return OsWrapper.set_new(self, [], [[], []])
+
+class DateTime(OsWrapper):
+    def __init__(self, name = "getdatetime"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        # If we're getting or setting the clock then the first argument
+        # should be either 'utc' or 'local' to indicate whether or not to
+        # use local time.
+        # If we're setting the clock there's a second argument which is the
+        # time to set, formatted as shown in set_cmd_args below.
+        set_parts = args_list[1].split(':')
+        set_param = '%2.2s%2.2s%2.2s%2.2s%4.4s.%2.2s' % (
+                        set_parts[1],
+                        set_parts[2],
+                        set_parts[3],
+                        set_parts[4],
+                        set_parts[0],
+                        set_parts[5],
+                        )
+        cmd_args = [str(set_param)]
+        if str(args_list[0]).lower() == 'utc':
+            cmd_args.insert(0, '-u')
+        #date_info = args_list[0]
+        #is_utc = args_list[1]
+        #date_str = "%s:%s:%s:%s:%s:%s:%s"
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/date', 'args_lst' : cmd_args},
+        ]
+        OsWrapper.set_new(self, [], [[]])
+        return self.get(args_list)
+    
+    def get(self, args_list):
+        cmd_args = ['+%Y:%m:%d:%H:%M:%S:%Z']
+        if str(args_list[0]).lower() == 'utc':
+            cmd_args.insert(0, '-u')
+        self.cmds_lst_for_get = [
+            {'bin_name' : '/bin/date', 'args_lst' : cmd_args},
+        ]
+        return OsWrapper.get_new(self, [], [[]])
+        
+class SetControllerId(OsWrapper):
+    def __init__(self, name = "setcontrollerid"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/sed',
+             'args_lst' : ['-i', '/^controller-id=/d', "%s/run/boot-config" % SDN_ROOT]},
+            {'bin_name' : '/bin/bash',
+             'args_lst' : ['-c', 'echo "controller-id=%s" >> %s/run/boot-config' % (
+                                str(args_list[0]), SDN_ROOT)
+                          ]
+             },
+        ]
+        return OsWrapper.set_new(self, [], [[], []])
+
+class HAFailback(OsWrapper):
+    def __init__(self, name = "hafailback"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/touch',
+             'args_lst' : ["%s/force-one-time-health-check-failure" % SDN_ROOT]},
+        ]
+        return OsWrapper.set_new(self, [], [[]])
+
+#class HAFailover(OsWrapper):
+#    def __init__(self, name = "hafailover"):
+#        OsWrapper.__init__(self, name, [], [])
+#    def set(self, args_list):
+#        self.cmds_lst_for_set = [
+#            {'bin_name' : '/bin/bash',
+#             'args_lst' : ["%s/sys/bin/ha-failover.sh" % SDN_ROOT, str(args_list[0])]},
+#        ]
+#        return OsWrapper.set_new(self, [], [[]])
+
+class SetVrrpVirtualRouterId(OsWrapper):
+    def __init__(self, name = "setvrrpvirtualrouterid"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/bash',
+             'args_lst' : ["%s/sys/bin/set-vrrp-virtual-router-id.sh" % SDN_ROOT, str(args_list[0])]},
+        ]
+        return OsWrapper.set_new(self, [], [[]])
+
+
+def write_controller_restarted():
+    """
+    Write the controller started file with the current timestamp + 5 seconds. This will
+    ensure that health check script ignores the state of sdnplatform for 5 seconds
+    from current time
+    """
+    f = open("/var/run/sdnplatform-healthcheck-disabled", "w")
+    # write time converted to int and then string
+    f.write(str(5 + long(time.time())))
+    f.close()
+
+
+class SetStaticFlowOnlyConfig(OsWrapper):
+    def __init__(self, name = "setstaticflowonlyconfig"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+
+        # write the controller started file. do it before actually resetting
+        # so that there is no race condition with health check script
+        try:
+            write_controller_restarted()
+        except Exception, _e:
+            traceback.print_exc()
+
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/touch',
+             'args_lst' : ["%s/feature/staticflowonlyconfig" % SDN_ROOT]},
+            {'bin_name' : '/sbin/initctl',
+             'args_lst' : ['restart', 'sdnplatform']},
+        ]
+        return OsWrapper.set_new(self, [], [[], [], []])
+
+class RestartSDNPlatform(OsWrapper):
+    def __init__(self, name = "restartsdnplatform"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+
+        # write the controller started file. do it before actually resetting
+        # so that there is no race condition with health check script
+        try:
+            write_controller_restarted()
+        except Exception, _e:
+            traceback.print_exc()
+
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/sbin/initctl',
+             'args_lst' : ['restart', 'sdnplatform']},
+        ]
+        return OsWrapper.set_new(self, [], [[]])
+
+class AbortUpgrade(OsWrapper):
+    def __init__(self, name = "abortupgrade"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/opt/sdnplatform/sys/bin/abort_upgrade.sh',
+             'args_lst' : []},
+        ]
+        return OsWrapper.set_new(self, [], [[]])
+
+class SetDefaultConfig(OsWrapper):
+    def __init__(self, name = "setdefaultconfig"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+
+        # write the controller started file. do it before actually resetting
+        # so that there is no race condition with health check script
+        try:
+            write_controller_restarted()
+        except Exception, _e:
+            traceback.print_exc()
+
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/rm',
+             'args_lst' : ['-f', "%s/feature/staticflowonlyconfig" % SDN_ROOT]},
+            {'bin_name' : '/sbin/initctl',
+             'args_lst' : ['restart', 'sdnplatform']},
+            # if there are other configs their flag files need to be deleted here too
+        ]
+        return OsWrapper.set_new(self, [], [[], [], []])
+
+class SetHostname(OsWrapper):
+    def __init__(self, name = "sethostname"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        hostname = str(args_list[0])
+        self.cmds_lst_for_set = [
+            # replace hostname from /etc/hosts
+            {'bin_name' : '/bin/sed',
+             'args_lst' : ['-i',
+                           r's/^127\.0\.1\.1 .*$$/127.0.1.1 %s/' % hostname,
+                           '/etc/hosts']},
+            # populate /etc/hosts
+            {'bin_name' : '/bin/bash',
+             'args_lst' : ['-c', 'echo "%s" >/etc/hostname' % hostname]},
+            # tell the system about the hostname
+            {'bin_name' : '/bin/hostname',
+             'args_lst' : ['-b', '-F', '/etc/hostname'] },
+        ]
+        return OsWrapper.set_new(self, [], [[], [], [],])
+
+# In PAM, "auth" == authentication (surprise!)
+# XXX roth -- maybe use PAP instead?
+
+# 'sufficient' --> tacacs+ and local authentication are enabled
+AUTHN_TPL = """\
+auth [default=1 success=ignore] pam_succeed_if.so uid >= 10000
+auth sufficient pam_tacplus.so %(servers)s %(secrets)s %(timeout)s service=login protocol=ip login=login
+"""
+
+# In PAM, "account" == authorization
+AUTHZ_TPL = """\
+account [default=1 success=ignore] pam_succeed_if.so uid >= 10000
+account sufficient pam_tacplus.so %(secrets)s %(timeout)s service=login service_av=shell protocol=ip login=login
+"""
+
+# In PAM, "session" == accounting
+ACCT_TPL = """\
+session sufficient pam_tacplus.so %(servers)s %(secrets)s %(timeout)s service=login service_av=shell protocol=ip
+"""
+
+# XXX roth -- keep this in sync with the current release
+# of /etc/pam.d/sshd
+SSHD_TACPLUS_TPL = """\
+# PAM configuration for the Secure Shell service
+
+# Read environment variables from /etc/environment and
+# /etc/security/pam_env.conf.
+auth       required     pam_env.so # [1]
+# In Debian 4.0 (etch), locale-related environment variables were moved to
+# /etc/default/locale, so read that as well.
+auth       required     pam_env.so envfile=/etc/default/locale
+
+# Standard Un*x authentication.
+%(authn)s
+
+# Disallow non-root logins when /etc/nologin exists.
+account    required     pam_nologin.so
+
+# Uncomment and edit /etc/security/access.conf if you need to set complex
+# access limits that are hard to express in sshd_config.
+# account  required     pam_access.so
+
+# Standard Un*x authorization.
+%(authz)s
+
+# Standard Un*x session setup and teardown.
+%(acct)s
+
+# Print the message of the day upon successful login.
+session    optional     pam_motd.so # [1]
+
+# Print the status of the user's mailbox upon successful login.
+session    optional     pam_mail.so standard noenv # [1]
+
+# Set up user limits from /etc/security/limits.conf.
+session    required     pam_limits.so
+
+# Set up SELinux capabilities (need modified pam)
+# session  required     pam_selinux.so multiple
+
+# Standard Un*x password updating.
+@include common-password
+"""
+
+class TacacsPlusConfig(OsWrapper):
+
+    def __init__(self, name="tacacs_plus_config"):
+        OsWrapper.__init__(self, name, [], [])
+        self.config = {}
+        self.hosts = []
+        self.result = dict(err=[], out=[])
+
+    def isEnabled(self):
+        """Is TACACS+ enabled?
+
+        If any of authn/authz/acct is set, *and* there is a non-empty
+        set of TACACS+ hosts, then we should enable the PAM plugin.
+        """
+
+        if (self.hosts 
+            and (self.config['fields']['tacacs_plus_authn']
+                 or self.config['fields']['tacacs_plus_authz']
+                 or self.config['fields']['tacacs_plus_acct'])):
+            return True
+
+        return False
+
+    def disablePamDefault(self):
+        """Disable the default PAM setup.
+
+        This is installed by the initial configuration scripts
+        for the libpam-tacplus DEB.
+        """
+
+        if not os.path.exists("/usr/share/pam-configs/tacplus"):
+            return
+
+        cmd = ("/usr/sbin/pam-auth-update", "--remove", "tacplus",)
+        pipe = Popen(cmd, stdout=PIPE, stderr=PIPE)
+        out, err = pipe.communicate()
+        code = pipe.wait()
+
+        out = (out or "").strip().split("\n")
+        err = (err or "").strip().split("\n")
+
+        if not code:
+            self.result['out'].append("disabled tacplus via pam-auth-update\n")
+        else:
+            self.result['out'].extend([l + "\n" for l in out])
+            self.result['err'].extend([l + "\n" for l in err])
+            self.result['err'].append("pam-auth-update failed\n")
+
+    def disablePam(self):
+        """Disable the TACACS+ PAM plugin."""
+        m = dict(authn="@include common-auth",
+                 authz="@include common-account",
+                 acct="@include common-session")
+        self.writeLocked("/etc/pam.d/sshd", SSHD_TACPLUS_TPL % m)
+
+    def readLocked(self, path):
+
+        fd = open(path, "r")
+
+        fcntl.lockf(fd, fcntl.LOCK_SH)
+        try:
+            buf = fd.read()
+        except Exception, what:
+            fcntl.lockf(fd, fcntl.LOCK_UN)
+            fd.close()
+            self.result['err'].append(str(what) + "\n")
+            self.result['err'].append("cannot read %s\n" % path)
+            return None
+        fcntl.lockf(fd, fcntl.LOCK_UN)
+
+        fd.close()
+
+        return buf
+
+    def writeLocked(self, path, buf, backup=True):
+        
+        if backup:
+            shutil.copy2(path, path + "-")
+
+        fd = open(path, "w")
+
+        fcntl.lockf(fd, fcntl.LOCK_EX)
+        try:
+            fd.write(buf)
+        except Exception, what:
+            fcntl.lockf(fd, fcntl.LOCK_UN)
+            fd.close()
+            self.result['err'].append(str(what) + "\n")
+            self.result['err'].append("cannot write %s\n" % path)
+            return
+        fcntl.lockf(fd, fcntl.LOCK_UN)
+
+        fd.close()
+
+    def enableNss(self):
+        """Enable the NSS plugin."""
+        
+        buf = self.readLocked("/etc/nsswitch.conf")
+        if buf is None: return
+
+        p = buf.find("\npasswd:")
+        q = buf.find("\n", p+8)
+        if p < 0 or q < 0:
+            self.result['err'].append("cannot find passwd entry"
+                                      " in /etc/nsswitch.conf\n")
+            return
+
+        f = buf[p+8:q]
+        if "remoteuser" in f:
+            self.result['out'].append("remoteuser already enabled"
+                                      " in /etc/nssswitch.conf\n")
+            return
+
+        self.result['out'].append("enabling remoteuser"
+                                  " in /etc/nssswitch.conf\n")
+        f = " " + f.strip() + " remoteuser"
+        buf = buf[:p+8] + f + buf[q:]
+
+        self.writeLocked("/etc/nsswitch.conf", buf)
+
+    def disableNss(self):
+        """Disable the NSS plugin."""
+
+        buf = self.readLocked("/etc/nsswitch.conf")
+        if buf is None: return
+        
+        p = buf.find("\npasswd:")
+        q = buf.find("\n", p+8)
+        if p < 0 or q < 0:
+            self.result['err'].append("cannot find passwd entry"
+                                      " in /etc/nsswitch.conf\n")
+            return
+
+        l = buf[p+8:q].strip().split()
+        if "remoteuser" not in l:
+            self.result['out'].append("remoteuser already disabled"
+                                      " in /etc/nssswitch.conf\n")
+            return
+
+        self.result['out'].append("disabling remoteuser"
+                                  " in /etc/nssswitch.conf\n")
+        l.remove("remoteuser")
+        f = " " + " ".join(l)
+        buf = buf[:p+8] + f + buf[q:]
+
+        self.writeLocked("/etc/nsswitch.conf", buf)
+
+    def enablePam(self):
+        """Enable the TACACS+ PAM plugin.
+
+        * construct consolidate server and secret lists
+        * generate a timeout line
+        * pick sufficient/required clauses as indicated by enable
+          flags in the JSON
+
+        See
+        http://tacplus.git.sourceforge.net/git/gitweb.cgi?p=tacplus/tacplus;a=blob_plain;f=README;hb=HEAD
+        """
+        
+        # disable TACACS+ while updating
+        self.disableNss()
+        self.disablePam()
+
+        # XXX roth -- field is 'ip', but since it is the primary key,
+        # it get mapped to 'pk'.  Go figure.
+        def svrClause(h):
+            self.result['out'].append("enabling host %s\n" % h['pk'])
+            return 'server=%s' % h['pk']
+        
+        servers = " ".join([svrClause(h) for h in self.hosts])
+
+        def keyClause(h):
+            """Secret for this host, possibly the global one."""
+            if h['fields']['key']:
+                return "secret=%s" % h['fields']['key']
+            if self.config['fields']['key']:
+                return "secret=%s" % self.config['fields']['key']
+            return ""
+
+        secrets = " ".join([keyClause(h) for h in self.hosts])
+
+        m = dict(servers=servers, secrets=secrets)
+
+        if self.config['fields']['timeout']:
+            m['timeout'] = 'timeout=%s' % self.config['fields']['timeout']
+        else:
+            m['timeout'] = ''
+
+        isLocal = self.config['fields']['local_authn']
+        isTacacs = self.config['fields']['tacacs_plus_authn']
+
+        authn = []
+        if isTacacs:
+            authn.append(AUTHN_TPL % m)
+        if isLocal:
+            authn.append("@include common-auth")
+
+        isLocal = self.config['fields']['local_authz']
+        isTacacs = self.config['fields']['tacacs_plus_authz']
+
+        authz = []
+        if isTacacs:
+            authz.append(AUTHZ_TPL % m)
+        if isLocal:
+            authz.append("@include common-account")
+
+        isTacacs = self.config['fields']['tacacs_plus_acct']
+
+        acct = []
+        if isTacacs:
+            acct.append(ACCT_TPL % m)
+        acct.append("@include common-session")
+
+        # enable userid lookups
+        self.enableNss()
+
+        # write out sshd PAM config as a final step to enable it
+        m = dict(authn="\n".join(authn),
+                 authz="\n".join(authz),
+                 acct="\n".join(acct))
+        self.writeLocked("/etc/pam.d/sshd", SSHD_TACPLUS_TPL % m)
+
+    def set(self, args_list):
+
+        self.config = json.loads(args_list[0])[0]
+        self.hosts = json.loads(args_list[1])
+
+        self.disablePamDefault()
+
+        if not self.isEnabled():
+            try:
+                self.disableNss()
+                self.disablePam()
+                self.result['out'].append('TACACS+ (via PAM) is now disabled\n')
+            except Exception:
+                traceback.print_exc()
+                self.result['err'].append('TACACS+ (via PAM) disable failed\n')
+            return self.result
+
+        # else, enable the PAM module
+        try:
+            self.enableNss()
+            self.enablePam()
+            self.result['out'].append('TACACS+ (via PAM) is now enabled\n')
+        except Exception:
+            # XXX roth -- maybe back out here and *disable* PAM
+            # so that we do not end up with a broken PAM config
+            traceback.print_exc()
+            self.result['err'].append('TACACS+ (via PAM) enable failed\n')
+
+        return self.result
+
+#
+# get_system_version_string
+#
+# Gets the version string of the controller.
+# Reference implementation is in sdncon/rest/views/do_system_version
+#
+def get_system_version_string():
+    version = "SDN OS 1.0 - custom version"
+    try:
+        f = open("%s/release" % SDN_ROOT, 'r')
+        version = f.read()
+        f.close()
+    except:
+        pass
+    return version
+
+#
+# rewrite_etc_snmpd_conf
+#
+# API to rewrite the /etc/snmp/snmpd.conf file based on latest config
+#
+def rewrite_etc_snmpd_conf(community, location, contact, ret_result):
+    """
+    Return True when the /etc/snmp/snmpd.conf is rewritten. Return False 
+    otherwise. The file is rewritten only when the intended new contents
+    is different from the old contents, this is an attempt to not restart
+    the snmp agent unless something really changed.
+    """
+
+    changed = False
+    new_conf = []
+    # start with default configuration of the file
+    new_conf.append("# Default Configuration for the SNMP daemon\n")
+    new_conf.append("# Agent address\n")
+    new_conf.append("agentAddress udp:161,udp6:[::1]:161\n")
+    new_conf.append("# System Object ID\n")
+    new_conf.append("sysObjectID %s\n" % (BSN_ENTERPRISE_OID_CONTROLLER))
+    new_conf.append("# System Description\n")
+    new_conf.append("sysDescr %s\n"%(get_system_version_string()))
+
+    #add community, location, contact information to the file if not there already
+    if community != '':
+        new_conf.append("rocommunity %s\n" % community)
+    if location != '': 
+        new_conf.append("sysLocation %s\n" % location)
+    if contact != '': 
+        new_conf.append("sysContact %s\n" % contact)
+
+    f = open("/etc/snmp/snmpd.conf", "r")
+    if (''.join(new_conf) != f.read()):
+        f.close()
+        f = open("/etc/snmp/snmpd.conf", "w")
+        f.write(''.join(new_conf))
+        changed = True
+
+    f.close()
+
+    return changed
+
+#
+# One of the entry in the snmp server configuration changed
+#
+class SetSnmpServerConfig(OsWrapper):
+    def __init__(self, name = "setsnmpserverconfig"):
+        OsWrapper.__init__(self, name, [], [])
+
+
+    def set(self, args_list):
+        # args_list: [server_enable, community, location, contact, enable_changed]
+        print "SnmpServerConfig Args List: ", args_list
+        server_enable = args_list[0]
+        community = args_list[1]
+        location = args_list[2]
+        contact = args_list[3]
+        enable_changed = args_list[4]
+
+        ret_result = {'err': [], 'out': []}
+
+        try:
+            # rewrite /etc/snmp/snmpd.conf file
+            need_restart = rewrite_etc_snmpd_conf(community, location, contact, ret_result)
+        except Exception, _e:
+            need_restart = False
+            traceback.print_exc()
+
+        if server_enable == 'True' and (need_restart or enable_changed == 'True'):
+            self.cmds_lst_for_set = [
+                # set snmpdrun=yes
+                {'bin_name' : '/bin/sed',
+                 'args_lst' : ['-i', 's/SNMPDRUN=no/SNMPDRUN=yes/',
+                               '/etc/default/snmpd']},
+                # restart snmpd service 
+                {'bin_name' : '/usr/sbin/service',
+                 'args_lst' : ['snmpd', 'restart']},
+            ]
+            return OsWrapper.set_new(self, [], [[], []])
+
+        elif server_enable == 'False' and enable_changed == 'True':
+            self.cmds_lst_for_set = [
+                # set snmpdrun=no
+                {'bin_name' : '/bin/sed',
+                 'args_lst' : ['-i', 's/SNMPDRUN=yes/SNMPDRUN=no/',
+                               '/etc/default/snmpd']},
+                # stop snmpd service 
+                {'bin_name' : '/usr/sbin/service',
+                 'args_lst' : ['snmpd', 'stop']},
+            ]
+            return OsWrapper.set_new(self, [], [[], []])
+
+        return ret_result
+
+
+#
+# The row entry in the snmp server config table was default and deleted
+#
+class UnsetSnmpServerConfig(OsWrapper):
+    def __init__(self, name = "unsetsnmpserverconfig"):
+        OsWrapper.__init__(self, name, [], [])
+
+    def set(self, args_list):
+        # args_list: []
+        ret_result = {'err': [], 'out': []}
+
+        try:
+            # rewrite /etc/snmp/snmpd.conf to default
+            rewrite_etc_snmpd_conf('', '', '', ret_result)
+        except Exception, _e:
+            traceback.print_exc()
+
+        # now stop the server if its there
+        self.cmds_lst_for_set = [
+            # set snmpdrun=no
+            {'bin_name' : '/bin/sed',
+             'args_lst' : ['-i', 's/SNMPDRUN=yes/SNMPDRUN=no/',
+                           '/etc/default/snmpd']},
+            # stop snmpd service 
+            {'bin_name' : '/usr/sbin/service',
+             'args_lst' : ['snmpd', 'stop']},
+        ]
+        return OsWrapper.set_new(self, [], [[], []])
+
+class SetImagesUserSSHKey(OsWrapper):
+    def __init__(self, name = 'setimagesusersshkey'):
+        OsWrapper.__init__(self, name, [], [])
+
+    def set(self, args_list):
+        sshkey = str(args_list[0]) 
+        # cat the ssh key to the file
+        # set the images user shell to be scponly
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/usr/sbin/usermod',
+             'args_lst' : ['-s', '/usr/bin/scponly', 'images']},
+            {'bin_name' : '/bin/echo',
+             'args_lst' : [sshkey], 
+             'stdoutfile' : '/home/images/.ssh/authorized_keys'},
+        ]
+        return OsWrapper.set_new(self, [], [[], []])
+
+class ReloadController(OsWrapper):
+    def __init__(self, name = 'reloadcontroller'):
+        OsWrapper.__init__(self, name, [], [])
+
+    def set(self, args_list):
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/sbin/reboot',
+             'args_list' : []},
+        ]
+        return OsWrapper.set_new(self, [], [[]])
+
+UPGRADE_IMAGE_FILE_PATH = '/tmp/upgrade-images'
+UPGRADE_IMAGE_MANIFEST = '/tmp/upgrade-image-manifest'
+UPGRADE_PACKAGE_DIRECTORY = '/tmp/upgrade-package/'
+
+class ExtractUpgradePkgManifest(OsWrapper):
+    def __init__(self, name = 'extractupgradepkg'):
+        OsWrapper.__init__(self, name, [], [])
+
+    def set(self, args_list):
+        imageName = str(args_list[0])
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/rm',
+             'args_lst' : [UPGRADE_IMAGE_MANIFEST]},
+            {'bin_name' : '/bin/touch',
+             'args_lst' : [UPGRADE_IMAGE_MANIFEST]},
+            {'bin_name' : '/usr/bin/unzip',
+             'args_lst' : ['-p', imageName, 'Manifest'],
+             'stdoutfile' : UPGRADE_IMAGE_MANIFEST},
+        ]
+        return OsWrapper.set_new(self, [], [[], [], []], useShell=False, appendStdOut=False)
+
+    def get(self, args_list):
+        self.cmds_lst_for_get = [
+            {'bin_name' : '/bin/cat',
+             'args_lst': [UPGRADE_IMAGE_MANIFEST]},
+        ]
+        return OsWrapper.get_new(self, [], [[]])
+
+class GetLatestUpgradePkg(OsWrapper):
+    def __init__(self, name = 'getlatestupgradepkg'):
+        OsWrapper.__init__(self, name, [], [])
+
+    # TODO -
+    # This is ghetto, it just finds the last zip
+    # file in the dir. FIX THIS!
+    def get(self, args_list):
+        execStr = 'ls -t /home/images/*.pkg | grep pkg | head -1 > ' + UPGRADE_IMAGE_FILE_PATH
+        self.cmds_lst_for_get = [
+            {'bin_name' : execStr, 
+             'args_lst' : []},
+        ]
+        return OsWrapper.get_new(self, [], [[]], useShell=True)
+
+class CatUpgradeImagesFile(OsWrapper):
+    def __init__(self, name = 'catupgradeimagesfile'):
+        OsWrapper.__init__(self, name, [], [])
+
+    def get(self, args_list):
+        self.cmds_lst_for_get = [
+            {'bin_name' : '/bin/cat',
+             'args_lst' : [UPGRADE_IMAGE_FILE_PATH]},
+        ]
+        return OsWrapper.get_new(self, [], [[]])
+
+class ExecuteUpgradeStep(OsWrapper):
+    def __init__(self, name = 'executeupgradestep'):
+        OsWrapper.__init__(self, name, [], [])
+
+    def get(self, args_list):
+        ret_result = {'err': [], 'out': []}
+        
+        try:
+            manifest = json.loads(exec_os_wrapper("ExtractUpgradePkgManifest", 'get')['out'])
+        except ValueError:
+            ret_result['err'].append("Corrupted manifest!")
+            return ret_result
+
+        stepToExec = None
+        for step in manifest:
+            if step['step'] == int(args_list[0]):
+                stepToExec = step['action']
+                break;
+
+        if stepToExec == None:
+            ret_result['err'].append("Step %s not found in upgrade package manifest!" %
+                str(args_list[0]))
+            return ret_result
+        
+        upgradePkg = args_list[1]
+        stepScript = tempfile.NamedTemporaryFile(delete=False)
+        scriptName = "scripts/%s" % step['action'].strip()
+        step = check_output(["unzip", "-p", upgradePkg, scriptName])
+        stepScript.write(step)
+        stepScript.flush()
+        stepScript.close()
+        os.chmod(stepScript.name, stat.S_IXUSR | stat.S_IWUSR | stat.S_IRUSR)
+
+        try:
+            ret = check_output([stepScript.name] + args_list[1:],
+                               stderr=PIPE)
+            ret_result['out'].append(stripped(ret.strip()))
+        except CalledProcessError, exception:
+            ret_result['err'].append("Error running %s\nreturn code %s\nOutput:\n%s" %
+                    (exception.cmd, exception.returncode, stripped(exception.output)))
+        return ret_result
+
+class CleanupOldUpgradeImages(OsWrapper):
+    def __init__(self, name = 'cleanupoldupgradeimages'):
+        OsWrapper.__init__(self, name, [], [])
+
+    def get(self, args_list):
+        # Removes all the .pkg files execpt for the newest one.
+        # It handles the case where there is only 1 package, we don't delete it.
+        execStr = 'c=`ls /home/images/*.pkg | wc -l`; if [ "$c" -gt 1 ]; then ls -t -r /home/images/*.pkg | head -n -1 | xargs rm; fi'
+        self.cmds_lst_for_get = [
+            {'bin_name' : execStr, 
+             'args_lst' : []},
+        ]
+        return OsWrapper.get_new(self, [], [[]], useShell=True)
+
+class Decommission(OsWrapper):
+    def __init__(self, name = "decommission"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/bash',
+             'args_lst' : ["%s/sys/bin/remove-node.sh" % SDN_ROOT, str(args_list[0])]},
+        ]
+        return OsWrapper.set_new(self, [], [[]])
+
+class DecommissionLocal(OsWrapper):
+    def __init__(self, name = "decommissionlocal"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/bash',
+             'args_lst' : ["%s/sys/bin/remove-node-local.sh" % SDN_ROOT, str(args_list[0])]},
+        ]
+        return OsWrapper.set_new(self, [], [[]])
+
+class ResetBsc(OsWrapper):
+    def __init__(self, name = "resetbsc"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/bash',
+             'args_lst' : ["%s/sys/bin/resetbsc" % SDN_ROOT, '--force']},
+        ]
+        return OsWrapper.set_new(self, [], [[]])
+
+class WriteDataToFile(OsWrapper):
+    def __init__(self, name = 'writedatatofile'):
+        OsWrapper.__init__(self, name, [], [])
+
+    def set(self, args_list):
+        data = str(args_list[0])
+        path = str(args_list[1])
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/touch',
+             'args_lst' : [path]},
+            {'bin_name' : '/bin/echo',
+             'args_lst' : [data], 
+             'stdoutfile' : path},
+        ]
+        return OsWrapper.set_new(self, [], [[], []])
+
+class DiffConfig(OsWrapper):
+    def __init__(self, name = "scpconfig"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/opt/sdnplatform/sys/bin/diff_config.py',
+             'args_lst' : [str(args_list[0]), str(args_list[1])]},
+        ]
+        return OsWrapper.set_new(self, [], [[]])
+
+class RollbackConfig(OsWrapper):
+    def __init__(self, name = "upgradeconfig"):
+        OsWrapper.__init__(self, name, [], [])
+    def set(self, args_list):
+        self.cmds_lst_for_set = [
+            {'bin_name' : '/bin/bash',
+             'args_lst' : ["%s/sys/bin/rollback-config.sh" % SDN_ROOT, str(args_list[0])]},
+        ]
+        return OsWrapper.set_new(self, [], [[]])
+
+def stripped(x):
+    # remove ascii escape
+    return "".join([i for i in x if ord(i) != 27])
+#
+# exec_os_wrapper
+#
+def exec_os_wrapper(obj_type, oper, args_list = None):
+    """
+    Execute the oswrapper.py using sudo(), raising an exception
+    for any stderr output from the executed script
+    """
+    # Safety check; only run if this file exists
+    if not os.path.exists("%s/con" % SDN_ROOT):
+        print "exec_os_wrapper: not an installed controller environment"
+        return {'out' : '', 'err' : ''}
+    if os.path.exists('/etc/not-controller'):
+        # XXX should issue some alert here
+        print "exec_os_wrapper: /etc/not-controller exists"
+        return {'out' : '', 'err' : ''}
+
+    oswrapper = os.path.dirname(__file__) + "/oswrapper.py"
+    full_cmd_string = ["/usr/bin/sudo", oswrapper, obj_type, oper]
+    if args_list:
+        full_cmd_string += [str(arg) for arg in args_list]
+
+    sub_proc_output = Popen(full_cmd_string, shell=False,
+                            stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
+
+    sub_proc_output.wait()
+    stderr = sub_proc_output.stderr.read()
+    stdout = sub_proc_output.stdout.read()
+    returncode = sub_proc_output.returncode
+
+    if returncode:
+        raise Exception("oswrapper: %s %s: exit code %d: %s" %
+                        (obj_type, oper, returncode, stderr))
+    
+    if len(stderr) != 0 and not stderr.isspace():
+        print " ".join(full_cmd_string), stderr
+
+    # ?!?
+    return {'out' : stdout, 'err' : stderr}
+
+
+def main(argv):
+    obj_type_map = {'ExecuteUfwCommand'       : UfwCommand,
+                    'SetNtpServer'            : SetNtpServer,
+                    'SetTimezone'             : SetTimezone,
+                    'UnsetTimezone'           : UnsetTimezone,
+                    'NetworkConfig'           : NetworkConfig,
+                    'SetSyslogServer'         : SetSyslogServer,
+                    'UnsetSyslogServer'       : UnsetSyslogServer,
+                    'DateTime'                : DateTime,
+                    'ControllerId'            : SetControllerId,
+                    'HAFailback'              : HAFailback,
+                    'SetHostname'             : SetHostname,
+                    'SetVrrpVirtualRouterId'  : SetVrrpVirtualRouterId,
+                    'SetStaticFlowOnlyConfig' : SetStaticFlowOnlyConfig,
+                    'SetDefaultConfig'        : SetDefaultConfig,
+                    'TacacsPlusConfig'        : TacacsPlusConfig,
+                    'SetSnmpServerConfig'     : SetSnmpServerConfig,
+                    'UnsetSnmpServerConfig'   : UnsetSnmpServerConfig,
+                    'SetImagesUserSSHKey'     : SetImagesUserSSHKey,
+                    'ReloadController'        : ReloadController,
+                    'ExtractUpgradePkgManifest' : ExtractUpgradePkgManifest,
+                    'GetLatestUpgradePkg'     : GetLatestUpgradePkg,
+                    'CatUpgradeImagesFile'    : CatUpgradeImagesFile,
+                    'ExecuteUpgradeStep'      : ExecuteUpgradeStep,
+                    'DirectNetworkConfig'     : DirectNetworkConfig,
+                    'CleanupOldUpgradeImages' : CleanupOldUpgradeImages,
+                    'RestartSDNPlatform'       : RestartSDNPlatform,
+                    'AbortUpgrade'            : AbortUpgrade,
+                    'Decommission'            : Decommission,
+                    'DecommissionLocal'       : DecommissionLocal,
+                    'RollbackConfig'          : RollbackConfig, 
+                    'DiffConfig'              : DiffConfig,
+                    'ResetBsc'                : ResetBsc,
+                    'WriteDataToFile'         : WriteDataToFile,
+                    }
+    ret_result = {'err': ["insufficient or invalid args"], 'out': []}
+    if len(argv) >= 3:
+        if argv[1] in obj_type_map:
+            obj_type = obj_type_map[argv[1]]
+            x = obj_type()
+            if argv[2] == 'set':
+                ret_result = x.set(argv[3:])
+            elif argv[2] == 'get':
+                ret_result = x.get(argv[3:])
+    
+    # The ret_result entries are lists of strings from the output's of
+    # various commands.
+    print >>sys.stdout, ''.join(ret_result['out'])
+    print >>sys.stderr, ''.join(ret_result['err'])
+
+if __name__ == '__main__':
+    main(sys.argv)
diff --git a/cli/sdncon/controller/tests.py b/cli/sdncon/controller/tests.py
new file mode 100755
index 0000000..9ab2783
--- /dev/null
+++ b/cli/sdncon/controller/tests.py
@@ -0,0 +1,16 @@
+#
+# Copyright (c) 2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+#      http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+
diff --git a/cli/sdncon/controller/views.py b/cli/sdncon/controller/views.py
new file mode 100755
index 0000000..9ab2783
--- /dev/null
+++ b/cli/sdncon/controller/views.py
@@ -0,0 +1,16 @@
+#
+# Copyright (c) 2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+#      http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+