Base net-virt CLI files on top of which ONOS specific changes will be done
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'