From 3c4ab0157b0f9be2a16b77d6e7732d6fb9676251 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Wed, 12 Nov 2014 16:41:26 -0800 Subject: [PATCH 01/81] Added Vagrantfile and resources to install a SciPass in a VM --- Vagrantfile | 37 ++++++ resources/puppet/hiera.yaml | 10 ++ resources/puppet/hieradata/hosts.json | 8 ++ resources/puppet/manifests/base.pp | 12 ++ resources/puppet/manifests/mininet.pp | 103 +++++++++++++++ resources/puppet/manifests/scipass.pp | 123 ++++++++++++++++++ resources/puppet/scripts/bootstrap.sh | 56 ++++++++ .../puppet/templates/compute.local.conf.erb | 55 ++++++++ .../puppet/templates/control.local.conf.erb | 64 +++++++++ resources/puppet/templates/hosts.erb | 8 ++ .../templates/ml2-compute.local.conf.erb | 103 +++++++++++++++ resources/puppet/templates/ml2.local.conf.erb | 58 +++++++++ 12 files changed, 637 insertions(+) create mode 100644 Vagrantfile create mode 100644 resources/puppet/hiera.yaml create mode 100644 resources/puppet/hieradata/hosts.json create mode 100644 resources/puppet/manifests/base.pp create mode 100644 resources/puppet/manifests/mininet.pp create mode 100644 resources/puppet/manifests/scipass.pp create mode 100644 resources/puppet/scripts/bootstrap.sh create mode 100644 resources/puppet/templates/compute.local.conf.erb create mode 100644 resources/puppet/templates/control.local.conf.erb create mode 100644 resources/puppet/templates/hosts.erb create mode 100644 resources/puppet/templates/ml2-compute.local.conf.erb create mode 100644 resources/puppet/templates/ml2.local.conf.erb diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..a5594ff --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,37 @@ +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! + + +# This uses Vagrant Cloud for simplicity so it needs to have Vagrant 1.5 +VAGRANTFILE_API_VERSION = "2" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + + config.vm.provision "shell", path: "resources/puppet/scripts/bootstrap.sh" + + config.vm.provision "puppet" do |puppet| + puppet.hiera_config_path = "resources/puppet/hiera.yaml" + puppet.working_directory = "/vagrant/resources/puppet" + puppet.manifests_path = "resources/puppet/manifests" + puppet.manifest_file = "base.pp" + end + + # Install single host with mininet and ODL + + config.vm.define "scipass" do |scipass| + scipass.vm.box = "ubuntu/trusty64" + scipass.vm.hostname = "scipass" + scipass.vm.network "private_network", ip: "192.168.50.70" + scipass.vm.provider :virtualbox do |vb| + vb.memory = 4096 + end + scipass.vm.provider "vmware_fusion" do |vf| + vf.vmx["memsize"] = "4096" + end + scipass.vm.provision "puppet" do |puppet| + puppet.hiera_config_path = "resources/puppet/hiera.yaml" + puppet.working_directory = "/vagrant/resources/puppet" + puppet.manifests_path = "resources/puppet/manifests" + puppet.manifest_file = "scipass.pp" + end + end +end \ No newline at end of file diff --git a/resources/puppet/hiera.yaml b/resources/puppet/hiera.yaml new file mode 100644 index 0000000..8e2f646 --- /dev/null +++ b/resources/puppet/hiera.yaml @@ -0,0 +1,10 @@ +--- +:backends: + - yaml + - json +:yaml: + :datadir: /vagrant/resources/puppet/hieradata +:json: + :datadir: /vagrant/resources/puppet/hieradata +:hierarchy: + - hosts diff --git a/resources/puppet/hieradata/hosts.json b/resources/puppet/hieradata/hosts.json new file mode 100644 index 0000000..ee7086b --- /dev/null +++ b/resources/puppet/hieradata/hosts.json @@ -0,0 +1,8 @@ +{ + "hosts": { + "scipass": { + "name": "scipass", + "ipaddress": "192.168.50.70" + } +} +} \ No newline at end of file diff --git a/resources/puppet/manifests/base.pp b/resources/puppet/manifests/base.pp new file mode 100644 index 0000000..3f095f2 --- /dev/null +++ b/resources/puppet/manifests/base.pp @@ -0,0 +1,12 @@ +package {"git": + ensure => "installed" +} + +$hosts = hiera('hosts') + +file { "/etc/hosts": + ensure => file, + owner => "root", + group => "root", + content => template('/vagrant/resources/puppet/templates/hosts.erb') +} diff --git a/resources/puppet/manifests/mininet.pp b/resources/puppet/manifests/mininet.pp new file mode 100644 index 0000000..91ac25a --- /dev/null +++ b/resources/puppet/manifests/mininet.pp @@ -0,0 +1,103 @@ +$deps = [ 'build-essential', + 'debhelper', + 'dkms', + 'fakeroot', + 'graphviz', + 'linux-headers-generic', + 'python-all', + 'python-qt4', + 'python-zopeinterface', + 'python-twisted-conch', + 'python-twisted-web', + 'xauth' +] + +package { $deps: + ensure => installed, +} + +vcsrepo { '/home/vagrant/mininet': + ensure => present, + provider => git, + user => 'vagrant', + source => 'git://github.com/mininet/mininet', + revision => '2.1.0p2', + before => Exec['Install Mininet'] +} + +exec { 'Install Mininet': + command => 'bash mininet/util/install.sh -nf > /dev/null', + cwd => '/home/vagrant', + user => 'vagrant', + path => $::path, + timeout => 0 +} + +exec {'openvswitch-2.1.2.tar.gz': + command => 'wget http://openvswitch.org/releases/openvswitch-2.1.2.tar.gz', + cwd => '/home/vagrant', + path => $::path, + user => 'vagrant' +} + +exec { 'Extract Open vSwitch': + command => 'tar -xvf openvswitch-2.1.2.tar.gz', + cwd => '/home/vagrant', + user => 'vagrant', + path => $::path, + timeout => 0, + require => Exec['openvswitch-2.1.2.tar.gz'] +} + +exec { 'Compile Open vSwitch': + command => 'fakeroot debian/rules binary', + cwd => '/home/vagrant/openvswitch-2.1.2', + user => 'root', + path => $::path, + timeout => 0, + require => [Exec['Extract Open vSwitch'], Package[$deps]] +} + +package { 'openvswitch-common': + ensure => installed, + provider => dpkg, + source => '/home/vagrant/openvswitch-common_2.1.2-1_amd64.deb', + require => Exec['Compile Open vSwitch'] +} + +package { 'openvswitch-switch': + ensure => installed, + provider => dpkg, + source => '/home/vagrant/openvswitch-switch_2.1.2-1_amd64.deb', + require => Package['openvswitch-common'] +} + +package { 'openvswitch-datapath-dkms': + ensure => installed, + provider => dpkg, + source => '/home/vagrant/openvswitch-datapath-dkms_2.1.2-1_all.deb', + require => Package['openvswitch-switch'] +} + +package { 'openvswitch-pki': + ensure => installed, + provider => dpkg, + source => '/home/vagrant/openvswitch-pki_2.1.2-1_all.deb', + require => Package['openvswitch-datapath-dkms'] +} + +exec { 'Compile Test Controller': + command => 'sh boot.sh && sh configure && make', + cwd => '/home/vagrant/openvswitch-2.1.2', + path => $::path, + user => 'root', + require => [Exec['Compile Open vSwitch'], Package[$deps]] +} + +exec { 'Link Test Controller': + command => 'ln -s /home/vagrant/openvswitch-2.1.2/tests/test-controller /usr/bin/ovs-controller', + cwd => '/home/vagrant/openvswitch-2.1.2', + path => $::path, + user => 'root', + require => Exec['Compile Test Controller'] +} diff --git a/resources/puppet/manifests/scipass.pp b/resources/puppet/manifests/scipass.pp new file mode 100644 index 0000000..41793f0 --- /dev/null +++ b/resources/puppet/manifests/scipass.pp @@ -0,0 +1,123 @@ +$deps = [ 'build-essential', + 'debhelper', + 'dkms', + 'fakeroot', + 'graphviz', + 'linux-headers-generic', + 'python-all', + 'python-qt4', + 'python-zopeinterface', + 'python-twisted-conch', + 'python-twisted-web', + 'xauth' +] + +package { $deps: + ensure => installed, +} + +vcsrepo { '/home/vagrant/mininet': + ensure => present, + provider => git, + user => 'vagrant', + source => 'git://github.com/mininet/mininet', + revision => '2.1.0p2', + before => Exec['Install Mininet'] +} + +exec { 'Install Mininet': + command => 'bash mininet/util/install.sh -nf > /dev/null', + cwd => '/home/vagrant', + user => 'vagrant', + path => $::path, + timeout => 0 +} + +exec {'openvswitch-2.1.2.tar.gz': + command => 'wget http://openvswitch.org/releases/openvswitch-2.1.2.tar.gz', + cwd => '/home/vagrant', + path => $::path, + user => 'vagrant' +} + +exec { 'Extract Open vSwitch': + command => 'tar -xvf openvswitch-2.1.2.tar.gz', + cwd => '/home/vagrant', + user => 'vagrant', + path => $::path, + timeout => 0, + require => Exec['openvswitch-2.1.2.tar.gz'] +} + +exec { 'Compile Open vSwitch': + command => 'fakeroot debian/rules binary', + cwd => '/home/vagrant/openvswitch-2.1.2', + user => 'root', + path => $::path, + timeout => 0, + require => [Exec['Extract Open vSwitch'], Package[$deps]] +} + +package { 'openvswitch-common': + ensure => installed, + provider => dpkg, + source => '/home/vagrant/openvswitch-common_2.1.2-1_amd64.deb', + require => Exec['Compile Open vSwitch'] +} + +package { 'openvswitch-switch': + ensure => installed, + provider => dpkg, + source => '/home/vagrant/openvswitch-switch_2.1.2-1_amd64.deb', + require => Package['openvswitch-common'] +} + +package { 'openvswitch-datapath-dkms': + ensure => installed, + provider => dpkg, + source => '/home/vagrant/openvswitch-datapath-dkms_2.1.2-1_all.deb', + require => Package['openvswitch-switch'] +} + +package { 'openvswitch-pki': + ensure => installed, + provider => dpkg, + source => '/home/vagrant/openvswitch-pki_2.1.2-1_all.deb', + require => Package['openvswitch-datapath-dkms'] +} + +exec { 'Compile Test Controller': + command => 'sh boot.sh && sh configure && make', + cwd => '/home/vagrant/openvswitch-2.1.2', + path => $::path, + user => 'root', + require => [Exec['Compile Open vSwitch'], Package[$deps]] +} + +exec { 'Link Test Controller': + command => 'ln -s /home/vagrant/openvswitch-2.1.2/tests/test-controller /usr/bin/ovs-controller', + cwd => '/home/vagrant/openvswitch-2.1.2', + path => $::path, + user => 'root', + require => Exec['Compile Test Controller'] +} + + +exec {'ODL-2.0': + command => 'wget http://nexus.opendaylight.org/content/groups/public/org/opendaylight/integration/distribution-karaf/0.2.0-Helium/distribution-karaf-0.2.0-Helium.tar.gz', + cwd => '/home/vagrant', + path => $::path, + user => 'vagrant' +} + +exec { 'Extract ODL': + command => 'tar -xvf distribution-karaf-0.2.0-Helium.tar.gz', + cwd => '/home/vagrant', + user => 'vagrant', + path => $::path, + timeout => 0, + require => Exec['ODL-2.0'] +} + + + diff --git a/resources/puppet/scripts/bootstrap.sh b/resources/puppet/scripts/bootstrap.sh new file mode 100644 index 0000000..b20f50c --- /dev/null +++ b/resources/puppet/scripts/bootstrap.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# +# This bootstraps Puppet on Ubuntu 12.04 LTS. +# +set -e + +# Load up the release information +. /etc/lsb-release + +REPO_DEB_URL="http://apt.puppetlabs.com/puppetlabs-release-${DISTRIB_CODENAME}.deb" + +#-------------------------------------------------------------------- +# NO TUNABLES BELOW THIS POINT +#-------------------------------------------------------------------- +if [ "$(id -u)" != "0" ]; then + echo "This script must be run as root." >&2 + exit 1 +fi + +if which puppet > /dev/null 2>&1 -a apt-cache policy | grep --quiet apt.puppetlabs.com; then + echo "Puppet is already installed." + exit 0 +fi + +# Do the initial apt-get update +echo "Initial apt-get update..." +apt-get update >/dev/null + +# Install wget if we have to (some older Ubuntu versions) +echo "Installing wget..." +apt-get install -y wget >/dev/null + +# Install the PuppetLabs repo +echo "Configuring PuppetLabs repo..." +repo_deb_path=$(mktemp) +wget --output-document="${repo_deb_path}" "${REPO_DEB_URL}" 2>/dev/null +dpkg -i "${repo_deb_path}" >/dev/null +apt-get update >/dev/null + +# Install Puppet +echo "Installing Puppet..." +DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install puppet >/dev/null + +echo "Puppet installed!" + +# Install RubyGems for the provider +echo "Installing RubyGems..." +if [ $DISTRIB_CODENAME != "trusty" ]; then + apt-get install -y rubygems >/dev/null +fi +gem install --no-ri --no-rdoc rubygems-update +update_rubygems >/dev/null + +# Installing Puppet Modules +puppet module install puppetlabs/vcsrepo +puppet module install puppetlabs/stdlib diff --git a/resources/puppet/templates/compute.local.conf.erb b/resources/puppet/templates/compute.local.conf.erb new file mode 100644 index 0000000..693d416 --- /dev/null +++ b/resources/puppet/templates/compute.local.conf.erb @@ -0,0 +1,55 @@ +[[local|localrc]] +SCREEN_LOGDIR=/opt/stack/log +LOGFILE=stack.sh.log +LOG_COLOR=False +#OFFLINE=True +#RECLONE=yes + +HOST_IP=<%= @hosts[@hostname]['ipaddress'] %> +HOST_NAME=<%= @hosts[@hostname]['name'] %> +SERVICE_HOST=<%= @hosts['devstack-control']['name'] %> +SERVICE_HOST_NAME=<%= @hosts['devstack-control']['name'] %> + +Q_HOST=$SERVICE_HOST +MYSQL_HOST=$SERVICE_HOST +RABBIT_HOST=$SERVICE_HOST +GLANCE_HOSTPORT=$SERVICE_HOST:9292 +KEYSTONE_AUTH_HOST=$SERVICE_HOST +KEYSTONE_SERVICE_HOST=$SERVICE_HOST + +MYSQL_PASSWORD=mysql +RABBIT_PASSWORD=rabbit +QPID_PASSWORD=rabbit +SERVICE_TOKEN=service +SERVICE_PASSWORD=admin +ADMIN_PASSWORD=admin + +VNCSERVER_PROXYCLIENT_ADDRESS=$HOST_IP +VNCSERVER_LISTEN=0.0.0.0 + +disable_all_services +enable_service neutron quantum nova n-cpu n-novnc rabbit odl-compute + +# ODL WITH ML2 +Q_PLUGIN=ml2 +Q_ML2_PLUGIN_MECHANISM_DRIVERS=opendaylight,logger +ODL_MGR_IP=<%= @hosts['opendaylight']['ipaddress'] %> + +ENABLE_TENANT_TUNNELS=True +# Q_ML2_TENANT_NETWORK_TYPE=vlan +# ENABLE_TENANT_VLANS=True +Q_ML2_TENANT_NETWORK_TYPE=vxlan +#Q_AGENT_EXTRA_AGENT_OPTS=(tunnel_types=vxlan) + +#FLOATING_RANGE=192.168.254.64/26 + +EXTRA_OPTS=(scheduler_default_filters=AllHostsFilter) + +[[post-config|/etc/neutron/plugins/ml2/ml2_conf.ini]] +[agent] +minimize_polling=True + +[ml2_odl] +url=http://<%= @hosts['opendaylight']['ipaddress'] %>:8080/controller/nb/v2/neutron +username=admin +password=admin diff --git a/resources/puppet/templates/control.local.conf.erb b/resources/puppet/templates/control.local.conf.erb new file mode 100644 index 0000000..563d5a9 --- /dev/null +++ b/resources/puppet/templates/control.local.conf.erb @@ -0,0 +1,64 @@ +[[local|localrc]] +SCREEN_LOGDIR=/opt/stack/log +LOGFILE=stack.sh.log +LOG_COLOR=False +#OFFLINE=True +RECLONE=yes + +HOST_IP=<%= @hosts['devstack-control']['ipaddress'] %> +HOST_NAME=<%= @hosts['devstack-control']['name'] %> +SERVICE_HOST=$HOST_IP +SERVICE_HOST_NAME=$HOST_NAME + +Q_HOST=$SERVICE_HOST +MYSQL_HOST=$SERVICE_HOST +RABBIT_HOST=$SERVICE_HOST +GLANCE_HOSTPORT=$SERVICE_HOST:9292 +KEYSTONE_AUTH_HOST=$SERVICE_HOST +KEYSTONE_SERVICE_HOST=$SERVICE_HOST + +MYSQL_PASSWORD=mysql +RABBIT_PASSWORD=rabbit +QPID_PASSWORD=rabbit +SERVICE_TOKEN=service +SERVICE_PASSWORD=admin +ADMIN_PASSWORD=admin + +enable_service rabbit +disable_service qpid + +enable_service n-cond +enable_service n-cpu +enable_service n-novnc +disable_service n-net +enable_service q-svc +# enable_service q-agt +enable_service q-dhcp +enable_service q-l3 +enable_service q-meta +enable_service quantum +enable_service odl-compute + +# ODL WITH ML2 +Q_PLUGIN=ml2 +Q_ML2_PLUGIN_MECHANISM_DRIVERS=opendaylight,logger +ODL_MGR_IP=<%= @hosts['opendaylight']['ipaddress'] %> + +ENABLE_TENANT_TUNNELS=True +# ENABLE_TENANT_VLANS=True +# TENANT_VLAN_RANGE=500:510 + +Q_ML2_TENANT_NETWORK_TYPE=vxlan +# Q_AGENT_EXTRA_AGENT_OPTS=(tunnel_types=vxlan) + +#FLOATING_RANGE=192.168.254.64/26 +#PUBLIC_NETWORK_GATEWAY=192.168.75.254 + +[[post-config|/etc/neutron/plugins/ml2/ml2_conf.ini]] +[agent] +minimize_polling=True + +[ml2_odl] +url=http://<%= @hosts['opendaylight']['ipaddress'] %>:8080/controller/nb/v2/neutron +username=admin +password=admin diff --git a/resources/puppet/templates/hosts.erb b/resources/puppet/templates/hosts.erb new file mode 100644 index 0000000..c23f40f --- /dev/null +++ b/resources/puppet/templates/hosts.erb @@ -0,0 +1,8 @@ +## Do Not Edit. Created by Puppet ## +127.0.0.1 localhost +255.255.255.255 broadcasthost +::1 localhost +fe80::1%lo0 localhost +<% @hosts.values.each do |h| %> +<%= h["ipaddress"] %> <%= h["name"] %> +<% end %> diff --git a/resources/puppet/templates/ml2-compute.local.conf.erb b/resources/puppet/templates/ml2-compute.local.conf.erb new file mode 100644 index 0000000..7d93cf4 --- /dev/null +++ b/resources/puppet/templates/ml2-compute.local.conf.erb @@ -0,0 +1,103 @@ +[[local|localrc]] +SCREEN_LOGDIR=/opt/stack/log +LOGFILE=stack.sh.log +LOG_COLOR=False +#OFFLINE=True +#RECLONE=no +VERBOSE=True + +disable_all_services +# openvswitch +#enable_service neutron q-agt n-cpu qpid n-novnc + +#opendaylight +enable_service neutron q- n-cpu qpid n-novnc odl-compute + +HOST_IP=192.168.51.21 +HOST_NAME=devstack-odl-compute +SERVICE_HOST_NAME=devstack-odl +SERVICE_HOST=192.168.51.20 +Q_HOST=$SERVICE_HOST + +#Q_PLUGIN=openvswitch +#ENABLE_TENANT_VLANS=True +#TENANT_VLAN_RANGE=2000:2999 +#PHYSICAL_NETWORK=physnet1 +#OVS_PHYSICAL_BRIDGE=br-eth1 +## If using OVS_BRIDGE_MAPPINGS, you need to create the bridges manually. +##OVS_BRIDGE_MAPPINGS=physnet1:br-eth1 + +# openvswitch ml2 +#Q_PLUGIN=ml2 +#Q_ML2_PLUGIN_MECHANISM_DRIVERS=openvswitch,linuxbridge +#Q_ML2_PLUGIN_TYPE_DRIVERS=vlan,flat +###ML2_VLAN_RANGES=physnet1:2000:2999,physnet3:3000:3999 +#ML2_VLAN_RANGES=physnet1:2000:2999 +#ENABLE_TENANT_VLANS=True +#PHYSICAL_NETWORK=physnet1 +#OVS_PHYSICAL_BRIDGE=br-eth1 +## If using OVS_BRIDGE_MAPPINGS, you need to create the bridges manually. +##OVS_BRIDGE_MAPPINGS=physnet1:br-eth1,physnet3:br-eth3 +##OVS_BRIDGE_MAPPINGS=physnet1:br-eth1 + +# openvswitch ml2 vlan+tunnels +#Q_PLUGIN=ml2 +# all mechanism and type drivers are enabled by default +##Q_ML2_PLUGIN_MECHANISM_DRIVERS=openvswitch,linuxbridge +##Q_ML2_PLUGIN_TYPE_DRIVERS=flat,vlan,gre,vxlan +##ML2_VLAN_RANGES=physnet1:2000:2999,physnet3:3000:3999 +#ML2_VLAN_RANGES=physnet1:2000:2999 +#ENABLE_TENANT_VLANS=True +#ENABLE_TENANT_TUNNELS=True +#PHYSICAL_NETWORK=physnet1 +#OVS_PHYSICAL_BRIDGE=br-eth1 +## If using OVS_BRIDGE_MAPPINGS, you need to manually add the bridges. +##OVS_BRIDGE_MAPPINGS=physnet1:br-eth1,physnet3:br-eth3 +##OVS_BRIDGE_MAPPINGS=physnet1:br-eth1 + +# opendaylight ml2 +ODL_MGR_IP=192.168.51.1 +Q_PLUGIN=ml2 +#Q_ML2_PLUGIN_MECHANISM_DRIVERS=opendaylight +##Q_ML2_PLUGIN_TYPE_DRIVERS=flat,vlan,gre,vxlan +##ML2_VLAN_RANGES=physnet1:2000:2999,physnet3:3000-3999 +ENABLE_TENANT_VLANS=True +ENABLE_TENANT_TUNNELS=True +##Q_ML2_TENANT_NETWORK_TYPE=gre +####PHYSICAL_NETWORK=physnet1 +####PHYSICAL_NETWORK=default +####OVS_PHYSICAL_BRIDGE=br-eth1 +### If using OVS_BRIDGE_MAPPINGS, you need to create the bridges manually. +###OVS_BRIDGE_MAPPINGS=physnet1:eth1:physnet3:eth3 +ODL_PROVIDER_MAPPINGS=physnet1:eth1 +##NEUTRON_REPO=https://github.com/CiscoSystems/neutron.git +##NEUTRON_BRANCH=odl_ml2 + +VNCSERVER_PROXYCLIENT_ADDRESS=192.168.51.21 +VNCSERVER_LISTEN=0.0.0.0 + +#FLOATING_RANGE=192.168.122.0/28 +#PUBLIC_NETWORK_GATEWAY=192.168.122.1 +#Q_FLOATING_ALLOCATION_POOL=start=192.168.122.10,end=192.168.122.15 + +MYSQL_HOST=$SERVICE_HOST +RABBIT_HOST=$SERVICE_HOST +GLANCE_HOSTPORT=$SERVICE_HOST:9292 +KEYSTONE_AUTH_HOST=$SERVICE_HOST +KEYSTONE_SERVICE_HOST=$SERVICE_HOST + +MYSQL_PASSWORD=mysql +RABBIT_PASSWORD=rabbit +QPID_PASSWORD=rabbit +SERVICE_TOKEN=service +SERVICE_PASSWORD=admin +ADMIN_PASSWORD=admin + +[[post-config|/etc/neutron/plugins/ml2/ml2_conf.ini]] +[ml2_odl] +url=http://192.168.51.1:8080/controller/nb/v2/neutron +username=admin +password=admin + +[agent] +minimize_polling=True \ No newline at end of file diff --git a/resources/puppet/templates/ml2.local.conf.erb b/resources/puppet/templates/ml2.local.conf.erb new file mode 100644 index 0000000..5e66bbf --- /dev/null +++ b/resources/puppet/templates/ml2.local.conf.erb @@ -0,0 +1,58 @@ +[[local|localrc]] +SCREEN_LOGDIR=/opt/stack/log +LOGFILE=stack.sh.log +LOG_COLOR=False +#OFFLINE=True +RECLONE=yes + +HOST_IP=<%= @hosts['devstack-ml2']['ipaddress'] %> +HOST_NAME=<%= @hosts['devstack-ml2']['name'] %> +SERVICE_HOST=$HOST_IP +SERVICE_HOST_NAME=$HOST_NAME + +Q_HOST=$SERVICE_HOST +MYSQL_HOST=$SERVICE_HOST +RABBIT_HOST=$SERVICE_HOST +GLANCE_HOSTPORT=$SERVICE_HOST:9292 +KEYSTONE_AUTH_HOST=$SERVICE_HOST +KEYSTONE_SERVICE_HOST=$SERVICE_HOST + +MYSQL_PASSWORD=mysql +RABBIT_PASSWORD=rabbit +QPID_PASSWORD=rabbit +SERVICE_TOKEN=service +SERVICE_PASSWORD=admin +ADMIN_PASSWORD=admin + +enable_service rabbit +disable_service qpid + +enable_service n-cond +enable_service n-cpu +enable_service n-novnc +disable_service n-net +enable_service q-svc +# enable_service q-agt +enable_service q-dhcp +enable_service q-l3 +enable_service q-meta +enable_service quantum +enable_service q-lbaas + +# ODL WITH ML2 +Q_PLUGIN=ml2 +Q_ML2_PLUGIN_MECHANISM_DRIVERS=logger + +ENABLE_TENANT_TUNNELS=True +# ENABLE_TENANT_VLANS=True +# TENANT_VLAN_RANGE=500:510 + +Q_ML2_TENANT_NETWORK_TYPE=vxlan +# Q_AGENT_EXTRA_AGENT_OPTS=(tunnel_types=vxlan) + +#FLOATING_RANGE=192.168.254.64/26 +#PUBLIC_NETWORK_GATEWAY=192.168.75.254 + +[[post-config|/etc/neutron/plugins/ml2/ml2_conf.ini]] +[agent] +minimize_polling=True \ No newline at end of file From d4f9885fc5555fc7a78d9568e34ece60ebebfdcf Mon Sep 17 00:00:00 2001 From: Chris Small Date: Wed, 12 Nov 2014 16:43:49 -0800 Subject: [PATCH 02/81] Removed unused puppet templates --- .../puppet/templates/compute.local.conf.erb | 55 ---------- .../puppet/templates/control.local.conf.erb | 64 ----------- .../templates/ml2-compute.local.conf.erb | 103 ------------------ resources/puppet/templates/ml2.local.conf.erb | 58 ---------- 4 files changed, 280 deletions(-) delete mode 100644 resources/puppet/templates/compute.local.conf.erb delete mode 100644 resources/puppet/templates/control.local.conf.erb delete mode 100644 resources/puppet/templates/ml2-compute.local.conf.erb delete mode 100644 resources/puppet/templates/ml2.local.conf.erb diff --git a/resources/puppet/templates/compute.local.conf.erb b/resources/puppet/templates/compute.local.conf.erb deleted file mode 100644 index 693d416..0000000 --- a/resources/puppet/templates/compute.local.conf.erb +++ /dev/null @@ -1,55 +0,0 @@ -[[local|localrc]] -SCREEN_LOGDIR=/opt/stack/log -LOGFILE=stack.sh.log -LOG_COLOR=False -#OFFLINE=True -#RECLONE=yes - -HOST_IP=<%= @hosts[@hostname]['ipaddress'] %> -HOST_NAME=<%= @hosts[@hostname]['name'] %> -SERVICE_HOST=<%= @hosts['devstack-control']['name'] %> -SERVICE_HOST_NAME=<%= @hosts['devstack-control']['name'] %> - -Q_HOST=$SERVICE_HOST -MYSQL_HOST=$SERVICE_HOST -RABBIT_HOST=$SERVICE_HOST -GLANCE_HOSTPORT=$SERVICE_HOST:9292 -KEYSTONE_AUTH_HOST=$SERVICE_HOST -KEYSTONE_SERVICE_HOST=$SERVICE_HOST - -MYSQL_PASSWORD=mysql -RABBIT_PASSWORD=rabbit -QPID_PASSWORD=rabbit -SERVICE_TOKEN=service -SERVICE_PASSWORD=admin -ADMIN_PASSWORD=admin - -VNCSERVER_PROXYCLIENT_ADDRESS=$HOST_IP -VNCSERVER_LISTEN=0.0.0.0 - -disable_all_services -enable_service neutron quantum nova n-cpu n-novnc rabbit odl-compute - -# ODL WITH ML2 -Q_PLUGIN=ml2 -Q_ML2_PLUGIN_MECHANISM_DRIVERS=opendaylight,logger -ODL_MGR_IP=<%= @hosts['opendaylight']['ipaddress'] %> - -ENABLE_TENANT_TUNNELS=True -# Q_ML2_TENANT_NETWORK_TYPE=vlan -# ENABLE_TENANT_VLANS=True -Q_ML2_TENANT_NETWORK_TYPE=vxlan -#Q_AGENT_EXTRA_AGENT_OPTS=(tunnel_types=vxlan) - -#FLOATING_RANGE=192.168.254.64/26 - -EXTRA_OPTS=(scheduler_default_filters=AllHostsFilter) - -[[post-config|/etc/neutron/plugins/ml2/ml2_conf.ini]] -[agent] -minimize_polling=True - -[ml2_odl] -url=http://<%= @hosts['opendaylight']['ipaddress'] %>:8080/controller/nb/v2/neutron -username=admin -password=admin diff --git a/resources/puppet/templates/control.local.conf.erb b/resources/puppet/templates/control.local.conf.erb deleted file mode 100644 index 563d5a9..0000000 --- a/resources/puppet/templates/control.local.conf.erb +++ /dev/null @@ -1,64 +0,0 @@ -[[local|localrc]] -SCREEN_LOGDIR=/opt/stack/log -LOGFILE=stack.sh.log -LOG_COLOR=False -#OFFLINE=True -RECLONE=yes - -HOST_IP=<%= @hosts['devstack-control']['ipaddress'] %> -HOST_NAME=<%= @hosts['devstack-control']['name'] %> -SERVICE_HOST=$HOST_IP -SERVICE_HOST_NAME=$HOST_NAME - -Q_HOST=$SERVICE_HOST -MYSQL_HOST=$SERVICE_HOST -RABBIT_HOST=$SERVICE_HOST -GLANCE_HOSTPORT=$SERVICE_HOST:9292 -KEYSTONE_AUTH_HOST=$SERVICE_HOST -KEYSTONE_SERVICE_HOST=$SERVICE_HOST - -MYSQL_PASSWORD=mysql -RABBIT_PASSWORD=rabbit -QPID_PASSWORD=rabbit -SERVICE_TOKEN=service -SERVICE_PASSWORD=admin -ADMIN_PASSWORD=admin - -enable_service rabbit -disable_service qpid - -enable_service n-cond -enable_service n-cpu -enable_service n-novnc -disable_service n-net -enable_service q-svc -# enable_service q-agt -enable_service q-dhcp -enable_service q-l3 -enable_service q-meta -enable_service quantum -enable_service odl-compute - -# ODL WITH ML2 -Q_PLUGIN=ml2 -Q_ML2_PLUGIN_MECHANISM_DRIVERS=opendaylight,logger -ODL_MGR_IP=<%= @hosts['opendaylight']['ipaddress'] %> - -ENABLE_TENANT_TUNNELS=True -# ENABLE_TENANT_VLANS=True -# TENANT_VLAN_RANGE=500:510 - -Q_ML2_TENANT_NETWORK_TYPE=vxlan -# Q_AGENT_EXTRA_AGENT_OPTS=(tunnel_types=vxlan) - -#FLOATING_RANGE=192.168.254.64/26 -#PUBLIC_NETWORK_GATEWAY=192.168.75.254 - -[[post-config|/etc/neutron/plugins/ml2/ml2_conf.ini]] -[agent] -minimize_polling=True - -[ml2_odl] -url=http://<%= @hosts['opendaylight']['ipaddress'] %>:8080/controller/nb/v2/neutron -username=admin -password=admin diff --git a/resources/puppet/templates/ml2-compute.local.conf.erb b/resources/puppet/templates/ml2-compute.local.conf.erb deleted file mode 100644 index 7d93cf4..0000000 --- a/resources/puppet/templates/ml2-compute.local.conf.erb +++ /dev/null @@ -1,103 +0,0 @@ -[[local|localrc]] -SCREEN_LOGDIR=/opt/stack/log -LOGFILE=stack.sh.log -LOG_COLOR=False -#OFFLINE=True -#RECLONE=no -VERBOSE=True - -disable_all_services -# openvswitch -#enable_service neutron q-agt n-cpu qpid n-novnc - -#opendaylight -enable_service neutron q- n-cpu qpid n-novnc odl-compute - -HOST_IP=192.168.51.21 -HOST_NAME=devstack-odl-compute -SERVICE_HOST_NAME=devstack-odl -SERVICE_HOST=192.168.51.20 -Q_HOST=$SERVICE_HOST - -#Q_PLUGIN=openvswitch -#ENABLE_TENANT_VLANS=True -#TENANT_VLAN_RANGE=2000:2999 -#PHYSICAL_NETWORK=physnet1 -#OVS_PHYSICAL_BRIDGE=br-eth1 -## If using OVS_BRIDGE_MAPPINGS, you need to create the bridges manually. -##OVS_BRIDGE_MAPPINGS=physnet1:br-eth1 - -# openvswitch ml2 -#Q_PLUGIN=ml2 -#Q_ML2_PLUGIN_MECHANISM_DRIVERS=openvswitch,linuxbridge -#Q_ML2_PLUGIN_TYPE_DRIVERS=vlan,flat -###ML2_VLAN_RANGES=physnet1:2000:2999,physnet3:3000:3999 -#ML2_VLAN_RANGES=physnet1:2000:2999 -#ENABLE_TENANT_VLANS=True -#PHYSICAL_NETWORK=physnet1 -#OVS_PHYSICAL_BRIDGE=br-eth1 -## If using OVS_BRIDGE_MAPPINGS, you need to create the bridges manually. -##OVS_BRIDGE_MAPPINGS=physnet1:br-eth1,physnet3:br-eth3 -##OVS_BRIDGE_MAPPINGS=physnet1:br-eth1 - -# openvswitch ml2 vlan+tunnels -#Q_PLUGIN=ml2 -# all mechanism and type drivers are enabled by default -##Q_ML2_PLUGIN_MECHANISM_DRIVERS=openvswitch,linuxbridge -##Q_ML2_PLUGIN_TYPE_DRIVERS=flat,vlan,gre,vxlan -##ML2_VLAN_RANGES=physnet1:2000:2999,physnet3:3000:3999 -#ML2_VLAN_RANGES=physnet1:2000:2999 -#ENABLE_TENANT_VLANS=True -#ENABLE_TENANT_TUNNELS=True -#PHYSICAL_NETWORK=physnet1 -#OVS_PHYSICAL_BRIDGE=br-eth1 -## If using OVS_BRIDGE_MAPPINGS, you need to manually add the bridges. -##OVS_BRIDGE_MAPPINGS=physnet1:br-eth1,physnet3:br-eth3 -##OVS_BRIDGE_MAPPINGS=physnet1:br-eth1 - -# opendaylight ml2 -ODL_MGR_IP=192.168.51.1 -Q_PLUGIN=ml2 -#Q_ML2_PLUGIN_MECHANISM_DRIVERS=opendaylight -##Q_ML2_PLUGIN_TYPE_DRIVERS=flat,vlan,gre,vxlan -##ML2_VLAN_RANGES=physnet1:2000:2999,physnet3:3000-3999 -ENABLE_TENANT_VLANS=True -ENABLE_TENANT_TUNNELS=True -##Q_ML2_TENANT_NETWORK_TYPE=gre -####PHYSICAL_NETWORK=physnet1 -####PHYSICAL_NETWORK=default -####OVS_PHYSICAL_BRIDGE=br-eth1 -### If using OVS_BRIDGE_MAPPINGS, you need to create the bridges manually. -###OVS_BRIDGE_MAPPINGS=physnet1:eth1:physnet3:eth3 -ODL_PROVIDER_MAPPINGS=physnet1:eth1 -##NEUTRON_REPO=https://github.com/CiscoSystems/neutron.git -##NEUTRON_BRANCH=odl_ml2 - -VNCSERVER_PROXYCLIENT_ADDRESS=192.168.51.21 -VNCSERVER_LISTEN=0.0.0.0 - -#FLOATING_RANGE=192.168.122.0/28 -#PUBLIC_NETWORK_GATEWAY=192.168.122.1 -#Q_FLOATING_ALLOCATION_POOL=start=192.168.122.10,end=192.168.122.15 - -MYSQL_HOST=$SERVICE_HOST -RABBIT_HOST=$SERVICE_HOST -GLANCE_HOSTPORT=$SERVICE_HOST:9292 -KEYSTONE_AUTH_HOST=$SERVICE_HOST -KEYSTONE_SERVICE_HOST=$SERVICE_HOST - -MYSQL_PASSWORD=mysql -RABBIT_PASSWORD=rabbit -QPID_PASSWORD=rabbit -SERVICE_TOKEN=service -SERVICE_PASSWORD=admin -ADMIN_PASSWORD=admin - -[[post-config|/etc/neutron/plugins/ml2/ml2_conf.ini]] -[ml2_odl] -url=http://192.168.51.1:8080/controller/nb/v2/neutron -username=admin -password=admin - -[agent] -minimize_polling=True \ No newline at end of file diff --git a/resources/puppet/templates/ml2.local.conf.erb b/resources/puppet/templates/ml2.local.conf.erb deleted file mode 100644 index 5e66bbf..0000000 --- a/resources/puppet/templates/ml2.local.conf.erb +++ /dev/null @@ -1,58 +0,0 @@ -[[local|localrc]] -SCREEN_LOGDIR=/opt/stack/log -LOGFILE=stack.sh.log -LOG_COLOR=False -#OFFLINE=True -RECLONE=yes - -HOST_IP=<%= @hosts['devstack-ml2']['ipaddress'] %> -HOST_NAME=<%= @hosts['devstack-ml2']['name'] %> -SERVICE_HOST=$HOST_IP -SERVICE_HOST_NAME=$HOST_NAME - -Q_HOST=$SERVICE_HOST -MYSQL_HOST=$SERVICE_HOST -RABBIT_HOST=$SERVICE_HOST -GLANCE_HOSTPORT=$SERVICE_HOST:9292 -KEYSTONE_AUTH_HOST=$SERVICE_HOST -KEYSTONE_SERVICE_HOST=$SERVICE_HOST - -MYSQL_PASSWORD=mysql -RABBIT_PASSWORD=rabbit -QPID_PASSWORD=rabbit -SERVICE_TOKEN=service -SERVICE_PASSWORD=admin -ADMIN_PASSWORD=admin - -enable_service rabbit -disable_service qpid - -enable_service n-cond -enable_service n-cpu -enable_service n-novnc -disable_service n-net -enable_service q-svc -# enable_service q-agt -enable_service q-dhcp -enable_service q-l3 -enable_service q-meta -enable_service quantum -enable_service q-lbaas - -# ODL WITH ML2 -Q_PLUGIN=ml2 -Q_ML2_PLUGIN_MECHANISM_DRIVERS=logger - -ENABLE_TENANT_TUNNELS=True -# ENABLE_TENANT_VLANS=True -# TENANT_VLAN_RANGE=500:510 - -Q_ML2_TENANT_NETWORK_TYPE=vxlan -# Q_AGENT_EXTRA_AGENT_OPTS=(tunnel_types=vxlan) - -#FLOATING_RANGE=192.168.254.64/26 -#PUBLIC_NETWORK_GATEWAY=192.168.75.254 - -[[post-config|/etc/neutron/plugins/ml2/ml2_conf.ini]] -[agent] -minimize_polling=True \ No newline at end of file From 081613e3027da668c162893e1ba3944289f871eb Mon Sep 17 00:00:00 2001 From: Chris Small Date: Wed, 12 Nov 2014 17:25:11 -0800 Subject: [PATCH 03/81] Removed OVS compile since it is currently broken in OVS --- .gitignore | 3 +- README.md | 30 ++++++++++- resources/puppet/manifests/scipass.pp | 73 +-------------------------- 3 files changed, 33 insertions(+), 73 deletions(-) diff --git a/.gitignore b/.gitignore index 7e99e36..5ff7b36 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -*.pyc \ No newline at end of file +*.pyc +.vagrant/* diff --git a/README.md b/README.md index d7a9aa8..576ac04 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,32 @@ SciPass ======= -SciPass is a SDN powered Science DMZ and IDS Load Balancer. \ No newline at end of file +SciPass is a SDN powered Science DMZ and IDS Load Balancer. + + +VM Environment with OpenDayLight +======= + +The Vagrantfile provides a quick and easy way to spin up a VM containing all the code to run +SciPass and a Mininet based test environment to test functionality. + + + +To create the SciPass VM: + +First make sure Vagrant https://www.vagrantup.com/ is installed for your OS. You will need Vagrant 1.5 to use the Vagrant Cloud boxes. You should also have Hypervisor software installed. this has been tested agqinst VirtualBox but VMWare Workstation/Fusion should work as well. + +To create the VM with SciPass and ODL installed + + vagrant up + vagrant ssh scipass + +To start up the OpenDaylight Controller + + cd distribution-karaf-0.2.0-Helium + bin/karaf + +To create a 2-node Mininet network + + cd mininet + mn --controller=remote,ip=localhost diff --git a/resources/puppet/manifests/scipass.pp b/resources/puppet/manifests/scipass.pp index 41793f0..e3371e5 100644 --- a/resources/puppet/manifests/scipass.pp +++ b/resources/puppet/manifests/scipass.pp @@ -9,7 +9,8 @@ 'python-zopeinterface', 'python-twisted-conch', 'python-twisted-web', - 'xauth' + 'xauth', + 'openvswitch-switch' ] package { $deps: @@ -33,76 +34,6 @@ timeout => 0 } -exec {'openvswitch-2.1.2.tar.gz': - command => 'wget http://openvswitch.org/releases/openvswitch-2.1.2.tar.gz', - cwd => '/home/vagrant', - path => $::path, - user => 'vagrant' -} - -exec { 'Extract Open vSwitch': - command => 'tar -xvf openvswitch-2.1.2.tar.gz', - cwd => '/home/vagrant', - user => 'vagrant', - path => $::path, - timeout => 0, - require => Exec['openvswitch-2.1.2.tar.gz'] -} - -exec { 'Compile Open vSwitch': - command => 'fakeroot debian/rules binary', - cwd => '/home/vagrant/openvswitch-2.1.2', - user => 'root', - path => $::path, - timeout => 0, - require => [Exec['Extract Open vSwitch'], Package[$deps]] -} - -package { 'openvswitch-common': - ensure => installed, - provider => dpkg, - source => '/home/vagrant/openvswitch-common_2.1.2-1_amd64.deb', - require => Exec['Compile Open vSwitch'] -} - -package { 'openvswitch-switch': - ensure => installed, - provider => dpkg, - source => '/home/vagrant/openvswitch-switch_2.1.2-1_amd64.deb', - require => Package['openvswitch-common'] -} - -package { 'openvswitch-datapath-dkms': - ensure => installed, - provider => dpkg, - source => '/home/vagrant/openvswitch-datapath-dkms_2.1.2-1_all.deb', - require => Package['openvswitch-switch'] -} - -package { 'openvswitch-pki': - ensure => installed, - provider => dpkg, - source => '/home/vagrant/openvswitch-pki_2.1.2-1_all.deb', - require => Package['openvswitch-datapath-dkms'] -} - -exec { 'Compile Test Controller': - command => 'sh boot.sh && sh configure && make', - cwd => '/home/vagrant/openvswitch-2.1.2', - path => $::path, - user => 'root', - require => [Exec['Compile Open vSwitch'], Package[$deps]] -} - -exec { 'Link Test Controller': - command => 'ln -s /home/vagrant/openvswitch-2.1.2/tests/test-controller /usr/bin/ovs-controller', - cwd => '/home/vagrant/openvswitch-2.1.2', - path => $::path, - user => 'root', - require => Exec['Compile Test Controller'] -} - - exec {'ODL-2.0': command => 'wget http://nexus.opendaylight.org/content/groups/public/org/opendaylight/integration/distribution-karaf/0.2.0-Helium/distribution-karaf-0.2.0-Helium.tar.gz', cwd => '/home/vagrant', From 174735c74ee68186a9a8a9b2c6ddf76ddeccd320 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Wed, 12 Nov 2014 22:38:05 -0800 Subject: [PATCH 04/81] Added SciPass git and ryu dependancies --- resources/puppet/manifests/scipass.pp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/resources/puppet/manifests/scipass.pp b/resources/puppet/manifests/scipass.pp index e3371e5..fe45471 100644 --- a/resources/puppet/manifests/scipass.pp +++ b/resources/puppet/manifests/scipass.pp @@ -10,7 +10,18 @@ 'python-twisted-conch', 'python-twisted-web', 'xauth', - 'openvswitch-switch' + 'openvswitch-switch', + 'python-coverage', + 'python-mock', + 'python-ipaddr', + 'python-libxml2', + 'python-lxml', + 'python-webob', + 'python-routes', + 'python-parimiko', + 'python-oslo.config', + 'python-netaddr', + 'msgpack-python' ] package { $deps: @@ -50,5 +61,10 @@ require => Exec['ODL-2.0'] } - - +vcsrepo { '/home/vagrant/scinet': + ensure => present, + provider => git, + user => 'vagrant', + source => 'git://github.com/chrissmall22/SciPass', + revision => 'odl' +} From d90a3e6751b54b2ba2da1ce5bce7866ce6b68174 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 13 Nov 2014 07:30:53 +0000 Subject: [PATCH 05/81] Added Mininet SciPass XML config file --- python/t/etc/SciPass-mininet.py | 31 +++++++++++++++++++++++++++++++ python/t/etc/SciPass-mininet.xml | 31 +++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 python/t/etc/SciPass-mininet.py create mode 100644 python/t/etc/SciPass-mininet.xml diff --git a/python/t/etc/SciPass-mininet.py b/python/t/etc/SciPass-mininet.py new file mode 100644 index 0000000..c09f993 --- /dev/null +++ b/python/t/etc/SciPass-mininet.py @@ -0,0 +1,31 @@ + + + + + + + 10.0.17.0/24 + 10.0.18.0/24 + + + + 10.0.19.0/24 + 10.0.20.0/24 + + + + + + + + + + + + + + + + diff --git a/python/t/etc/SciPass-mininet.xml b/python/t/etc/SciPass-mininet.xml new file mode 100644 index 0000000..c09f993 --- /dev/null +++ b/python/t/etc/SciPass-mininet.xml @@ -0,0 +1,31 @@ + + + + + + + 10.0.17.0/24 + 10.0.18.0/24 + + + + 10.0.19.0/24 + 10.0.20.0/24 + + + + + + + + + + + + + + + + From 124cb5c540b6785c27a9de19f508e589ed58a34d Mon Sep 17 00:00:00 2001 From: Chris Small Date: Wed, 12 Nov 2014 23:25:50 -0800 Subject: [PATCH 06/81] Added postman API ref --- .../SciPass-API.json.postman_collection | 269 ++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 resources/postman/SciPass-API.json.postman_collection diff --git a/resources/postman/SciPass-API.json.postman_collection b/resources/postman/SciPass-API.json.postman_collection new file mode 100644 index 0000000..5d72b77 --- /dev/null +++ b/resources/postman/SciPass-API.json.postman_collection @@ -0,0 +1,269 @@ +{ + "id": "35a3cdcb-55df-9be0-47d1-58a7fc4d0ce4", + "name": "SciPass API", + "description": "SDN powered Science DMZ and IDS Load Balancer \n\nhttp://globalnoc.iu.edu/sdn/scipass.html", + "order": [ + "4a642c67-78c5-7206-fff2-387c1259b279", + "cecfceb5-bf3e-e220-c372-eb3e8afccf13", + "516fb4bc-b5f0-b254-108e-d987e1c423ea", + "0723aabb-8fd8-9872-894d-8bae55366a68", + "f29ece83-d848-24dc-37aa-47b1509636d3", + "76b13b3a-a812-bd55-7f11-113b07c9b6f7", + "fd0990a4-90b1-1265-239d-0e9e65cc2a5f", + "84b5ad94-f614-f9f9-0af5-5f88ed96ba78", + "b44b355d-3582-6323-542e-c312ff4a776d", + "0b8f1794-fe4d-bcbf-c446-fd26ac1f1754" + ], + "folders": [], + "timestamp": 1415861202181, + "synced": false, + "requests": [ + { + "id": "0723aabb-8fd8-9872-894d-8bae55366a68", + "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nAccept: application/json\nContent-Type: application/json\n", + "url": "http://{{controllerHost}}:{{controllerPort}}/scipass/flows/good_flow", + "preRequestScript": "", + "pathVariables": {}, + "method": "PUT", + "data": [], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415862011803, + "name": "Put Good Flow", + "description": "", + "collectionId": "35a3cdcb-55df-9be0-47d1-58a7fc4d0ce4", + "responses": [], + "synced": false + }, + { + "id": "0b8f1794-fe4d-bcbf-c446-fd26ac1f1754", + "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nAccept: application/json\nContent-Type: application/json\n", + "url": "http://{{controllerHost}}:{{controllerPort}}/scipass/switch/0000000000000001/domain/R&E/flows", + "preRequestScript": "", + "pathVariables": {}, + "method": "GET", + "data": [ + { + "key": "sensor_id", + "value": "1", + "type": "text", + "enabled": true + }, + { + "key": "load", + "value": "1", + "type": "text", + "enabled": true + } + ], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415863338608, + "name": "Get Domain R&E Flows", + "description": "", + "collectionId": "35a3cdcb-55df-9be0-47d1-58a7fc4d0ce4", + "responses": [], + "synced": false + }, + { + "id": "4a642c67-78c5-7206-fff2-387c1259b279", + "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nAccept: application/json\nContent-Type: application/json\n", + "url": "http://{{controllerHost}}:{{controllerPort}}/scipass/flows/get_good_flows", + "preRequestScript": "", + "pathVariables": {}, + "method": "GET", + "data": [], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415862080367, + "name": "Get Good Flows", + "description": "", + "collectionId": "35a3cdcb-55df-9be0-47d1-58a7fc4d0ce4", + "responses": [], + "synced": false + }, + { + "id": "516fb4bc-b5f0-b254-108e-d987e1c423ea", + "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nAccept: application/json\nContent-Type: application/json\n", + "url": "http://{{controllerHost}}:{{controllerPort}}/scipass/switch/0000000000000001/flows", + "preRequestScript": "", + "pathVariables": {}, + "method": "GET", + "data": [], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415861820792, + "name": "Get Flows from DPID 1", + "description": "", + "collectionId": "35a3cdcb-55df-9be0-47d1-58a7fc4d0ce4", + "responses": [], + "synced": false + }, + { + "id": "76b13b3a-a812-bd55-7f11-113b07c9b6f7", + "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nAccept: application/json\nContent-Type: application/json\n", + "url": "http://{{controllerHost}}:{{controllerPort}}/scipass/flows/good_flow", + "preRequestScript": "", + "pathVariables": {}, + "method": "PUT", + "data": [ + { + "key": "sensor_id", + "value": "1", + "type": "text", + "enabled": true + }, + { + "key": "load", + "value": "1", + "type": "text", + "enabled": true + } + ], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415862602431, + "name": "Put Sensor 1 Load 1", + "description": "", + "collectionId": "35a3cdcb-55df-9be0-47d1-58a7fc4d0ce4", + "responses": [], + "synced": false + }, + { + "id": "84b5ad94-f614-f9f9-0af5-5f88ed96ba78", + "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nAccept: application/json\nContent-Type: application/json\n", + "url": "http://{{controllerHost}}:{{controllerPort}}/scipass/switches", + "pathVariables": {}, + "preRequestScript": "", + "method": "GET", + "data": [ + { + "key": "sensor_id", + "value": "1", + "type": "text", + "enabled": true + }, + { + "key": "load", + "value": "1", + "type": "text", + "enabled": true + } + ], + "dataMode": "params", + "name": "Get Domains", + "description": "", + "descriptionFormat": "html", + "time": 1415863150714, + "version": 2, + "responses": [], + "tests": "", + "collectionId": "35a3cdcb-55df-9be0-47d1-58a7fc4d0ce4", + "synced": false + }, + { + "id": "b44b355d-3582-6323-542e-c312ff4a776d", + "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nAccept: application/json\nContent-Type: application/json\n", + "url": "http://{{controllerHost}}:{{controllerPort}}/scipass/switch/0000000000000001/domain/R&E", + "preRequestScript": "", + "pathVariables": {}, + "method": "GET", + "data": [ + { + "key": "sensor_id", + "value": "1", + "type": "text", + "enabled": true + }, + { + "key": "load", + "value": "1", + "type": "text", + "enabled": true + } + ], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415863274971, + "name": "Get Domain R&E Sensors", + "description": "", + "collectionId": "35a3cdcb-55df-9be0-47d1-58a7fc4d0ce4", + "responses": [], + "synced": false + }, + { + "id": "cecfceb5-bf3e-e220-c372-eb3e8afccf13", + "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nAccept: application/json\nContent-Type: application/json\n", + "url": "http://{{controllerHost}}:{{controllerPort}}/scipass/flows/get_bad_flows", + "preRequestScript": "", + "pathVariables": {}, + "method": "GET", + "data": [], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415861635872, + "name": "Get Bad Flows", + "description": "", + "collectionId": "35a3cdcb-55df-9be0-47d1-58a7fc4d0ce4", + "responses": [], + "synced": false + }, + { + "id": "f29ece83-d848-24dc-37aa-47b1509636d3", + "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nAccept: application/json\nContent-Type: application/json\n", + "url": "http://{{controllerHost}}:{{controllerPort}}/scipass/flows/good_flow", + "pathVariables": {}, + "preRequestScript": "", + "method": "PUT", + "data": [], + "dataMode": "params", + "name": "Put Bad Flow", + "description": "", + "descriptionFormat": "html", + "time": 1415862418371, + "version": 2, + "responses": [], + "tests": "", + "collectionId": "35a3cdcb-55df-9be0-47d1-58a7fc4d0ce4", + "synced": false + }, + { + "id": "fd0990a4-90b1-1265-239d-0e9e65cc2a5f", + "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nAccept: application/json\nContent-Type: application/json\n", + "url": "http://{{controllerHost}}:{{controllerPort}}/scipass/switches", + "preRequestScript": "", + "pathVariables": {}, + "method": "GET", + "data": [ + { + "key": "sensor_id", + "value": "1", + "type": "text", + "enabled": true + }, + { + "key": "load", + "value": "1", + "type": "text", + "enabled": true + } + ], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415862643099, + "name": "Get Switches", + "description": "", + "collectionId": "35a3cdcb-55df-9be0-47d1-58a7fc4d0ce4", + "responses": [], + "synced": false + } + ] +} \ No newline at end of file From 33a514ce439101eaaf17e81d6b3d0ad4882db21b Mon Sep 17 00:00:00 2001 From: Chris Small Date: Wed, 12 Nov 2014 23:44:36 -0800 Subject: [PATCH 07/81] Added Postman and Ryu startup info to Readme --- README.md | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 576ac04..261e8dc 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,29 @@ To create the VM with SciPass and ODL installed vagrant up vagrant ssh scipass -To start up the OpenDaylight Controller +To start the Ryu version of SciPass + + cd scipass/python + sudo mkdir /etc/SciPass + sudo cp t/etc/SciPass-mininet.xml /etc/SciPass + ryu-manager Ryu.py - cd distribution-karaf-0.2.0-Helium - bin/karaf -To create a 2-node Mininet network +To create a Mininet network to match SciPass-mininet.xml cd mininet mn --controller=remote,ip=localhost + +Postman +======= +Install Postman for Chrome http://www.getpostman.com/ + +Add repository at scipass/resources/postman/SciPass-API.json.postman_collection + +OpenDayLight +=========== + +To start up the OpenDaylight Controller + + cd distribution-karaf-0.2.0-Helium + bin/karaf From 722e5281b8b21447c525adee20c3009927f2be5e Mon Sep 17 00:00:00 2001 From: Chris Small Date: Wed, 12 Nov 2014 23:53:13 -0800 Subject: [PATCH 08/81] Fixed mininet config in Readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 261e8dc..6ee44ee 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ To start the Ryu version of SciPass To create a Mininet network to match SciPass-mininet.xml cd mininet - mn --controller=remote,ip=localhost + sudo mn --topo single,7 --mac --switch ovsk --controller remote Postman ======= @@ -40,6 +40,7 @@ Install Postman for Chrome http://www.getpostman.com/ Add repository at scipass/resources/postman/SciPass-API.json.postman_collection + OpenDayLight =========== From 3feca3691b8c2634d3426285a47c1e22d5836f38 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 13 Nov 2014 11:11:34 -0800 Subject: [PATCH 09/81] Added ryu compile from source to puppet manifests --- resources/puppet/manifests/scipass.pp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/resources/puppet/manifests/scipass.pp b/resources/puppet/manifests/scipass.pp index fe45471..fd05743 100644 --- a/resources/puppet/manifests/scipass.pp +++ b/resources/puppet/manifests/scipass.pp @@ -18,10 +18,13 @@ 'python-lxml', 'python-webob', 'python-routes', - 'python-parimiko', + 'python-paramiko', 'python-oslo.config', 'python-netaddr', - 'msgpack-python' + 'msgpack-python', + 'python-greenlet', + 'python-pip', + 'python-dev' ] package { $deps: @@ -68,3 +71,19 @@ source => 'git://github.com/chrissmall22/SciPass', revision => 'odl' } + +vcsrepo { '/home/vagrant/ryu': + ensure => present, + provider => git, + user => 'vagrant', + source => 'https://github.com/osrg/ryu', + before => Exec['Install Ryu'] +} + +exec { 'Install Ryu': + command => 'python ./setup.py install', + cwd => '/home/vagrant/ryu', + user => 'vagrant', + path => $::path, + timeout => 0 +} \ No newline at end of file From 837f6d2b71435ac7492ef1353270718b3119d537 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 13 Nov 2014 19:30:49 +0000 Subject: [PATCH 10/81] Created tests based on Mininet ids and interfaces --- Makefile | 5 +- python/t/SciPassTest_mininet.py | 254 +++++++++++++++++++++++++++++++ python/t/etc/SciPass-mininet.py | 31 ---- python/t/etc/SciPass-mininet.xml | 11 +- 4 files changed, 265 insertions(+), 36 deletions(-) create mode 100644 python/t/SciPassTest_mininet.py delete mode 100644 python/t/etc/SciPass-mininet.py diff --git a/Makefile b/Makefile index bdd3329..6427f91 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,11 @@ clean: test: cd python; coverage run --source=./ --omit=__init__.py,*Test.py,t/SciPass.py t/SciPassTest.py; coverage report -m; coverage xml;coverage annotate; coverage html; +test_mininet: + cd python; python -m coverage run --source=./ --omit=__init__.py,*Test.py,t/SciPass.py t/SciPassTest_mininet.py; python -m coverage report -m; python -m coverage xml;python -m coverage annotate; python -m coverage html; + dist: rm -rf dist/$(NAME)-$(VERSION) mkdir -p dist/$(NAME)-$(VERSION) cp -r etc/ python/ SciPass.spec dist/$(NAME)-$(VERSION)/ - cd dist; tar -czvf $(NAME)-$(VERSION).tar.gz $(NAME)-$(VERSION)/ --exclude .svn \ No newline at end of file + cd dist; tar -czvf $(NAME)-$(VERSION).tar.gz $(NAME)-$(VERSION)/ --exclude .svn diff --git a/python/t/SciPassTest_mininet.py b/python/t/SciPassTest_mininet.py new file mode 100644 index 0000000..091ab17 --- /dev/null +++ b/python/t/SciPassTest_mininet.py @@ -0,0 +1,254 @@ +# Version of the SciPass tests that intergrates a mininet instance +# Provides an easy way to test without a physical switch and/or with more complex topologies + + +import sys +sys.path.append(".") +import pprint +import unittest +from mock import Mock +import logging +import ipaddr +import os +from SciPass import SciPass +import libxml2 + +logging.basicConfig() + + +class TestInit(unittest.TestCase): + + def test_valid_config(self): + api = SciPass( logger = logging.getLogger(__name__), + config = str(os.getcwd()) + "/t/etc/SciPass_mininet.xml" + ) + self.assertTrue(isinstance(api,SciPass)) + + +# def test_no_config(self): +# self.assertRaises(libxml2.parserError,SciPass) +# +# api = SciPass( logger = logging.getLogger(__name__), +# config = str(os.getcwd()) + "/t/etc/no_config.xml" ) + + +# def test_invalid_config(self): +# self.assertRaises(libxml2.parserError,SciPass) +# +# api = SciPass( logger = logging.getLogger(__name__), +# config = str(os.getcwd()) + "/t/etc/InvalidConfig.xml" ) + + def test_switch_init(self): + api = SciPass( logger = logging.getLogger(__name__), + config = str(os.getcwd()) + "/t/etc/SciPass_mininet.xml" ) + + #first setup the handler to get all the flows that were sent + flows = [] + def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): + flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) + + api.registerForwardingStateChangeHandler(flowSent) + datapath = Mock(id=1) + api.switchJoined(datapath) + + self.assertTrue( len(flows) == 33) + #verify all of the 'flow details are set properly' + for flow in flows: + self.assertEquals(flow['dpid'], "%016x" % datapath.id) + self.assertEquals(flow['hard_timeout'], 0) + self.assertEquals(flow['idle_timeout'], 0) + + flow = flows[0] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 1, 'dl_type': None}) + self.assertEquals(flow['priority'], 5) + flow = flows[1] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167776512, 'nw_dst_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[2] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':1, 'nw_src': 167776512, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[3] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167776768, 'nw_dst_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[4] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':1, 'nw_src': 167776768, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[5] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 2, 'dl_type': None}) + self.assertEquals(flow['priority'], 5) + flow = flows[6] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 2}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167777024, 'nw_dst_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[7] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':2, 'nw_src': 167777024, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[8] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 2}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167777280, 'nw_dst_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[9] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':2, 'nw_src': 167777280, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[10] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}, {'type': 'output', 'port': 2}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 5, 'dl_type': None}) + self.assertEquals(flow['priority'], 3) + flow = flows[11] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 10}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 6, 'dl_type': None}) + self.assertEquals(flow['priority'], 10) + flow = flows[12] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 6}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 10, 'dl_type': None}) + self.assertEquals(flow['priority'], 10) + flow = flows[13] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '20'}, {'type': 'output', 'port': '5'}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 500) + flow = flows[14] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '20'}, {'type': 'output', 'port': '6'}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 10, 'nw_dst': 167776512, 'nw_dst_mask': 24}) + self.assertEquals(flow['priority'], 500) + flow = flows[15] + self.assertEquals(flow['actions'],[]) + self.assertEquals(flow['command'],"DELETE_STRICT") + self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 500) + flow = flows[16] + self.assertEquals(flow['actions'],[]) + self.assertEquals(flow['command'],"DELETE_STRICT") + self.assertEquals(flow['header'], {'phys_port': 10, 'nw_dst': 167776512, 'nw_dst_mask': 24}) + self.assertEquals(flow['priority'], 500) + flow = flows[17] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '22'}, {'type': 'output', 'port': '5'}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 500) + flow = flows[18] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '22'}, {'type': 'output', 'port': '6'}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 10, 'nw_dst': 167776512, 'nw_dst_mask': 24}) + self.assertEquals(flow['priority'], 500) + flow = flows[19] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '20'}, {'type': 'output', 'port': '5'}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776768, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 500) + flow = flows[20] + +class TestFunctionality(unittest.TestCase): + def setUp(self): + self.api = SciPass( logger = logging.getLogger(__name__), + config = str(os.getcwd()) + "/t/etc/SciPass.xml" ) + + def test_update_prefix_bw(self): + #first setup the handler to get all the flows that were sent + flows = [] + def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): + flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) + + self.api.registerForwardingStateChangeHandler(flowSent) + datapath = Mock(id=1) + self.api.switchJoined(datapath) + + self.assertEquals( len(flows), 33) + self.api.updatePrefixBW("%016x" % datapath.id, ipaddr.IPv4Network("10.0.19.0/24"), 500,500) + self.assertTrue(self.api.getBalancer("%016x" % datapath.id, "R&E").getPrefixBW(ipaddr.IPv4Network("10.0.19.0/24")), 1000) + self.api.updatePrefixBW("%016x" % datapath.id, ipaddr.IPv4Network("10.0.17.0/24"), 500,500) + self.assertTrue(self.api.getBalancer("%016x" % datapath.id, "R&E").getPrefixBW(ipaddr.IPv4Network("10.0.17.0/24")), 1000) + + + def test_good_flow(self): + flows = [] + def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): + flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) + + self.api.registerForwardingStateChangeHandler(flowSent) + datapath = Mock(id=1) + self.api.switchJoined(datapath) + #self.logger.error("testing good flow") + self.assertEquals(len(flows),33) + flows = [] + self.api.good_flow({"nw_src": "10.0.20.2/32", "nw_dst":"156.56.6.1/32", "tp_src":1, "tp_dst":2}) + self.assertEquals(len(flows),2) + flow = flows[0] + self.assertEqual(int(flow['hard_timeout']),0) + self.assertEqual(int(flow['idle_timeout']),90) + self.assertEqual(flow['actions'],[{'type': 'output', 'port': '10'}]) + self.assertEqual(flow['header'],{'phys_port': 2, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_src': 167777282, 'tp_dst': 2, 'tp_src': 1, 'nw_dst': 2620917249}) + self.assertEqual(int(flow['priority']),900) + self.assertEqual(flow['command'],"ADD") + self.assertEqual(flow['dpid'],"%016x" % datapath.id) + flow = flows[1] + self.assertEqual(int(flow['hard_timeout']),0) + self.assertEqual(int(flow['idle_timeout']),90) + self.assertEqual(flow['actions'],[{'type': 'output', 'port': '2'}]) + self.assertEqual(flow['header'],{'phys_port': 10, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) + self.assertEqual(int(flow['priority']),900) + self.assertEqual(flow['command'],"ADD") + self.assertEqual(flow['dpid'],"%016x" % datapath.id) + + + + def test_bad_flow(self): + flows = [] + def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): + flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) + + self.api.registerForwardingStateChangeHandler(flowSent) + datapath = Mock(id=1) + self.api.switchJoined(datapath) + #self.logger.error("testing good flow") + self.assertEquals(len(flows),33) + flows = [] + self.api.bad_flow({"nw_src": "10.0.20.2/32", "nw_dst":"156.56.6.1/32", "tp_src":1, "tp_dst":2}) + self.assertEquals(len(flows),2) + flow = flows[0] + self.assertEqual(int(flow['hard_timeout']),0) + self.assertEqual(int(flow['idle_timeout']),90) + self.assertEqual(flow['actions'],[]) + self.assertEqual(flow['header'],{'phys_port': 2, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_src': 167777282, 'tp_dst': 2, 'tp_src': 1, 'nw_dst': 2620917249}) + self.assertEqual(int(flow['priority']),900) + self.assertEqual(flow['command'],"ADD") + self.assertEqual(flow['dpid'],"%016x" % datapath.id) + flow = flows[1] + self.assertEqual(int(flow['hard_timeout']),0) + self.assertEqual(int(flow['idle_timeout']),90) + self.assertEqual(flow['actions'],[]) + self.assertEqual(flow['header'],{'phys_port': 10, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) + self.assertEqual(int(flow['priority']),900) + self.assertEqual(flow['command'],"ADD") + self.assertEqual(flow['dpid'],"%016x" % datapath.id) + +def suite(): + suite = unittest.TestLoader().loadTestsFromTestCase(TestInit) + suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestFunctionality)) + return suite + + +if __name__ == '__main__': + unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/python/t/etc/SciPass-mininet.py b/python/t/etc/SciPass-mininet.py deleted file mode 100644 index c09f993..0000000 --- a/python/t/etc/SciPass-mininet.py +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - 10.0.17.0/24 - 10.0.18.0/24 - - - - 10.0.19.0/24 - 10.0.20.0/24 - - - - - - - - - - - - - - - - diff --git a/python/t/etc/SciPass-mininet.xml b/python/t/etc/SciPass-mininet.xml index c09f993..50f7787 100644 --- a/python/t/etc/SciPass-mininet.xml +++ b/python/t/etc/SciPass-mininet.xml @@ -19,11 +19,14 @@ + + - - - - + + + + + From 499c176562eed902c8c2b6cee525db7de009ca95 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 13 Nov 2014 14:00:18 -0800 Subject: [PATCH 11/81] Fixed scipass directory in puppet manifest --- resources/puppet/manifests/scipass.pp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/puppet/manifests/scipass.pp b/resources/puppet/manifests/scipass.pp index fd05743..3d8ce9f 100644 --- a/resources/puppet/manifests/scipass.pp +++ b/resources/puppet/manifests/scipass.pp @@ -64,7 +64,7 @@ require => Exec['ODL-2.0'] } -vcsrepo { '/home/vagrant/scinet': +vcsrepo { '/home/vagrant/scipass': ensure => present, provider => git, user => 'vagrant', From 5e9cbe027cd9e5669c1658d3317e9f46ecdba9bd Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 13 Nov 2014 14:04:29 -0800 Subject: [PATCH 12/81] Added .idea to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5ff7b36..47e37f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.pyc .vagrant/* +.idea/* From 254d6b2949a53902de634d0b57b0e4be31e48f69 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 13 Nov 2014 14:09:47 -0800 Subject: [PATCH 13/81] Use https for git clones in manifests --- resources/puppet/manifests/scipass.pp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/puppet/manifests/scipass.pp b/resources/puppet/manifests/scipass.pp index 3d8ce9f..ee62b3c 100644 --- a/resources/puppet/manifests/scipass.pp +++ b/resources/puppet/manifests/scipass.pp @@ -68,7 +68,7 @@ ensure => present, provider => git, user => 'vagrant', - source => 'git://github.com/chrissmall22/SciPass', + source => 'https://github.com/chrissmall22/SciPass', revision => 'odl' } From 82bcbc2b716298fa6e35f0dda8e585ce1e107077 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 13 Nov 2014 22:19:38 +0000 Subject: [PATCH 14/81] Added to coverage files to .gitignore --- .gitignore | 4 + python/t/SciPassTest_mininet.py,cover | 254 ++++++++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 python/t/SciPassTest_mininet.py,cover diff --git a/.gitignore b/.gitignore index 47e37f5..65cfb56 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ *.pyc .vagrant/* .idea/* +*,cover +coverage.xml +python/.coverage +python/htmlcov diff --git a/python/t/SciPassTest_mininet.py,cover b/python/t/SciPassTest_mininet.py,cover new file mode 100644 index 0000000..c922d44 --- /dev/null +++ b/python/t/SciPassTest_mininet.py,cover @@ -0,0 +1,254 @@ + # Version of the SciPass tests that intergrates a mininet instance + # Provides an easy way to test without a physical switch and/or with more complex topologies + + +> import sys +> sys.path.append(".") +> import pprint +> import unittest +> from mock import Mock +> import logging +> import ipaddr +> import os +> from SciPass import SciPass +> import libxml2 + +> logging.basicConfig() + + +> class TestInit(unittest.TestCase): + +> def test_valid_config(self): +> api = SciPass( logger = logging.getLogger(__name__), +> config = str(os.getcwd()) + "/t/etc/SciPass_mininet.xml" +> ) +! self.assertTrue(isinstance(api,SciPass)) + + + # def test_no_config(self): + # self.assertRaises(libxml2.parserError,SciPass) + # + # api = SciPass( logger = logging.getLogger(__name__), + # config = str(os.getcwd()) + "/t/etc/no_config.xml" ) + + + # def test_invalid_config(self): + # self.assertRaises(libxml2.parserError,SciPass) + # + # api = SciPass( logger = logging.getLogger(__name__), + # config = str(os.getcwd()) + "/t/etc/InvalidConfig.xml" ) + +> def test_switch_init(self): +> api = SciPass( logger = logging.getLogger(__name__), +> config = str(os.getcwd()) + "/t/etc/SciPass_mininet.xml" ) + + #first setup the handler to get all the flows that were sent +! flows = [] +! def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): +! flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) + +! api.registerForwardingStateChangeHandler(flowSent) +! datapath = Mock(id=1) +! api.switchJoined(datapath) + +! self.assertTrue( len(flows) == 33) + #verify all of the 'flow details are set properly' +! for flow in flows: +! self.assertEquals(flow['dpid'], "%016x" % datapath.id) +! self.assertEquals(flow['hard_timeout'], 0) +! self.assertEquals(flow['idle_timeout'], 0) + +! flow = flows[0] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port': 1, 'dl_type': None}) +! self.assertEquals(flow['priority'], 5) +! flow = flows[1] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167776512, 'nw_dst_mask': 24}) +! self.assertEquals(flow['priority'], 10) +! flow = flows[2] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port':1, 'nw_src': 167776512, 'nw_src_mask': 24}) +! self.assertEquals(flow['priority'], 10) +! flow = flows[3] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167776768, 'nw_dst_mask': 24}) +! self.assertEquals(flow['priority'], 10) +! flow = flows[4] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port':1, 'nw_src': 167776768, 'nw_src_mask': 24}) +! self.assertEquals(flow['priority'], 10) +! flow = flows[5] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port': 2, 'dl_type': None}) +! self.assertEquals(flow['priority'], 5) +! flow = flows[6] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 2}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167777024, 'nw_dst_mask': 24}) +! self.assertEquals(flow['priority'], 10) +! flow = flows[7] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port':2, 'nw_src': 167777024, 'nw_src_mask': 24}) +! self.assertEquals(flow['priority'], 10) +! flow = flows[8] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 2}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167777280, 'nw_dst_mask': 24}) +! self.assertEquals(flow['priority'], 10) +! flow = flows[9] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port':2, 'nw_src': 167777280, 'nw_src_mask': 24}) +! self.assertEquals(flow['priority'], 10) +! flow = flows[10] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}, {'type': 'output', 'port': 2}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port': 5, 'dl_type': None}) +! self.assertEquals(flow['priority'], 3) +! flow = flows[11] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 10}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port': 6, 'dl_type': None}) +! self.assertEquals(flow['priority'], 10) +! flow = flows[12] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 6}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port': 10, 'dl_type': None}) +! self.assertEquals(flow['priority'], 10) +! flow = flows[13] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': '20'}, {'type': 'output', 'port': '5'}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) +! self.assertEquals(flow['priority'], 500) +! flow = flows[14] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': '20'}, {'type': 'output', 'port': '6'}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port': 10, 'nw_dst': 167776512, 'nw_dst_mask': 24}) +! self.assertEquals(flow['priority'], 500) +! flow = flows[15] +! self.assertEquals(flow['actions'],[]) +! self.assertEquals(flow['command'],"DELETE_STRICT") +! self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) +! self.assertEquals(flow['priority'], 500) +! flow = flows[16] +! self.assertEquals(flow['actions'],[]) +! self.assertEquals(flow['command'],"DELETE_STRICT") +! self.assertEquals(flow['header'], {'phys_port': 10, 'nw_dst': 167776512, 'nw_dst_mask': 24}) +! self.assertEquals(flow['priority'], 500) +! flow = flows[17] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': '22'}, {'type': 'output', 'port': '5'}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) +! self.assertEquals(flow['priority'], 500) +! flow = flows[18] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': '22'}, {'type': 'output', 'port': '6'}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port': 10, 'nw_dst': 167776512, 'nw_dst_mask': 24}) +! self.assertEquals(flow['priority'], 500) +! flow = flows[19] +! self.assertEquals(flow['actions'],[{'type': 'output', 'port': '20'}, {'type': 'output', 'port': '5'}]) +! self.assertEquals(flow['command'],"ADD") +! self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776768, 'nw_src_mask': 24}) +! self.assertEquals(flow['priority'], 500) +! flow = flows[20] + +> class TestFunctionality(unittest.TestCase): +> def setUp(self): +> self.api = SciPass( logger = logging.getLogger(__name__), +> config = str(os.getcwd()) + "/t/etc/SciPass.xml" ) + +> def test_update_prefix_bw(self): + #first setup the handler to get all the flows that were sent +> flows = [] +> def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): +> flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) + +> self.api.registerForwardingStateChangeHandler(flowSent) +> datapath = Mock(id=1) +> self.api.switchJoined(datapath) + +> self.assertEquals( len(flows), 33) +> self.api.updatePrefixBW("%016x" % datapath.id, ipaddr.IPv4Network("10.0.19.0/24"), 500,500) +> self.assertTrue(self.api.getBalancer("%016x" % datapath.id, "R&E").getPrefixBW(ipaddr.IPv4Network("10.0.19.0/24")), 1000) +> self.api.updatePrefixBW("%016x" % datapath.id, ipaddr.IPv4Network("10.0.17.0/24"), 500,500) +> self.assertTrue(self.api.getBalancer("%016x" % datapath.id, "R&E").getPrefixBW(ipaddr.IPv4Network("10.0.17.0/24")), 1000) + + +> def test_good_flow(self): +> flows = [] +> def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): +> flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) + +> self.api.registerForwardingStateChangeHandler(flowSent) +> datapath = Mock(id=1) +> self.api.switchJoined(datapath) + #self.logger.error("testing good flow") +> self.assertEquals(len(flows),33) +> flows = [] +> self.api.good_flow({"nw_src": "10.0.20.2/32", "nw_dst":"156.56.6.1/32", "tp_src":1, "tp_dst":2}) +> self.assertEquals(len(flows),2) +> flow = flows[0] +> self.assertEqual(int(flow['hard_timeout']),0) +> self.assertEqual(int(flow['idle_timeout']),90) +> self.assertEqual(flow['actions'],[{'type': 'output', 'port': '10'}]) +> self.assertEqual(flow['header'],{'phys_port': 2, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_src': 167777282, 'tp_dst': 2, 'tp_src': 1, 'nw_dst': 2620917249}) +> self.assertEqual(int(flow['priority']),900) +> self.assertEqual(flow['command'],"ADD") +> self.assertEqual(flow['dpid'],"%016x" % datapath.id) +> flow = flows[1] +> self.assertEqual(int(flow['hard_timeout']),0) +> self.assertEqual(int(flow['idle_timeout']),90) +> self.assertEqual(flow['actions'],[{'type': 'output', 'port': '2'}]) +> self.assertEqual(flow['header'],{'phys_port': 10, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) +> self.assertEqual(int(flow['priority']),900) +> self.assertEqual(flow['command'],"ADD") +> self.assertEqual(flow['dpid'],"%016x" % datapath.id) + + + +> def test_bad_flow(self): +> flows = [] +> def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): +> flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) + +> self.api.registerForwardingStateChangeHandler(flowSent) +> datapath = Mock(id=1) +> self.api.switchJoined(datapath) + #self.logger.error("testing good flow") +> self.assertEquals(len(flows),33) +> flows = [] +> self.api.bad_flow({"nw_src": "10.0.20.2/32", "nw_dst":"156.56.6.1/32", "tp_src":1, "tp_dst":2}) +> self.assertEquals(len(flows),2) +> flow = flows[0] +> self.assertEqual(int(flow['hard_timeout']),0) +> self.assertEqual(int(flow['idle_timeout']),90) +> self.assertEqual(flow['actions'],[]) +> self.assertEqual(flow['header'],{'phys_port': 2, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_src': 167777282, 'tp_dst': 2, 'tp_src': 1, 'nw_dst': 2620917249}) +> self.assertEqual(int(flow['priority']),900) +> self.assertEqual(flow['command'],"ADD") +> self.assertEqual(flow['dpid'],"%016x" % datapath.id) +> flow = flows[1] +> self.assertEqual(int(flow['hard_timeout']),0) +> self.assertEqual(int(flow['idle_timeout']),90) +> self.assertEqual(flow['actions'],[]) +> self.assertEqual(flow['header'],{'phys_port': 10, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) +> self.assertEqual(int(flow['priority']),900) +> self.assertEqual(flow['command'],"ADD") +> self.assertEqual(flow['dpid'],"%016x" % datapath.id) + +> def suite(): +> suite = unittest.TestLoader().loadTestsFromTestCase(TestInit) +> suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestFunctionality)) +> return suite + + +> if __name__ == '__main__': +> unittest.TextTestRunner(verbosity=2).run(suite()) From b7ba57f5eb19b5a66bd15f349009fb62167ca609 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 13 Nov 2014 22:31:57 -0800 Subject: [PATCH 15/81] Removed SciPass-mininet.py --- python/t/etc/SciPass-mininet.py | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 python/t/etc/SciPass-mininet.py diff --git a/python/t/etc/SciPass-mininet.py b/python/t/etc/SciPass-mininet.py deleted file mode 100644 index c09f993..0000000 --- a/python/t/etc/SciPass-mininet.py +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - 10.0.17.0/24 - 10.0.18.0/24 - - - - 10.0.19.0/24 - 10.0.20.0/24 - - - - - - - - - - - - - - - - From f557107f3bad6f7eb7d9939faed5c0d41e3dd133 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 13 Nov 2014 22:44:15 -0800 Subject: [PATCH 16/81] Mininet test changes --- python/SciPass.py | 8 +- python/t/SciPassTest_mininet.py | 6 +- python/t/SciPassTest_mininet.py,cover | 254 -------------------------- 3 files changed, 7 insertions(+), 261 deletions(-) delete mode 100644 python/t/SciPassTest_mininet.py,cover diff --git a/python/SciPass.py b/python/SciPass.py index eecfb12..1d2e835 100644 --- a/python/SciPass.py +++ b/python/SciPass.py @@ -68,7 +68,7 @@ def good_flow(self, obj): for name in self.config[datapath_id]: for port in self.config[datapath_id][name]['ports']['lan']: for prefix in port['prefixes']: - self.logger.error("Comparing" + str(new_prefix) + " " + str(prefix['prefix'])) + #self.logger.error("Comparing" + str(new_prefix) + " " + str(prefix['prefix'])) if(prefix['prefix'].Contains( new_prefix )): in_port = port dpid = datapath_id @@ -652,7 +652,7 @@ def _setupSciDMZRules(self, dpid = None, domain_name = None): actions = [] actions.append({"type": "output", "port": int(ports['wan'][0]['port_id'])}) - self.logger.error("FW WAN -> WAN: ") + #self.logger.error("FW WAN -> WAN: ") self.fireForwardingStateChangeHandlers( dpid = dpid, domain = domain_name, header = header, @@ -668,7 +668,7 @@ def _setupSciDMZRules(self, dpid = None, domain_name = None): actions = [] actions.append({"type": "output", "port": int(ports['fw_wan'][0]['port_id'])}) - self.logger.error("WAN -> FW WAN") + #self.logger.error("WAN -> FW WAN") self.fireForwardingStateChangeHandlers( dpid = dpid, domain = domain_name, header = header, @@ -716,7 +716,7 @@ def _setupInlineIDS(self, dpid = None, domain_name = None): actions = [] actions.append({"type": "output", "port": int(ports['lan'][0]['port_id'])}) - self.logger.error("FW WAN -> WAN: ") + #self.logger.error("FW WAN -> WAN: ") self.fireForwardingStateChangeHandlers( dpid = dpid, domain = domain_name, header = header, diff --git a/python/t/SciPassTest_mininet.py b/python/t/SciPassTest_mininet.py index 091ab17..7bd9a29 100644 --- a/python/t/SciPassTest_mininet.py +++ b/python/t/SciPassTest_mininet.py @@ -20,7 +20,7 @@ class TestInit(unittest.TestCase): def test_valid_config(self): api = SciPass( logger = logging.getLogger(__name__), - config = str(os.getcwd()) + "/t/etc/SciPass_mininet.xml" + config = str(os.getcwd()) + "/t/etc/SciPass-mininet.xml" ) self.assertTrue(isinstance(api,SciPass)) @@ -40,7 +40,7 @@ def test_valid_config(self): def test_switch_init(self): api = SciPass( logger = logging.getLogger(__name__), - config = str(os.getcwd()) + "/t/etc/SciPass_mininet.xml" ) + config = str(os.getcwd()) + "/t/etc/SciPass-mininet.xml" ) #first setup the handler to get all the flows that were sent flows = [] @@ -163,7 +163,7 @@ def flowSent(dpid = None, header = None, actions = None,command = None, priority class TestFunctionality(unittest.TestCase): def setUp(self): self.api = SciPass( logger = logging.getLogger(__name__), - config = str(os.getcwd()) + "/t/etc/SciPass.xml" ) + config = str(os.getcwd()) + "/t/etc/SciPass-mininet.xml" ) def test_update_prefix_bw(self): #first setup the handler to get all the flows that were sent diff --git a/python/t/SciPassTest_mininet.py,cover b/python/t/SciPassTest_mininet.py,cover deleted file mode 100644 index c922d44..0000000 --- a/python/t/SciPassTest_mininet.py,cover +++ /dev/null @@ -1,254 +0,0 @@ - # Version of the SciPass tests that intergrates a mininet instance - # Provides an easy way to test without a physical switch and/or with more complex topologies - - -> import sys -> sys.path.append(".") -> import pprint -> import unittest -> from mock import Mock -> import logging -> import ipaddr -> import os -> from SciPass import SciPass -> import libxml2 - -> logging.basicConfig() - - -> class TestInit(unittest.TestCase): - -> def test_valid_config(self): -> api = SciPass( logger = logging.getLogger(__name__), -> config = str(os.getcwd()) + "/t/etc/SciPass_mininet.xml" -> ) -! self.assertTrue(isinstance(api,SciPass)) - - - # def test_no_config(self): - # self.assertRaises(libxml2.parserError,SciPass) - # - # api = SciPass( logger = logging.getLogger(__name__), - # config = str(os.getcwd()) + "/t/etc/no_config.xml" ) - - - # def test_invalid_config(self): - # self.assertRaises(libxml2.parserError,SciPass) - # - # api = SciPass( logger = logging.getLogger(__name__), - # config = str(os.getcwd()) + "/t/etc/InvalidConfig.xml" ) - -> def test_switch_init(self): -> api = SciPass( logger = logging.getLogger(__name__), -> config = str(os.getcwd()) + "/t/etc/SciPass_mininet.xml" ) - - #first setup the handler to get all the flows that were sent -! flows = [] -! def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): -! flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) - -! api.registerForwardingStateChangeHandler(flowSent) -! datapath = Mock(id=1) -! api.switchJoined(datapath) - -! self.assertTrue( len(flows) == 33) - #verify all of the 'flow details are set properly' -! for flow in flows: -! self.assertEquals(flow['dpid'], "%016x" % datapath.id) -! self.assertEquals(flow['hard_timeout'], 0) -! self.assertEquals(flow['idle_timeout'], 0) - -! flow = flows[0] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port': 1, 'dl_type': None}) -! self.assertEquals(flow['priority'], 5) -! flow = flows[1] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167776512, 'nw_dst_mask': 24}) -! self.assertEquals(flow['priority'], 10) -! flow = flows[2] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port':1, 'nw_src': 167776512, 'nw_src_mask': 24}) -! self.assertEquals(flow['priority'], 10) -! flow = flows[3] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167776768, 'nw_dst_mask': 24}) -! self.assertEquals(flow['priority'], 10) -! flow = flows[4] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port':1, 'nw_src': 167776768, 'nw_src_mask': 24}) -! self.assertEquals(flow['priority'], 10) -! flow = flows[5] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port': 2, 'dl_type': None}) -! self.assertEquals(flow['priority'], 5) -! flow = flows[6] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 2}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167777024, 'nw_dst_mask': 24}) -! self.assertEquals(flow['priority'], 10) -! flow = flows[7] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port':2, 'nw_src': 167777024, 'nw_src_mask': 24}) -! self.assertEquals(flow['priority'], 10) -! flow = flows[8] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 2}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167777280, 'nw_dst_mask': 24}) -! self.assertEquals(flow['priority'], 10) -! flow = flows[9] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port':2, 'nw_src': 167777280, 'nw_src_mask': 24}) -! self.assertEquals(flow['priority'], 10) -! flow = flows[10] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}, {'type': 'output', 'port': 2}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port': 5, 'dl_type': None}) -! self.assertEquals(flow['priority'], 3) -! flow = flows[11] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 10}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port': 6, 'dl_type': None}) -! self.assertEquals(flow['priority'], 10) -! flow = flows[12] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': 6}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port': 10, 'dl_type': None}) -! self.assertEquals(flow['priority'], 10) -! flow = flows[13] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': '20'}, {'type': 'output', 'port': '5'}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) -! self.assertEquals(flow['priority'], 500) -! flow = flows[14] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': '20'}, {'type': 'output', 'port': '6'}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port': 10, 'nw_dst': 167776512, 'nw_dst_mask': 24}) -! self.assertEquals(flow['priority'], 500) -! flow = flows[15] -! self.assertEquals(flow['actions'],[]) -! self.assertEquals(flow['command'],"DELETE_STRICT") -! self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) -! self.assertEquals(flow['priority'], 500) -! flow = flows[16] -! self.assertEquals(flow['actions'],[]) -! self.assertEquals(flow['command'],"DELETE_STRICT") -! self.assertEquals(flow['header'], {'phys_port': 10, 'nw_dst': 167776512, 'nw_dst_mask': 24}) -! self.assertEquals(flow['priority'], 500) -! flow = flows[17] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': '22'}, {'type': 'output', 'port': '5'}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) -! self.assertEquals(flow['priority'], 500) -! flow = flows[18] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': '22'}, {'type': 'output', 'port': '6'}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port': 10, 'nw_dst': 167776512, 'nw_dst_mask': 24}) -! self.assertEquals(flow['priority'], 500) -! flow = flows[19] -! self.assertEquals(flow['actions'],[{'type': 'output', 'port': '20'}, {'type': 'output', 'port': '5'}]) -! self.assertEquals(flow['command'],"ADD") -! self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776768, 'nw_src_mask': 24}) -! self.assertEquals(flow['priority'], 500) -! flow = flows[20] - -> class TestFunctionality(unittest.TestCase): -> def setUp(self): -> self.api = SciPass( logger = logging.getLogger(__name__), -> config = str(os.getcwd()) + "/t/etc/SciPass.xml" ) - -> def test_update_prefix_bw(self): - #first setup the handler to get all the flows that were sent -> flows = [] -> def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): -> flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) - -> self.api.registerForwardingStateChangeHandler(flowSent) -> datapath = Mock(id=1) -> self.api.switchJoined(datapath) - -> self.assertEquals( len(flows), 33) -> self.api.updatePrefixBW("%016x" % datapath.id, ipaddr.IPv4Network("10.0.19.0/24"), 500,500) -> self.assertTrue(self.api.getBalancer("%016x" % datapath.id, "R&E").getPrefixBW(ipaddr.IPv4Network("10.0.19.0/24")), 1000) -> self.api.updatePrefixBW("%016x" % datapath.id, ipaddr.IPv4Network("10.0.17.0/24"), 500,500) -> self.assertTrue(self.api.getBalancer("%016x" % datapath.id, "R&E").getPrefixBW(ipaddr.IPv4Network("10.0.17.0/24")), 1000) - - -> def test_good_flow(self): -> flows = [] -> def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): -> flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) - -> self.api.registerForwardingStateChangeHandler(flowSent) -> datapath = Mock(id=1) -> self.api.switchJoined(datapath) - #self.logger.error("testing good flow") -> self.assertEquals(len(flows),33) -> flows = [] -> self.api.good_flow({"nw_src": "10.0.20.2/32", "nw_dst":"156.56.6.1/32", "tp_src":1, "tp_dst":2}) -> self.assertEquals(len(flows),2) -> flow = flows[0] -> self.assertEqual(int(flow['hard_timeout']),0) -> self.assertEqual(int(flow['idle_timeout']),90) -> self.assertEqual(flow['actions'],[{'type': 'output', 'port': '10'}]) -> self.assertEqual(flow['header'],{'phys_port': 2, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_src': 167777282, 'tp_dst': 2, 'tp_src': 1, 'nw_dst': 2620917249}) -> self.assertEqual(int(flow['priority']),900) -> self.assertEqual(flow['command'],"ADD") -> self.assertEqual(flow['dpid'],"%016x" % datapath.id) -> flow = flows[1] -> self.assertEqual(int(flow['hard_timeout']),0) -> self.assertEqual(int(flow['idle_timeout']),90) -> self.assertEqual(flow['actions'],[{'type': 'output', 'port': '2'}]) -> self.assertEqual(flow['header'],{'phys_port': 10, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) -> self.assertEqual(int(flow['priority']),900) -> self.assertEqual(flow['command'],"ADD") -> self.assertEqual(flow['dpid'],"%016x" % datapath.id) - - - -> def test_bad_flow(self): -> flows = [] -> def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): -> flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) - -> self.api.registerForwardingStateChangeHandler(flowSent) -> datapath = Mock(id=1) -> self.api.switchJoined(datapath) - #self.logger.error("testing good flow") -> self.assertEquals(len(flows),33) -> flows = [] -> self.api.bad_flow({"nw_src": "10.0.20.2/32", "nw_dst":"156.56.6.1/32", "tp_src":1, "tp_dst":2}) -> self.assertEquals(len(flows),2) -> flow = flows[0] -> self.assertEqual(int(flow['hard_timeout']),0) -> self.assertEqual(int(flow['idle_timeout']),90) -> self.assertEqual(flow['actions'],[]) -> self.assertEqual(flow['header'],{'phys_port': 2, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_src': 167777282, 'tp_dst': 2, 'tp_src': 1, 'nw_dst': 2620917249}) -> self.assertEqual(int(flow['priority']),900) -> self.assertEqual(flow['command'],"ADD") -> self.assertEqual(flow['dpid'],"%016x" % datapath.id) -> flow = flows[1] -> self.assertEqual(int(flow['hard_timeout']),0) -> self.assertEqual(int(flow['idle_timeout']),90) -> self.assertEqual(flow['actions'],[]) -> self.assertEqual(flow['header'],{'phys_port': 10, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) -> self.assertEqual(int(flow['priority']),900) -> self.assertEqual(flow['command'],"ADD") -> self.assertEqual(flow['dpid'],"%016x" % datapath.id) - -> def suite(): -> suite = unittest.TestLoader().loadTestsFromTestCase(TestInit) -> suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestFunctionality)) -> return suite - - -> if __name__ == '__main__': -> unittest.TextTestRunner(verbosity=2).run(suite()) From 5bd5bce77148a4b1ec83055b970fb6a9043607db Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 13 Nov 2014 22:48:37 -0800 Subject: [PATCH 17/81] Fix mininet test port numbers --- python/t/SciPassTest_mininet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/t/SciPassTest_mininet.py b/python/t/SciPassTest_mininet.py index 7bd9a29..4a10c7b 100644 --- a/python/t/SciPassTest_mininet.py +++ b/python/t/SciPassTest_mininet.py @@ -198,7 +198,7 @@ def flowSent(dpid = None, header = None, actions = None,command = None, priority flow = flows[0] self.assertEqual(int(flow['hard_timeout']),0) self.assertEqual(int(flow['idle_timeout']),90) - self.assertEqual(flow['actions'],[{'type': 'output', 'port': '10'}]) + self.assertEqual(flow['actions'],[{'type': 'output', 'port': '3'}]) self.assertEqual(flow['header'],{'phys_port': 2, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_src': 167777282, 'tp_dst': 2, 'tp_src': 1, 'nw_dst': 2620917249}) self.assertEqual(int(flow['priority']),900) self.assertEqual(flow['command'],"ADD") @@ -239,7 +239,7 @@ def flowSent(dpid = None, header = None, actions = None,command = None, priority self.assertEqual(int(flow['hard_timeout']),0) self.assertEqual(int(flow['idle_timeout']),90) self.assertEqual(flow['actions'],[]) - self.assertEqual(flow['header'],{'phys_port': 10, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) + self.assertEqual(flow['header'],{'phys_port': 3, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) self.assertEqual(int(flow['priority']),900) self.assertEqual(flow['command'],"ADD") self.assertEqual(flow['dpid'],"%016x" % datapath.id) From baa8f1b0008170176f6fa45c58caf0dc4d952728 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 13 Nov 2014 23:00:27 -0800 Subject: [PATCH 18/81] Moved port numbers in SciPassTest_mininet/py --- README.md | 2 +- python/t/SciPassTest_mininet.py | 48 ++++++++++++++++----------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 6ee44ee..9146ca1 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ To start the Ryu version of SciPass To create a Mininet network to match SciPass-mininet.xml cd mininet - sudo mn --topo single,7 --mac --switch ovsk --controller remote + sudo mn --topo single,9 --mac --switch ovsk --controller remote Postman ======= diff --git a/python/t/SciPassTest_mininet.py b/python/t/SciPassTest_mininet.py index 4a10c7b..b1f83e9 100644 --- a/python/t/SciPassTest_mininet.py +++ b/python/t/SciPassTest_mininet.py @@ -59,79 +59,79 @@ def flowSent(dpid = None, header = None, actions = None,command = None, priority self.assertEquals(flow['idle_timeout'], 0) flow = flows[0] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) self.assertEquals(flow['command'],"ADD") self.assertEquals(flow['header'], {'phys_port': 1, 'dl_type': None}) self.assertEquals(flow['priority'], 5) flow = flows[1] self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}]) self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167776512, 'nw_dst_mask': 24}) + self.assertEquals(flow['header'], {'phys_port':4, 'nw_dst': 167776512, 'nw_dst_mask': 24}) self.assertEquals(flow['priority'], 10) flow = flows[2] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) self.assertEquals(flow['command'],"ADD") self.assertEquals(flow['header'], {'phys_port':1, 'nw_src': 167776512, 'nw_src_mask': 24}) self.assertEquals(flow['priority'], 10) flow = flows[3] self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}]) self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167776768, 'nw_dst_mask': 24}) + self.assertEquals(flow['header'], {'phys_port':4, 'nw_dst': 167776768, 'nw_dst_mask': 24}) self.assertEquals(flow['priority'], 10) flow = flows[4] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) self.assertEquals(flow['command'],"ADD") self.assertEquals(flow['header'], {'phys_port':1, 'nw_src': 167776768, 'nw_src_mask': 24}) self.assertEquals(flow['priority'], 10) flow = flows[5] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) self.assertEquals(flow['command'],"ADD") self.assertEquals(flow['header'], {'phys_port': 2, 'dl_type': None}) self.assertEquals(flow['priority'], 5) flow = flows[6] self.assertEquals(flow['actions'],[{'type': 'output', 'port': 2}]) self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167777024, 'nw_dst_mask': 24}) + self.assertEquals(flow['header'], {'phys_port':4, 'nw_dst': 167777024, 'nw_dst_mask': 24}) self.assertEquals(flow['priority'], 10) flow = flows[7] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) self.assertEquals(flow['command'],"ADD") self.assertEquals(flow['header'], {'phys_port':2, 'nw_src': 167777024, 'nw_src_mask': 24}) self.assertEquals(flow['priority'], 10) flow = flows[8] self.assertEquals(flow['actions'],[{'type': 'output', 'port': 2}]) self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port':5, 'nw_dst': 167777280, 'nw_dst_mask': 24}) + self.assertEquals(flow['header'], {'phys_port':4, 'nw_dst': 167777280, 'nw_dst_mask': 24}) self.assertEquals(flow['priority'], 10) flow = flows[9] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) self.assertEquals(flow['command'],"ADD") self.assertEquals(flow['header'], {'phys_port':2, 'nw_src': 167777280, 'nw_src_mask': 24}) self.assertEquals(flow['priority'], 10) flow = flows[10] self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}, {'type': 'output', 'port': 2}]) self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 5, 'dl_type': None}) + self.assertEquals(flow['header'], {'phys_port': 4, 'dl_type': None}) self.assertEquals(flow['priority'], 3) flow = flows[11] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 10}]) + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 3}]) self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 6, 'dl_type': None}) + self.assertEquals(flow['header'], {'phys_port': 7, 'dl_type': None}) self.assertEquals(flow['priority'], 10) flow = flows[12] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 6}]) + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 10, 'dl_type': None}) + self.assertEquals(flow['header'], {'phys_port': 3, 'dl_type': None}) self.assertEquals(flow['priority'], 10) flow = flows[13] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': '20'}, {'type': 'output', 'port': '5'}]) + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '6'}, {'type': 'output', 'port': '4'}]) self.assertEquals(flow['command'],"ADD") self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) self.assertEquals(flow['priority'], 500) flow = flows[14] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': '20'}, {'type': 'output', 'port': '6'}]) + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '6'}, {'type': 'output', 'port': '5'}]) self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 10, 'nw_dst': 167776512, 'nw_dst_mask': 24}) + self.assertEquals(flow['header'], {'phys_port': 3, 'nw_dst': 167776512, 'nw_dst_mask': 24}) self.assertEquals(flow['priority'], 500) flow = flows[15] self.assertEquals(flow['actions'],[]) @@ -141,20 +141,20 @@ def flowSent(dpid = None, header = None, actions = None,command = None, priority flow = flows[16] self.assertEquals(flow['actions'],[]) self.assertEquals(flow['command'],"DELETE_STRICT") - self.assertEquals(flow['header'], {'phys_port': 10, 'nw_dst': 167776512, 'nw_dst_mask': 24}) + self.assertEquals(flow['header'], {'phys_port': 3, 'nw_dst': 167776512, 'nw_dst_mask': 24}) self.assertEquals(flow['priority'], 500) flow = flows[17] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': '22'}, {'type': 'output', 'port': '5'}]) + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '8'}, {'type': 'output', 'port': '4'}]) self.assertEquals(flow['command'],"ADD") self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) self.assertEquals(flow['priority'], 500) flow = flows[18] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': '22'}, {'type': 'output', 'port': '6'}]) + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '8'}, {'type': 'output', 'port': '5'}]) self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 10, 'nw_dst': 167776512, 'nw_dst_mask': 24}) + self.assertEquals(flow['header'], {'phys_port': 3, 'nw_dst': 167776512, 'nw_dst_mask': 24}) self.assertEquals(flow['priority'], 500) flow = flows[19] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': '20'}, {'type': 'output', 'port': '5'}]) + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '6'}, {'type': 'output', 'port': '4'}]) self.assertEquals(flow['command'],"ADD") self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776768, 'nw_src_mask': 24}) self.assertEquals(flow['priority'], 500) @@ -207,7 +207,7 @@ def flowSent(dpid = None, header = None, actions = None,command = None, priority self.assertEqual(int(flow['hard_timeout']),0) self.assertEqual(int(flow['idle_timeout']),90) self.assertEqual(flow['actions'],[{'type': 'output', 'port': '2'}]) - self.assertEqual(flow['header'],{'phys_port': 10, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) + self.assertEqual(flow['header'],{'phys_port': 3, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) self.assertEqual(int(flow['priority']),900) self.assertEqual(flow['command'],"ADD") self.assertEqual(flow['dpid'],"%016x" % datapath.id) From 26c3b2991d867ae826b0d34f83f0d34550358ece Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 13 Nov 2014 23:02:17 -0800 Subject: [PATCH 19/81] One more mininet test port change --- python/t/SciPassTest_mininet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/t/SciPassTest_mininet.py b/python/t/SciPassTest_mininet.py index b1f83e9..5835b01 100644 --- a/python/t/SciPassTest_mininet.py +++ b/python/t/SciPassTest_mininet.py @@ -116,7 +116,7 @@ def flowSent(dpid = None, header = None, actions = None,command = None, priority flow = flows[11] self.assertEquals(flow['actions'],[{'type': 'output', 'port': 3}]) self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 7, 'dl_type': None}) + self.assertEquals(flow['header'], {'phys_port': 5, 'dl_type': None}) self.assertEquals(flow['priority'], 10) flow = flows[12] self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) From 75250a767feb43280ddc21ad8e7bbfc5a67523c4 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 13 Nov 2014 23:36:12 -0800 Subject: [PATCH 20/81] ODL initial checkin Main class to do ODL based config --- python/ODL.py | 527 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 527 insertions(+) create mode 100644 python/ODL.py diff --git a/python/ODL.py b/python/ODL.py new file mode 100644 index 0000000..a289412 --- /dev/null +++ b/python/ODL.py @@ -0,0 +1,527 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2014 The Trustees of Indiana University +# Copyright (C) 2013 Hewlett-Packard Development Company, L.P +# +# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 +# +# 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 logging +import struct +import time + +from operator import attrgetter +from webob import Response +import json + +import socket +import ipaddr + +from collections import defaultdict +from SimpleBalancer import SimpleBalancer +from SciPass import SciPass + +""" + Forwarding rule Priorities + BlackList + WhiteList + Balancer + Default +""" + + +class SciPassRest(ControllerBase): + def __init__(self, req, link, data, **config): + super(SciPassRest, self).__init__(req, link, data, **config) + self.api = data['api'] + + #POST /scipass/flows/good_flow + @route('scipass', '/scipass/flows/good_flow', methods=['PUT']) + def good_flow(self, req): + try: + obj = eval(req.body) + except SyntaxError: + self.logger.error("Syntax Error processing good_flow signal %s", req.body) + return Response(status=400) + + result = self.api.good_flow(obj) + return Response(content_type='application/json',body=json.dumps(result)) + + #POST /scipass/flows/bad_flow + @route('scipass', '/scipass/flows/bad_flow', methods=['PUT']) + def bad_flow(self, req): + try: + obj = eval(req.body) + except SyntaxError: + self.logger.error("Syntax Error processing bad_flow signal %s", req.body) + return Response(status=400) + result = self.api.bad_flow(obj) + return Response(content_type='application/json',body=json.dumps(result)) + + #GET /scipass/flows/get_good_flows + @route('scipass', '/scipass/flows/get_good_flows', methods=['GET']) + def get_good_flows(self, req): + result = self.api.get_good_flows() + return Response(content_type='application/json',body=json.dumps(result)) + + #GET /scipass/flows/get_bad_flows + @route('scipass', '/scipass/flows/get_bad_flows', methods=['GET']) + def get_bad_flows(self, req): + result = self.api.get_bad_flows() + return Response(content_type='application/json',body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/flows', methods=['GET'], requirements= {'dpid': dpid_lib.DPID_PATTERN}) + def get_switch_flows(self, req, **kwargs): + result = self.api.getSwitchFlows(dpid=kwargs['dpid']) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/sensor/load', methods=['PUT']) + def update_sensor_load(self, req): + try: + obj = eval(req.body) + except SyntaxError: + self.logger.error("Syntax Error processing update_sensor_status signal %s", req.body) + return Response(status=400) + result = self.api.setSensorStatus(obj['sensor_id'],obj['load']) + return Response(content_type='application/json',body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensor/{sensor_id}', methods=['GET'], requirements= {'dpid': dpid_lib.DPID_PATTERN}) + def get_sensor_load(self, req, **kwargs): + result = self.api.getSensorStatus(dpid=kwargs['dpid'], domain=kwargs['domain'], sensor_id=kwargs['sensor_id']) + return Response(content_type='application/json',body=json.dumps(result)) + + @route('scipass', '/scipass/switches', methods=['GET']) + def get_switches(self, req): + result = self.api.getSwitches() + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensors', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) + def get_domain_sensors(self, req, **kwargs): + result = self.api.getDomainSensors(dpid = kwargs['dpid'], domain = kwargs['domain'] ) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domains', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) + def get_switch_domains(self, req, **kwargs): + result = self.api.getSwitchDomains(dpid=kwargs['dpid']) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) + def get_domain_status(self, req, **kwargs): + result = self.api.getDomainStatus(dpid = kwargs['dpid'], domain = kwargs['domain']) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/flows',methods=['GET'],requirements = {'dpid': dpid_lib.DPID_PATTERN}) + def get_domain_flows(self,req, **kwargs): + result = self.api.getDomainFlows(dpid = kwargs['dpid'], domain = kwargs['domain']) + return Response(content_type='application/json', body=json.dumps(result)) + + +class ODL(): + def __init__(self, *args, **kwargs): + #--- register for configuration options + self.CONF.register_opts([ + cfg.StrOpt('SciPassConfig',default='/etc/SciPass/SciPass.xml', + help='where to find the SciPass config file'), + ]) + + self.logger.error("Starting SciPass") + self.datapaths = {} + self.isactive = 1 + self.statsInterval = 5 + self.balanceInterval = 15 + self.bal = None + + #--- ODL Specifics + self.odl_host = "127.0.0.1" + self.odl_port = "8080" + + self.stats_thread = hub.spawn(self._stats_loop) + self.balance_thread = hub.spawn(self._balance_loop) + + self.ports = defaultdict(dict); + self.prefix_bytes = defaultdict(lambda: defaultdict(int)) + self.lastStatsTime = None + self.flowmods = {} + + api = SciPass(logger = self.logger, + config_file = self.CONF.SciPassConfig ) + + api.registerForwardingStateChangeHandler(self.changeSwitchForwardingState) + + self.api = api + + wsgi = kwargs['wsgi'] + wsgi.register(SciPassRest, {'api' : self.api}) + + + + + def changeSwitchForwardingState(self, dpid=None, header=None, actions=None, command=None, idle_timeout=None, hard_timeout=None, priority=1): + self.logger.debug("Changing switch forwarding state") + + if(not self.datapaths.has_key(dpid)): + self.logger.error("unable to find switch with dpid " + dpid) + self.logger.error(self.datapaths) + return + + datapath = self.datapaths[dpid] + + ofp = datapath.ofproto + parser = datapath.ofproto_parser + + obj = {} + + if(header.has_key('dl_type')): + if(header['dl_type'] == None): + obj['dl_type'] = None + else: + obj['dl_type'] = int(header['dl_type']) + else: + obj['dl_type'] = ether.ETH_TYPE_IP + + if(header.has_key('phys_port')): + obj['in_port'] = int(header['phys_port']) + else: + obj['in_port'] = None + + if(header.has_key('nw_src')): + obj['nw_src'] = int(header['nw_src']) + else: + obj['nw_src'] = None + + if(header.has_key('nw_src_mask')): + obj['nw_src_mask'] = int(header['nw_src_mask']) + else: + obj['nw_src_mask'] = None + + if(header.has_key('nw_dst')): + obj['nw_dst'] = int(header['nw_dst']) + else: + obj['nw_dst'] = None + + if(header.has_key('nw_dst_mask')): + obj['nw_dst_mask'] = int(header['nw_dst_mask']) + else: + obj['nw_dst_mask'] = None + + if(header.has_key('tp_src')): + obj['tp_src'] = int(header['tp_src']) + else: + obj['tp_src'] = None + + if(header.has_key('tp_dst')): + obj['tp_dst'] = int(header['tp_dst']) + else: + obj['tp_dst'] = None + + if(obj['dl_type'] == None): + match = parser.OFPMatch( in_port = obj['in_port'], + nw_dst = obj['nw_dst'], + nw_dst_mask = obj['nw_dst_mask'], + nw_src = obj['nw_src'], + nw_src_mask = obj['nw_src_mask'], + tp_src = obj['tp_src'], + tp_dst = obj['tp_dst']) + else: + + match = parser.OFPMatch( in_port = obj['in_port'], + nw_dst = obj['nw_dst'], + nw_dst_mask = obj['nw_dst_mask'], + nw_src = obj['nw_src'], + nw_src_mask = obj['nw_src_mask'], + dl_type = obj['dl_type'], + tp_src = obj['tp_src'], + tp_dst = obj['tp_dst']) + + self.logger.debug("Match: " + str(match)) + + of_actions = [] + for action in actions: + if(action['type'] == "output"): + of_actions.append(parser.OFPActionOutput(int(action['port']),0)) + + self.logger.debug("Actions: " + str(of_actions)) + if(command == "ADD"): + command = ofp.OFPFC_ADD + elif(command == "DELETE_STRICT"): + command = ofp.OFPFC_DELETE_STRICT + else: + command = -1 + + self.logger.debug("Sending flow mod with command: " + str(command)) + self.logger.debug("Datpath: " + str(datapath)) + + mod = parser.OFPFlowMod( datapath = datapath, + priority = int(priority), + match = match, + cookie = 0, + command = command, + idle_timeout = int(idle_timeout), + hard_timeout = int(hard_timeout), + actions = of_actions) + + if(datapath.is_active == True): + datapath.send_msg(mod) + + def flushRules(self, dpid): + if(not self.datapaths.has_key(dpid)): + self.logger.error("unable to find switch with dpid " + dpid) + return + + datapath = self.datapaths[dpid] + ofp = datapath.ofproto + parser = datapath.ofproto_parser + + # --- create flowmod to control traffic from the prefix to the interwebs + match = parser.OFPMatch() + mod = parser.OFPFlowMod(datapath,match,0,ofp.OFPFC_DELETE) + + #--- remove mods in the flowmod cache + self.flowmods[dpid] = [] + + + #--- if dp is active then push the rules + if(datapath.is_active == True): + datapath.send_msg(mod) + + def synchRules(self, dpid): + #--- routine to syncronize the rules to the DP + #--- currently just pushes, somday should diff + + #--- yep thats a hack, need to think about what multiple switches means for scipass + if(not self.datapaths.has_key(dpid)): + self.logger.error("unable to find switch with dpid " + dpid) + return + + datapath = self.datapaths[dpid] + if(datapath.is_active == True): + for mod in self.flowmods: + datapath.send_msg(mod) + + def _stats_loop(self): + while 1: + #--- send stats request + for dp in self.datapaths.values(): + self._request_stats(dp) + + #--- sleep + hub.sleep(self.statsInterval) + + def _balance_loop(self): + while 1: + self.logger.debug("here!!") + #--- tell the system to rebalance + self.api.run_balancers() + #--- sleep + hub.sleep(self.balanceInterval) + + def _request_stats(self,datapath): + ofp = datapath.ofproto + parser = datapath.ofproto_parser + + cookie = cookie_mask = 0 + match = parser.OFPMatch() + req = parser.OFPFlowStatsRequest( datapath, + 0, + match, + #ofp.OFPTT_ALL, + 0xff, + ofp.OFPP_NONE) + #0xffffffff, + #cookie, + #cookie_mask, + #match) + datapath.send_msg(req) + + req = parser.OFPPortStatsRequest(datapath, 0, ofp.OFPP_NONE) + datapath.send_msg(req) + + #handle the remove flow event so we know what to sync up when we do this + @set_ev_cls(ofp_event.EventOFPFlowRemoved, MAIN_DISPATCHER) + def _remove_flow_handler(self, ev): + msg = ev.msg + self.api.remove_flow(msg) + for flow in self.flows: + if(flow.match == msg.match and flow.actions == msg.actions): + self.flows.delete(flow) + return + self.logger.error("A flow was removed but we didn't know it was there!") + + @set_ev_cls(ofp_event.EventOFPStateChange, + [MAIN_DISPATCHER, DEAD_DISPATCHER]) + def _state_change_handler(self, ev): + datapath = ev.datapath + if ev.state == MAIN_DISPATCHER: + if not datapath.id in self.datapaths: + self.logger.debug('register datapath: %016x', datapath.id) + dpid = "%016x" % datapath.id + self.datapaths[dpid] = datapath + if(not self.flowmods.has_key(dpid)): + self.flowmods[dpid] = [] + self.flushRules("%016x" % datapath.id) + #--- start the balancing act + self.api.switchJoined(datapath) + + elif ev.state == DEAD_DISPATCHER: + if datapath.id in self.datapaths: + self.logger.debug('unregister datapath: %016x', datapath.id) + del self.datapaths[datapath.id] + + @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) + def _port_status_handler(self, ev): + msg = ev.msg + reason = msg.reason + port_no = msg.desc.port_no + link_state = msg.desc.state + + ofproto = msg.datapath.ofproto + if reason == ofproto.OFPPR_ADD: + self.logger.info("port added %s", port_no) + elif reason == ofproto.OFPPR_DELETE: + self.logger.info("port deleted %s", port_no) + elif reason == ofproto.OFPPR_MODIFY: + self.logger.info("port modified %s state %s", port_no,link_state) + #--- need to check the state to see if port is down + + else: + self.logger.info("Illeagal port state %s %s", port_no, reason) + + + @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) + def _flow_stats_reply_handler(self, ev): + + #--- figure out the time since last stats + old_time = self.lastStatsTime + self.lastStatsTime = int(time.time()) + stats_et = None + if(old_time != None): + stats_et = self.lastStatsTime - old_time + + body = ev.msg.body + + ofproto = ev.msg.datapath.ofproto + flows = [] + prefix_bps = defaultdict(lambda: defaultdict(int)) + #--- update scipass utilization stats for forwarding rules + + for stat in body: + dur_sec = stat.duration_sec + in_port = stat.match.in_port + src_mask = 32 - ((stat.match.wildcards & ofproto.OFPFW_NW_SRC_MASK) >> ofproto.OFPFW_NW_SRC_SHIFT) + dst_mask = 32 - ((stat.match.wildcards & ofproto.OFPFW_NW_DST_MASK) >> ofproto.OFPFW_NW_DST_SHIFT) + + if(src_mask > 0): + #--- this is traffic TX from target prefix + id = ipaddr.IPv4Address(stat.match.nw_src) + prefix = ipaddr.IPv4Network(str(id)+"/"+str(src_mask)) + dir = "tx" + + elif(dst_mask > 0): + #--- this is traffic RX from target prefix + id = ipaddr.IPv4Address(stat.match.nw_dst) + prefix = ipaddr.IPv4Network(str(id)+"/"+str(dst_mask)) + dir = "rx" + else: + #--- no mask, lets skip + continue + + raw_bytes = stat.byte_count + old_bytes = self.prefix_bytes[prefix][dir] + self.prefix_bytes[prefix][dir] = raw_bytes + bytes = raw_bytes - old_bytes + et = stats_et + if(et == None or dur_sec < et): + et = dur_sec + + try: + rate = bytes / float(et) + except ZeroDivisionError: + rate = 0 + + prefix_bps[prefix][dir] = rate + + match = stat.match.__dict__ + wildcards = stat.match.wildcards + del match['dl_dst'] + del match['dl_src'] + del match['dl_type'] + del match['wildcards'] + + if(match['dl_vlan_pcp'] == 0): + del match['dl_vlan_pcp'] + + if(match['dl_vlan'] == 0): + del match['dl_vlan'] + + if(match['nw_proto'] == 0): + del match['nw_proto'] + + if(match['nw_tos'] == 0): + del match['nw_tos'] + + if(match['nw_src'] == 0): + del match['nw_src'] + + if(match['nw_dst'] == 0): + del match['nw_dst'] + + if(match['tp_src'] == 0): + del match['tp_src'] + + if(match['tp_dst'] == 0): + del match['tp_dst'] + + if(match['in_port'] == 0): + del match['in_port'] + else: + match['phys_port'] = int(match['in_port']) + del match['in_port'] + + mask = 32 - ((wildcards & ofproto_v1_0.OFPFW_NW_SRC_MASK) + >> ofproto_v1_0.OFPFW_NW_SRC_SHIFT) + match['nw_src_mask'] = mask + + mask = 32 - ((wildcards & ofproto_v1_0.OFPFW_NW_DST_MASK) + >> ofproto_v1_0.OFPFW_NW_DST_SHIFT) + match['nw_dst_mask'] = mask + + flows.append({'match': match, + 'wildcards': wildcards, + 'packet_count': stat.packet_count + }) + + #--- update the balancer + for prefix in prefix_bps.keys(): + rx = prefix_bps[prefix]["rx"] + tx = prefix_bps[prefix]["tx"] + self.api.updatePrefixBW("%016x" % ev.msg.datapath.id, prefix, tx, rx) + + self.api.TimeoutFlows("%016x" % ev.msg.datapath.id, flows) + + @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) + def _port_stats_reply_handler(self, ev): + body = ev.msg.body + + #--- update scipass utilization stats for ports + + #self.logger.info('datapath port ' + # 'rx-pkts rx-bytes rx-error ' + # 'tx-pkts tx-bytes tx-error') + #self.logger.info('---------------- -------- ' + # '-------- -------- -------- ' + # '-------- -------- --------') + #for stat in sorted(body, key=attrgetter('port_no')): + # self.logger.info('%016x %8x %8d %8d %8d %8d %8d %8d', + # ev.msg.datapath.id, stat.port_no, + # stat.rx_packets, stat.rx_bytes, stat.rx_errors, + # stat.tx_packets, stat.tx_bytes, stat.tx_errors) + From 0a775ebeb1b412ee4435a08e78d7c58e28979cb0 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Sat, 15 Nov 2014 14:49:50 -0800 Subject: [PATCH 21/81] Added OpeyDaylight module --- python/OpenDaylight.py | 369 ++++++++++++++++++++++++ python/SciPassRest.py | 511 +++++++++++++++++++++++++++++++++ python/SciPass_start.py | 513 ++++++++++++++++++++++++++++++++++ python/t/test-OpenDaylight.py | 295 +++++++++++++++++++ 4 files changed, 1688 insertions(+) create mode 100644 python/OpenDaylight.py create mode 100644 python/SciPassRest.py create mode 100644 python/SciPass_start.py create mode 100644 python/t/test-OpenDaylight.py diff --git a/python/OpenDaylight.py b/python/OpenDaylight.py new file mode 100644 index 0000000..d6e9e1c --- /dev/null +++ b/python/OpenDaylight.py @@ -0,0 +1,369 @@ +""" +OpenDaylight REST API + +Copyright 2013 The University of Wisconsin Board of Regents + + Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + + 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. + + + Written by: Dale W. Carder, dwcarder@wisc.edu + Network Services Group + Division of Information Technology + University of Wisconsin at Madison + +This material is based upon work supported by the National Science Foundation +under Grant No. 1247322. +""" + +from __future__ import print_function +import json +import requests +from requests.auth import HTTPBasicAuth + +class OpenDaylight(object): + """An object holding details to talk to the OpenDaylight REST API + + OpenDaylight.setup is a dictionary loaded with the following + default values: + {'hostname':'localhost', + 'port':'8080', + 'username':'admin', + 'password':'admin', + 'path':'/controller/nb/v2/', + 'container':'default', + 'http':'http://' } + + Your code should change these as required for your installation. + OpenDaylight.url holds the url for each REST query. Typically + you would let OpenDaylight.prepare() build this for you. + + OpenDaylight.auth holds an auth object for Requests to use + for each REST query. Typically you would also let + OpenDaylight.prepare() build this for you. + """ + + def __init__(self): + """Set some mostly reasonable defaults. + """ + self.setup = {'hostname':'localhost', + 'port':'8080', + 'username':'admin', + 'password':'admin', + 'path':'/controller/nb/v2/', + 'container':'default', + 'http':'http://'} + + self._base_url = None + self.url = None + self.auth = None + + def prepare(self, app, path): + """Sets up the necessary details for the REST connection by calling + prepare_url and prepare_auth. + + Arguments: + 'app' - which OpenDaylight northbound api component (application) + we want to talk to. + 'path' - the specific rest query for the application. + """ + self.prepare_url(app, path) + self.prepare_auth() + + def prepare_url(self, app, path): + """Build the URL for this REST connection which is then stored as + OpenDaylight.url + + If you use prepare(), you shouldn't need to call prepare_url() + yourself. However, if there were a URL you wanted to construct that + was so whacked out custom, then by all means build it yourself and don't + bother to call this function. + + Arguments: + 'app' - which OpenDaylight northbound api component (application) + we want to talk to. + 'path' - the specific rest query for the application. + + Note that other attributes, including 'container' are specified + in the OpenDaylight.setup dictionary. + """ + + # the base url we will use for the connection + self._base_url = self.setup['http'] + self.setup['hostname'] + ':' + \ + self.setup['port'] + self.setup['path'] + + # the specific path we are building + self.url = self._base_url + app + '/' + self.setup['container'] + path + + def prepare_auth(self): + """Set up the credentials for the REST connection by creating + an auth object for Requests and shoving it into OpenDaylight.auth + + Currently, as far as I know, the OpenDaylight controller uses + http basic auth. If/when that changes this function should be + updated. + + If you use prepare(), you shouldn't need to call prepare_auth() + yourself. However, if there were something you wanted to do + that was so whacked out custom, then by all means build it yourself + and don't bother to call this function. + """ + + # stuff an HTTPBasicAuth object in here ready for use + self.auth = HTTPBasicAuth(self.setup['username'], + self.setup['password']) + #print("Prepare set up auth: " + self.setup['username'] + ', ' + \ + # self.setup['password']) + + +class OpenDaylightFlow(object): + """OpenDaylightFlow is an object that talks to the OpenDaylight + Flow Programmer application REST API + + OpenDaylight.odl holds an OpenDaylight object containing details + on how to communicate with the controller. + + OpenDaylightFlow.request holds a Requests object for the REST + session. Take a look at the Requests documentation for all of + the methods available, but here are a few handy examples: + OpenDaylightFlow.request.status_code - returns the http code + OpenDaylightFlow.request.text - returns the response as text + + OpenDaylightFlow.flows holds a dictionary that corresponds to + the flowConfig element in the OpenDaylight REST API. Note that + we don't statically define what those fields are here in this + object. This makes this library code more flexible as flowConfig + changes over time. After all, this is REST, not RPC. + """ + + def __init__(self, odl): + """Mandatory argument: + odl - an OpenDaylight object + """ + self.odl = odl + self.__app = 'flow' + self.request = None + self.flows = None + + def get(self, node_id=None, flow_name=None): + """Get Flows specified on the Controller and stuffs the results into + the OpenDaylightFlow.flows dictionary. + + Optional Arguments: + node_id - returns flows just for that switch dpid + flow_name - returns the specifically named flow on that switch + """ + + # clear out any remaining crud from previous calls + if hasattr(self, 'request'): + del self.request + if hasattr(self, 'flows'): + del self.flows + + if node_id is None: + self.odl.prepare(self.__app, '/') + elif flow_name is None: + self.odl.prepare(self.__app, '/' + 'OF/' + node_id + '/') + else: + self.odl.prepare(self.__app, '/' + 'OF/' + node_id + '/' + + flow_name + '/') + + self.request = requests.get(url=self.odl.url, auth=self.odl.auth) + + if self.request.status_code == 200: + self.flows = self.request.json() + if 'flowConfig' in self.flows: + self.flows = self.flows.get('flowConfig') + else: + raise OpenDaylightError({'url':self.odl.url, + 'http_code':self.request.status_code, + 'msg':self.request.text}) + + + def add(self, flow): + """Given a dictionary corresponding to a flowConfig, add this flow to + the Controller. Note that the switch dpid and the flow's name is + specified in the flowConfig passed in. + """ + if hasattr(self, 'request'): + del self.request + #print(flow) + self.odl.prepare(self.__app, '/' + flow['node']['@type'] + '/' + + flow['node']['@id'] + '/' + flow['name'] + '/') + headers = {'Content-type': 'application/json'} + body = json.dumps(flow) + self.request = requests.post(url=self.odl.url, auth=self.odl.auth, + data=body, headers=headers) + + if self.request.status_code != 201: + raise OpenDaylightError({'url':self.odl.url, + 'http_code':self.request.status_code, + 'msg':self.request.text}) + + #def update(self): + # """Update a flow to a Node on the Controller + # """ + # raise NotImplementedError("update()") + + def delete(self, node_id, flow_name): + """Delete a flow to a Node on the Controller + + Mandatory Arguments: + node_id - the switch dpid + flow_name - the specifically named flow on that switch + """ + if hasattr(self, 'request'): + del self.request + + self.odl.prepare(self.__app, '/' + 'OF/' + node_id + '/' + + flow_name + '/') + self.request = requests.delete(url=self.odl.url, auth=self.odl.auth) + + # note, if you wanted to pass in a flowConfig style dictionary, + # this is how you would do it. This is what I did initially, but + # it seemed clunky to pass in an entire flow. + #self.prepare(self.__app, '/' + flow['node']['@type'] + '/' + + # flow['node']['@id'] + '/' + flow['name'] + '/') + + if self.request.status_code != 200: + raise OpenDaylightError({'url':self.odl.url, + 'http_code':self.request.status_code, + 'msg':self.request.text}) + + +#pylint: disable=R0921 +class OpenDaylightNode(object): + """A way to talk to the OpenDaylight Switch Manager REST API + + OpenDaylight.odl holds an OpenDaylight object containing details + on how to communicate with the controller. + + OpenDaylightNode.request holds a Requests object for the REST + session. Take a look at the Requests documentation for all of + the methods available, but here are a few handy examples: + OpenDaylightNode.request.status_code - returns the http code + OpenDaylightNode.request.text - returns the response as text + + OpenDaylightNode.nodes holds a dictionary that corresponds to + the 'nodes' element in the OpenDaylight REST API. + + OpenDaylightNode.node_connectors holds a dictionary that corresponds to + the 'nodeConnectors' element in the OpenDaylight REST API. + + Note that we don't statically define what those fields are contained + in the 'nodes' or 'nodeConnectors' elements here in this object. + """ + + # Just a note that there are more functions available on + # the controller that could be implemented, but it is not + # clear at this time if that is useful + + def __init__(self, odl): + """Mandatory argument: + odl - an OpenDaylight object + """ + self.odl = odl + self.__app = 'switch' + self.nodes = None + self.node_connectors = None + self.request = None + + def get_nodes(self): + """Get information about Nodes on the Controller and stuffs the + result into the OpenDaylightNode.notes dictionary. + + """ + if hasattr(self, 'request'): + del self.request + if hasattr(self, 'nodes'): + del self.nodes + + self.odl.prepare(self.__app, '/nodes/') + self.request = requests.get(url=self.odl.url, auth=self.odl.auth) + + if self.request.status_code == 200: + self.nodes = self.request.json() + if 'nodeProperties' in self.nodes: + self.nodes = self.nodes.get('nodeProperties') + else: + raise OpenDaylightError({'url':self.odl.url, + 'http_code':self.request.status_code, + 'msg':self.request.text}) + + def get_node_connectors(self, node_id): + """Get information about NodeConnectors on the Controller and stuffs the + result into the OpenDaylightNode.node_connectors dictionary. + + Mandatory Arguments: + node_id - returns flows just for that switch dpid + """ + + if hasattr(self, 'request'): + del self.request + if hasattr(self, 'node_connectors'): + del self.node_connectors + + self.odl.prepare(self.__app, '/node/' + 'OF/' + node_id + '/') + self.request = requests.get(url=self.odl.url, auth=self.odl.auth) + if self.request.status_code == 200: + self.node_connectors = self.request.json() + if 'nodeConnectorProperties' in self.node_connectors: + self.node_connectors = self.node_connectors.get( + 'nodeConnectorProperties') + else: + raise OpenDaylightError({'url':self.odl.url, + 'http_code':self.request.status_code, + 'msg':self.request.text}) + + def save(self): + """Save current switch configurations + + The REST API documentation says: + "Save the current switch configurations", but I am not sure what + that actually means. If you think you do, then here you go. + """ + + if hasattr(self, 'request'): + del self.request + + self.odl.prepare(self.__app, '/switch-config/') + self.request = requests.post(url=self.odl.url, auth=self.odl.auth) + if self.request.status_code != 200: + raise OpenDaylightError({'url':self.odl.url, + 'http_code':self.request.status_code, + 'msg':self.request.text}) + + def delete_node_property(self): + """Delete a property of a Node on the Controller + """ + raise NotImplementedError("delete_node_property()") + + def add_node_property(self): + """Add a property of a Node on the Controller + """ + raise NotImplementedError("add_node_property()") + + def delete_node_connector_property(self): + """Delete a property of a Node on the Controller + """ + raise NotImplementedError("delete_node_connector_property()") + + def add_node_connector_property(self): + """Add a property of a Node on the Controller + """ + raise NotImplementedError("add_node_connector_property()") + + +class OpenDaylightError(Exception): + """OpenDaylight Exception Class + """ + pass diff --git a/python/SciPassRest.py b/python/SciPassRest.py new file mode 100644 index 0000000..bd0c1e8 --- /dev/null +++ b/python/SciPassRest.py @@ -0,0 +1,511 @@ +# Copyright (C) 2014 Hewlett-Packard Development Company, L.P +# +# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 +# +# 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 logging +import struct +import time +import SciPassRest + +import json + + + + +class SciPassRest(ControllerBase): + def __init__(self, req, link, data, **config): + super(SciPassRest, self).__init__(req, link, data, **config) + self.api = data['api'] + + #POST /scipass/flows/good_flow + @route('scipass', '/scipass/flows/good_flow', methods=['PUT']) + def good_flow(self, req): + try: + obj = eval(req.body) + except SyntaxError: + self.logger.error("Syntax Error processing good_flow signal %s", req.body) + return Response(status=400) + + result = self.api.good_flow(obj) + return Response(content_type='application/json',body=json.dumps(result)) + + #POST /scipass/flows/bad_flow + @route('scipass', '/scipass/flows/bad_flow', methods=['PUT']) + def bad_flow(self, req): + try: + obj = eval(req.body) + except SyntaxError: + self.logger.error("Syntax Error processing bad_flow signal %s", req.body) + return Response(status=400) + result = self.api.bad_flow(obj) + return Response(content_type='application/json',body=json.dumps(result)) + + #GET /scipass/flows/get_good_flows + @route('scipass', '/scipass/flows/get_good_flows', methods=['GET']) + def get_good_flows(self, req): + result = self.api.get_good_flows() + return Response(content_type='application/json',body=json.dumps(result)) + + #GET /scipass/flows/get_bad_flows + @route('scipass', '/scipass/flows/get_bad_flows', methods=['GET']) + def get_bad_flows(self, req): + result = self.api.get_bad_flows() + return Response(content_type='application/json',body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/flows', methods=['GET'], requirements= {'dpid': dpid_lib.DPID_PATTERN}) + def get_switch_flows(self, req, **kwargs): + result = self.api.getSwitchFlows(dpid=kwargs['dpid']) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/sensor/load', methods=['PUT']) + def update_sensor_load(self, req): + try: + obj = eval(req.body) + except SyntaxError: + self.logger.error("Syntax Error processing update_sensor_status signal %s", req.body) + return Response(status=400) + result = self.api.setSensorStatus(obj['sensor_id'],obj['load']) + return Response(content_type='application/json',body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensor/{sensor_id}', methods=['GET'], requirements= {'dpid': dpid_lib.DPID_PATTERN}) + def get_sensor_load(self, req, **kwargs): + result = self.api.getSensorStatus(dpid=kwargs['dpid'], domain=kwargs['domain'], sensor_id=kwargs['sensor_id']) + return Response(content_type='application/json',body=json.dumps(result)) + + @route('scipass', '/scipass/switches', methods=['GET']) + def get_switches(self, req): + result = self.api.getSwitches() + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensors', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) + def get_domain_sensors(self, req, **kwargs): + result = self.api.getDomainSensors(dpid = kwargs['dpid'], domain = kwargs['domain'] ) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domains', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) + def get_switch_domains(self, req, **kwargs): + result = self.api.getSwitchDomains(dpid=kwargs['dpid']) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) + def get_domain_status(self, req, **kwargs): + result = self.api.getDomainStatus(dpid = kwargs['dpid'], domain = kwargs['domain']) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/flows',methods=['GET'],requirements = {'dpid': dpid_lib.DPID_PATTERN}) + def get_domain_flows(self,req, **kwargs): + result = self.api.getDomainFlows(dpid = kwargs['dpid'], domain = kwargs['domain']) + return Response(content_type='application/json', body=json.dumps(result)) + + +class ODL(): + def __init__(self, *args, **kwargs): + #--- register for configuration options + self.CONF.register_opts([ + cfg.StrOpt('SciPassConfig',default='/etc/SciPass/SciPass.xml', + help='where to find the SciPass config file'), + ]) + + self.logger.error("Starting SciPass") + self.datapaths = {} + self.isactive = 1 + self.statsInterval = 5 + self.balanceInterval = 15 + self.bal = None + + #--- ODL Specifics + self.odl_host = "127.0.0.1" + self.odl_port = "8080" + + self.stats_thread = hub.spawn(self._stats_loop) + self.balance_thread = hub.spawn(self._balance_loop) + + self.ports = defaultdict(dict); + self.prefix_bytes = defaultdict(lambda: defaultdict(int)) + self.lastStatsTime = None + self.flowmods = {} + + api = SciPass(logger = self.logger, + config_file = self.CONF.SciPassConfig ) + + api.registerForwardingStateChangeHandler(self.changeSwitchForwardingState) + + self.api = api + + wsgi = kwargs['wsgi'] + wsgi.register(SciPassRest, {'api' : self.api}) + + + + + def changeSwitchForwardingState(self, dpid=None, header=None, actions=None, command=None, idle_timeout=None, hard_timeout=None, priority=1): + self.logger.debug("Changing switch forwarding state") + + if(not self.datapaths.has_key(dpid)): + self.logger.error("unable to find switch with dpid " + dpid) + self.logger.error(self.datapaths) + return + + datapath = self.datapaths[dpid] + + ofp = datapath.ofproto + parser = datapath.ofproto_parser + + obj = {} + + if(header.has_key('dl_type')): + if(header['dl_type'] == None): + obj['dl_type'] = None + else: + obj['dl_type'] = int(header['dl_type']) + else: + obj['dl_type'] = ether.ETH_TYPE_IP + + if(header.has_key('phys_port')): + obj['in_port'] = int(header['phys_port']) + else: + obj['in_port'] = None + + if(header.has_key('nw_src')): + obj['nw_src'] = int(header['nw_src']) + else: + obj['nw_src'] = None + + if(header.has_key('nw_src_mask')): + obj['nw_src_mask'] = int(header['nw_src_mask']) + else: + obj['nw_src_mask'] = None + + if(header.has_key('nw_dst')): + obj['nw_dst'] = int(header['nw_dst']) + else: + obj['nw_dst'] = None + + if(header.has_key('nw_dst_mask')): + obj['nw_dst_mask'] = int(header['nw_dst_mask']) + else: + obj['nw_dst_mask'] = None + + if(header.has_key('tp_src')): + obj['tp_src'] = int(header['tp_src']) + else: + obj['tp_src'] = None + + if(header.has_key('tp_dst')): + obj['tp_dst'] = int(header['tp_dst']) + else: + obj['tp_dst'] = None + + if(obj['dl_type'] == None): + match = parser.OFPMatch( in_port = obj['in_port'], + nw_dst = obj['nw_dst'], + nw_dst_mask = obj['nw_dst_mask'], + nw_src = obj['nw_src'], + nw_src_mask = obj['nw_src_mask'], + tp_src = obj['tp_src'], + tp_dst = obj['tp_dst']) + else: + + match = parser.OFPMatch( in_port = obj['in_port'], + nw_dst = obj['nw_dst'], + nw_dst_mask = obj['nw_dst_mask'], + nw_src = obj['nw_src'], + nw_src_mask = obj['nw_src_mask'], + dl_type = obj['dl_type'], + tp_src = obj['tp_src'], + tp_dst = obj['tp_dst']) + + self.logger.debug("Match: " + str(match)) + + of_actions = [] + for action in actions: + if(action['type'] == "output"): + of_actions.append(parser.OFPActionOutput(int(action['port']),0)) + + self.logger.debug("Actions: " + str(of_actions)) + if(command == "ADD"): + command = ofp.OFPFC_ADD + elif(command == "DELETE_STRICT"): + command = ofp.OFPFC_DELETE_STRICT + else: + command = -1 + + self.logger.debug("Sending flow mod with command: " + str(command)) + self.logger.debug("Datpath: " + str(datapath)) + + mod = parser.OFPFlowMod( datapath = datapath, + priority = int(priority), + match = match, + cookie = 0, + command = command, + idle_timeout = int(idle_timeout), + hard_timeout = int(hard_timeout), + actions = of_actions) + + if(datapath.is_active == True): + datapath.send_msg(mod) + + def flushRules(self, dpid): + if(not self.datapaths.has_key(dpid)): + self.logger.error("unable to find switch with dpid " + dpid) + return + + datapath = self.datapaths[dpid] + ofp = datapath.ofproto + parser = datapath.ofproto_parser + + # --- create flowmod to control traffic from the prefix to the interwebs + match = parser.OFPMatch() + mod = parser.OFPFlowMod(datapath,match,0,ofp.OFPFC_DELETE) + + #--- remove mods in the flowmod cache + self.flowmods[dpid] = [] + + + #--- if dp is active then push the rules + if(datapath.is_active == True): + datapath.send_msg(mod) + + def synchRules(self, dpid): + #--- routine to syncronize the rules to the DP + #--- currently just pushes, somday should diff + + #--- yep thats a hack, need to think about what multiple switches means for scipass + if(not self.datapaths.has_key(dpid)): + self.logger.error("unable to find switch with dpid " + dpid) + return + + datapath = self.datapaths[dpid] + if(datapath.is_active == True): + for mod in self.flowmods: + datapath.send_msg(mod) + + def _stats_loop(self): + while 1: + #--- send stats request + for dp in self.datapaths.values(): + self._request_stats(dp) + + #--- sleep + hub.sleep(self.statsInterval) + + def _balance_loop(self): + while 1: + self.logger.debug("here!!") + #--- tell the system to rebalance + self.api.run_balancers() + #--- sleep + hub.sleep(self.balanceInterval) + + def _request_stats(self,datapath): + ofp = datapath.ofproto + parser = datapath.ofproto_parser + + cookie = cookie_mask = 0 + match = parser.OFPMatch() + req = parser.OFPFlowStatsRequest( datapath, + 0, + match, + #ofp.OFPTT_ALL, + 0xff, + ofp.OFPP_NONE) + #0xffffffff, + #cookie, + #cookie_mask, + #match) + datapath.send_msg(req) + + req = parser.OFPPortStatsRequest(datapath, 0, ofp.OFPP_NONE) + datapath.send_msg(req) + + #handle the remove flow event so we know what to sync up when we do this + @set_ev_cls(ofp_event.EventOFPFlowRemoved, MAIN_DISPATCHER) + def _remove_flow_handler(self, ev): + msg = ev.msg + self.api.remove_flow(msg) + for flow in self.flows: + if(flow.match == msg.match and flow.actions == msg.actions): + self.flows.delete(flow) + return + self.logger.error("A flow was removed but we didn't know it was there!") + + @set_ev_cls(ofp_event.EventOFPStateChange, + [MAIN_DISPATCHER, DEAD_DISPATCHER]) + def _state_change_handler(self, ev): + datapath = ev.datapath + if ev.state == MAIN_DISPATCHER: + if not datapath.id in self.datapaths: + self.logger.debug('register datapath: %016x', datapath.id) + dpid = "%016x" % datapath.id + self.datapaths[dpid] = datapath + if(not self.flowmods.has_key(dpid)): + self.flowmods[dpid] = [] + self.flushRules("%016x" % datapath.id) + #--- start the balancing act + self.api.switchJoined(datapath) + + elif ev.state == DEAD_DISPATCHER: + if datapath.id in self.datapaths: + self.logger.debug('unregister datapath: %016x', datapath.id) + del self.datapaths[datapath.id] + + @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) + def _port_status_handler(self, ev): + msg = ev.msg + reason = msg.reason + port_no = msg.desc.port_no + link_state = msg.desc.state + + ofproto = msg.datapath.ofproto + if reason == ofproto.OFPPR_ADD: + self.logger.info("port added %s", port_no) + elif reason == ofproto.OFPPR_DELETE: + self.logger.info("port deleted %s", port_no) + elif reason == ofproto.OFPPR_MODIFY: + self.logger.info("port modified %s state %s", port_no,link_state) + #--- need to check the state to see if port is down + + else: + self.logger.info("Illeagal port state %s %s", port_no, reason) + + + @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) + def _flow_stats_reply_handler(self, ev): + + #--- figure out the time since last stats + old_time = self.lastStatsTime + self.lastStatsTime = int(time.time()) + stats_et = None + if(old_time != None): + stats_et = self.lastStatsTime - old_time + + body = ev.msg.body + + ofproto = ev.msg.datapath.ofproto + flows = [] + prefix_bps = defaultdict(lambda: defaultdict(int)) + #--- update scipass utilization stats for forwarding rules + + for stat in body: + dur_sec = stat.duration_sec + in_port = stat.match.in_port + src_mask = 32 - ((stat.match.wildcards & ofproto.OFPFW_NW_SRC_MASK) >> ofproto.OFPFW_NW_SRC_SHIFT) + dst_mask = 32 - ((stat.match.wildcards & ofproto.OFPFW_NW_DST_MASK) >> ofproto.OFPFW_NW_DST_SHIFT) + + if(src_mask > 0): + #--- this is traffic TX from target prefix + id = ipaddr.IPv4Address(stat.match.nw_src) + prefix = ipaddr.IPv4Network(str(id)+"/"+str(src_mask)) + dir = "tx" + + elif(dst_mask > 0): + #--- this is traffic RX from target prefix + id = ipaddr.IPv4Address(stat.match.nw_dst) + prefix = ipaddr.IPv4Network(str(id)+"/"+str(dst_mask)) + dir = "rx" + else: + #--- no mask, lets skip + continue + + raw_bytes = stat.byte_count + old_bytes = self.prefix_bytes[prefix][dir] + self.prefix_bytes[prefix][dir] = raw_bytes + bytes = raw_bytes - old_bytes + et = stats_et + if(et == None or dur_sec < et): + et = dur_sec + + try: + rate = bytes / float(et) + except ZeroDivisionError: + rate = 0 + + prefix_bps[prefix][dir] = rate + + match = stat.match.__dict__ + wildcards = stat.match.wildcards + del match['dl_dst'] + del match['dl_src'] + del match['dl_type'] + del match['wildcards'] + + if(match['dl_vlan_pcp'] == 0): + del match['dl_vlan_pcp'] + + if(match['dl_vlan'] == 0): + del match['dl_vlan'] + + if(match['nw_proto'] == 0): + del match['nw_proto'] + + if(match['nw_tos'] == 0): + del match['nw_tos'] + + if(match['nw_src'] == 0): + del match['nw_src'] + + if(match['nw_dst'] == 0): + del match['nw_dst'] + + if(match['tp_src'] == 0): + del match['tp_src'] + + if(match['tp_dst'] == 0): + del match['tp_dst'] + + if(match['in_port'] == 0): + del match['in_port'] + else: + match['phys_port'] = int(match['in_port']) + del match['in_port'] + + mask = 32 - ((wildcards & ofproto_v1_0.OFPFW_NW_SRC_MASK) + >> ofproto_v1_0.OFPFW_NW_SRC_SHIFT) + match['nw_src_mask'] = mask + + mask = 32 - ((wildcards & ofproto_v1_0.OFPFW_NW_DST_MASK) + >> ofproto_v1_0.OFPFW_NW_DST_SHIFT) + match['nw_dst_mask'] = mask + + flows.append({'match': match, + 'wildcards': wildcards, + 'packet_count': stat.packet_count + }) + + #--- update the balancer + for prefix in prefix_bps.keys(): + rx = prefix_bps[prefix]["rx"] + tx = prefix_bps[prefix]["tx"] + self.api.updatePrefixBW("%016x" % ev.msg.datapath.id, prefix, tx, rx) + + self.api.TimeoutFlows("%016x" % ev.msg.datapath.id, flows) + + @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) + def _port_stats_reply_handler(self, ev): + body = ev.msg.body + + #--- update scipass utilization stats for ports + + #self.logger.info('datapath port ' + # 'rx-pkts rx-bytes rx-error ' + # 'tx-pkts tx-bytes tx-error') + #self.logger.info('---------------- -------- ' + # '-------- -------- -------- ' + # '-------- -------- --------') + #for stat in sorted(body, key=attrgetter('port_no')): + # self.logger.info('%016x %8x %8d %8d %8d %8d %8d %8d', + # ev.msg.datapath.id, stat.port_no, + # stat.rx_packets, stat.rx_bytes, stat.rx_errors, + # stat.tx_packets, stat.tx_bytes, stat.tx_errors) + diff --git a/python/SciPass_start.py b/python/SciPass_start.py new file mode 100644 index 0000000..f4e117e --- /dev/null +++ b/python/SciPass_start.py @@ -0,0 +1,513 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2014 The Trustees of Indiana University +# Copyright (C) 2013 Hewlett-Packard Development Company, L.P +# +# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 +# +# 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 logging +import struct +import time + +import bottle +import json + + + + +class SciPassRest(ControllerBase): + def __init__(self, req, link, data, **config): + super(SciPassRest, self).__init__(req, link, data, **config) + self.api = data['api'] + + #POST /scipass/flows/good_flow + @route('scipass', '/scipass/flows/good_flow', methods=['PUT']) + def good_flow(self, req): + try: + obj = eval(req.body) + except SyntaxError: + self.logger.error("Syntax Error processing good_flow signal %s", req.body) + return Response(status=400) + + result = self.api.good_flow(obj) + return Response(content_type='application/json',body=json.dumps(result)) + + #POST /scipass/flows/bad_flow + @route('scipass', '/scipass/flows/bad_flow', methods=['PUT']) + def bad_flow(self, req): + try: + obj = eval(req.body) + except SyntaxError: + self.logger.error("Syntax Error processing bad_flow signal %s", req.body) + return Response(status=400) + result = self.api.bad_flow(obj) + return Response(content_type='application/json',body=json.dumps(result)) + + #GET /scipass/flows/get_good_flows + @route('scipass', '/scipass/flows/get_good_flows', methods=['GET']) + def get_good_flows(self, req): + result = self.api.get_good_flows() + return Response(content_type='application/json',body=json.dumps(result)) + + #GET /scipass/flows/get_bad_flows + @route('scipass', '/scipass/flows/get_bad_flows', methods=['GET']) + def get_bad_flows(self, req): + result = self.api.get_bad_flows() + return Response(content_type='application/json',body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/flows', methods=['GET'], requirements= {'dpid': dpid_lib.DPID_PATTERN}) + def get_switch_flows(self, req, **kwargs): + result = self.api.getSwitchFlows(dpid=kwargs['dpid']) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/sensor/load', methods=['PUT']) + def update_sensor_load(self, req): + try: + obj = eval(req.body) + except SyntaxError: + self.logger.error("Syntax Error processing update_sensor_status signal %s", req.body) + return Response(status=400) + result = self.api.setSensorStatus(obj['sensor_id'],obj['load']) + return Response(content_type='application/json',body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensor/{sensor_id}', methods=['GET'], requirements= {'dpid': dpid_lib.DPID_PATTERN}) + def get_sensor_load(self, req, **kwargs): + result = self.api.getSensorStatus(dpid=kwargs['dpid'], domain=kwargs['domain'], sensor_id=kwargs['sensor_id']) + return Response(content_type='application/json',body=json.dumps(result)) + + @route('scipass', '/scipass/switches', methods=['GET']) + def get_switches(self, req): + result = self.api.getSwitches() + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensors', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) + def get_domain_sensors(self, req, **kwargs): + result = self.api.getDomainSensors(dpid = kwargs['dpid'], domain = kwargs['domain'] ) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domains', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) + def get_switch_domains(self, req, **kwargs): + result = self.api.getSwitchDomains(dpid=kwargs['dpid']) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) + def get_domain_status(self, req, **kwargs): + result = self.api.getDomainStatus(dpid = kwargs['dpid'], domain = kwargs['domain']) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/flows',methods=['GET'],requirements = {'dpid': dpid_lib.DPID_PATTERN}) + def get_domain_flows(self,req, **kwargs): + result = self.api.getDomainFlows(dpid = kwargs['dpid'], domain = kwargs['domain']) + return Response(content_type='application/json', body=json.dumps(result)) + + +class ODL(): + def __init__(self, *args, **kwargs): + #--- register for configuration options + self.CONF.register_opts([ + cfg.StrOpt('SciPassConfig',default='/etc/SciPass/SciPass.xml', + help='where to find the SciPass config file'), + ]) + + self.logger.error("Starting SciPass") + self.datapaths = {} + self.isactive = 1 + self.statsInterval = 5 + self.balanceInterval = 15 + self.bal = None + + #--- ODL Specifics + self.odl_host = "127.0.0.1" + self.odl_port = "8080" + + self.stats_thread = hub.spawn(self._stats_loop) + self.balance_thread = hub.spawn(self._balance_loop) + + self.ports = defaultdict(dict); + self.prefix_bytes = defaultdict(lambda: defaultdict(int)) + self.lastStatsTime = None + self.flowmods = {} + + api = SciPass(logger = self.logger, + config_file = self.CONF.SciPassConfig ) + + api.registerForwardingStateChangeHandler(self.changeSwitchForwardingState) + + self.api = api + + wsgi = kwargs['wsgi'] + wsgi.register(SciPassRest, {'api' : self.api}) + + + + + def changeSwitchForwardingState(self, dpid=None, header=None, actions=None, command=None, idle_timeout=None, hard_timeout=None, priority=1): + self.logger.debug("Changing switch forwarding state") + + if(not self.datapaths.has_key(dpid)): + self.logger.error("unable to find switch with dpid " + dpid) + self.logger.error(self.datapaths) + return + + datapath = self.datapaths[dpid] + + ofp = datapath.ofproto + parser = datapath.ofproto_parser + + obj = {} + + if(header.has_key('dl_type')): + if(header['dl_type'] == None): + obj['dl_type'] = None + else: + obj['dl_type'] = int(header['dl_type']) + else: + obj['dl_type'] = ether.ETH_TYPE_IP + + if(header.has_key('phys_port')): + obj['in_port'] = int(header['phys_port']) + else: + obj['in_port'] = None + + if(header.has_key('nw_src')): + obj['nw_src'] = int(header['nw_src']) + else: + obj['nw_src'] = None + + if(header.has_key('nw_src_mask')): + obj['nw_src_mask'] = int(header['nw_src_mask']) + else: + obj['nw_src_mask'] = None + + if(header.has_key('nw_dst')): + obj['nw_dst'] = int(header['nw_dst']) + else: + obj['nw_dst'] = None + + if(header.has_key('nw_dst_mask')): + obj['nw_dst_mask'] = int(header['nw_dst_mask']) + else: + obj['nw_dst_mask'] = None + + if(header.has_key('tp_src')): + obj['tp_src'] = int(header['tp_src']) + else: + obj['tp_src'] = None + + if(header.has_key('tp_dst')): + obj['tp_dst'] = int(header['tp_dst']) + else: + obj['tp_dst'] = None + + if(obj['dl_type'] == None): + match = parser.OFPMatch( in_port = obj['in_port'], + nw_dst = obj['nw_dst'], + nw_dst_mask = obj['nw_dst_mask'], + nw_src = obj['nw_src'], + nw_src_mask = obj['nw_src_mask'], + tp_src = obj['tp_src'], + tp_dst = obj['tp_dst']) + else: + + match = parser.OFPMatch( in_port = obj['in_port'], + nw_dst = obj['nw_dst'], + nw_dst_mask = obj['nw_dst_mask'], + nw_src = obj['nw_src'], + nw_src_mask = obj['nw_src_mask'], + dl_type = obj['dl_type'], + tp_src = obj['tp_src'], + tp_dst = obj['tp_dst']) + + self.logger.debug("Match: " + str(match)) + + of_actions = [] + for action in actions: + if(action['type'] == "output"): + of_actions.append(parser.OFPActionOutput(int(action['port']),0)) + + self.logger.debug("Actions: " + str(of_actions)) + if(command == "ADD"): + command = ofp.OFPFC_ADD + elif(command == "DELETE_STRICT"): + command = ofp.OFPFC_DELETE_STRICT + else: + command = -1 + + self.logger.debug("Sending flow mod with command: " + str(command)) + self.logger.debug("Datpath: " + str(datapath)) + + mod = parser.OFPFlowMod( datapath = datapath, + priority = int(priority), + match = match, + cookie = 0, + command = command, + idle_timeout = int(idle_timeout), + hard_timeout = int(hard_timeout), + actions = of_actions) + + if(datapath.is_active == True): + datapath.send_msg(mod) + + def flushRules(self, dpid): + if(not self.datapaths.has_key(dpid)): + self.logger.error("unable to find switch with dpid " + dpid) + return + + datapath = self.datapaths[dpid] + ofp = datapath.ofproto + parser = datapath.ofproto_parser + + # --- create flowmod to control traffic from the prefix to the interwebs + match = parser.OFPMatch() + mod = parser.OFPFlowMod(datapath,match,0,ofp.OFPFC_DELETE) + + #--- remove mods in the flowmod cache + self.flowmods[dpid] = [] + + + #--- if dp is active then push the rules + if(datapath.is_active == True): + datapath.send_msg(mod) + + def synchRules(self, dpid): + #--- routine to syncronize the rules to the DP + #--- currently just pushes, somday should diff + + #--- yep thats a hack, need to think about what multiple switches means for scipass + if(not self.datapaths.has_key(dpid)): + self.logger.error("unable to find switch with dpid " + dpid) + return + + datapath = self.datapaths[dpid] + if(datapath.is_active == True): + for mod in self.flowmods: + datapath.send_msg(mod) + + def _stats_loop(self): + while 1: + #--- send stats request + for dp in self.datapaths.values(): + self._request_stats(dp) + + #--- sleep + hub.sleep(self.statsInterval) + + def _balance_loop(self): + while 1: + self.logger.debug("here!!") + #--- tell the system to rebalance + self.api.run_balancers() + #--- sleep + hub.sleep(self.balanceInterval) + + def _request_stats(self,datapath): + ofp = datapath.ofproto + parser = datapath.ofproto_parser + + cookie = cookie_mask = 0 + match = parser.OFPMatch() + req = parser.OFPFlowStatsRequest( datapath, + 0, + match, + #ofp.OFPTT_ALL, + 0xff, + ofp.OFPP_NONE) + #0xffffffff, + #cookie, + #cookie_mask, + #match) + datapath.send_msg(req) + + req = parser.OFPPortStatsRequest(datapath, 0, ofp.OFPP_NONE) + datapath.send_msg(req) + + #handle the remove flow event so we know what to sync up when we do this + @set_ev_cls(ofp_event.EventOFPFlowRemoved, MAIN_DISPATCHER) + def _remove_flow_handler(self, ev): + msg = ev.msg + self.api.remove_flow(msg) + for flow in self.flows: + if(flow.match == msg.match and flow.actions == msg.actions): + self.flows.delete(flow) + return + self.logger.error("A flow was removed but we didn't know it was there!") + + @set_ev_cls(ofp_event.EventOFPStateChange, + [MAIN_DISPATCHER, DEAD_DISPATCHER]) + def _state_change_handler(self, ev): + datapath = ev.datapath + if ev.state == MAIN_DISPATCHER: + if not datapath.id in self.datapaths: + self.logger.debug('register datapath: %016x', datapath.id) + dpid = "%016x" % datapath.id + self.datapaths[dpid] = datapath + if(not self.flowmods.has_key(dpid)): + self.flowmods[dpid] = [] + self.flushRules("%016x" % datapath.id) + #--- start the balancing act + self.api.switchJoined(datapath) + + elif ev.state == DEAD_DISPATCHER: + if datapath.id in self.datapaths: + self.logger.debug('unregister datapath: %016x', datapath.id) + del self.datapaths[datapath.id] + + @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) + def _port_status_handler(self, ev): + msg = ev.msg + reason = msg.reason + port_no = msg.desc.port_no + link_state = msg.desc.state + + ofproto = msg.datapath.ofproto + if reason == ofproto.OFPPR_ADD: + self.logger.info("port added %s", port_no) + elif reason == ofproto.OFPPR_DELETE: + self.logger.info("port deleted %s", port_no) + elif reason == ofproto.OFPPR_MODIFY: + self.logger.info("port modified %s state %s", port_no,link_state) + #--- need to check the state to see if port is down + + else: + self.logger.info("Illeagal port state %s %s", port_no, reason) + + + @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) + def _flow_stats_reply_handler(self, ev): + + #--- figure out the time since last stats + old_time = self.lastStatsTime + self.lastStatsTime = int(time.time()) + stats_et = None + if(old_time != None): + stats_et = self.lastStatsTime - old_time + + body = ev.msg.body + + ofproto = ev.msg.datapath.ofproto + flows = [] + prefix_bps = defaultdict(lambda: defaultdict(int)) + #--- update scipass utilization stats for forwarding rules + + for stat in body: + dur_sec = stat.duration_sec + in_port = stat.match.in_port + src_mask = 32 - ((stat.match.wildcards & ofproto.OFPFW_NW_SRC_MASK) >> ofproto.OFPFW_NW_SRC_SHIFT) + dst_mask = 32 - ((stat.match.wildcards & ofproto.OFPFW_NW_DST_MASK) >> ofproto.OFPFW_NW_DST_SHIFT) + + if(src_mask > 0): + #--- this is traffic TX from target prefix + id = ipaddr.IPv4Address(stat.match.nw_src) + prefix = ipaddr.IPv4Network(str(id)+"/"+str(src_mask)) + dir = "tx" + + elif(dst_mask > 0): + #--- this is traffic RX from target prefix + id = ipaddr.IPv4Address(stat.match.nw_dst) + prefix = ipaddr.IPv4Network(str(id)+"/"+str(dst_mask)) + dir = "rx" + else: + #--- no mask, lets skip + continue + + raw_bytes = stat.byte_count + old_bytes = self.prefix_bytes[prefix][dir] + self.prefix_bytes[prefix][dir] = raw_bytes + bytes = raw_bytes - old_bytes + et = stats_et + if(et == None or dur_sec < et): + et = dur_sec + + try: + rate = bytes / float(et) + except ZeroDivisionError: + rate = 0 + + prefix_bps[prefix][dir] = rate + + match = stat.match.__dict__ + wildcards = stat.match.wildcards + del match['dl_dst'] + del match['dl_src'] + del match['dl_type'] + del match['wildcards'] + + if(match['dl_vlan_pcp'] == 0): + del match['dl_vlan_pcp'] + + if(match['dl_vlan'] == 0): + del match['dl_vlan'] + + if(match['nw_proto'] == 0): + del match['nw_proto'] + + if(match['nw_tos'] == 0): + del match['nw_tos'] + + if(match['nw_src'] == 0): + del match['nw_src'] + + if(match['nw_dst'] == 0): + del match['nw_dst'] + + if(match['tp_src'] == 0): + del match['tp_src'] + + if(match['tp_dst'] == 0): + del match['tp_dst'] + + if(match['in_port'] == 0): + del match['in_port'] + else: + match['phys_port'] = int(match['in_port']) + del match['in_port'] + + mask = 32 - ((wildcards & ofproto_v1_0.OFPFW_NW_SRC_MASK) + >> ofproto_v1_0.OFPFW_NW_SRC_SHIFT) + match['nw_src_mask'] = mask + + mask = 32 - ((wildcards & ofproto_v1_0.OFPFW_NW_DST_MASK) + >> ofproto_v1_0.OFPFW_NW_DST_SHIFT) + match['nw_dst_mask'] = mask + + flows.append({'match': match, + 'wildcards': wildcards, + 'packet_count': stat.packet_count + }) + + #--- update the balancer + for prefix in prefix_bps.keys(): + rx = prefix_bps[prefix]["rx"] + tx = prefix_bps[prefix]["tx"] + self.api.updatePrefixBW("%016x" % ev.msg.datapath.id, prefix, tx, rx) + + self.api.TimeoutFlows("%016x" % ev.msg.datapath.id, flows) + + @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) + def _port_stats_reply_handler(self, ev): + body = ev.msg.body + + #--- update scipass utilization stats for ports + + #self.logger.info('datapath port ' + # 'rx-pkts rx-bytes rx-error ' + # 'tx-pkts tx-bytes tx-error') + #self.logger.info('---------------- -------- ' + # '-------- -------- -------- ' + # '-------- -------- --------') + #for stat in sorted(body, key=attrgetter('port_no')): + # self.logger.info('%016x %8x %8d %8d %8d %8d %8d %8d', + # ev.msg.datapath.id, stat.port_no, + # stat.rx_packets, stat.rx_bytes, stat.rx_errors, + # stat.tx_packets, stat.tx_bytes, stat.tx_errors) + diff --git a/python/t/test-OpenDaylight.py b/python/t/test-OpenDaylight.py new file mode 100644 index 0000000..0101227 --- /dev/null +++ b/python/t/test-OpenDaylight.py @@ -0,0 +1,295 @@ +#!/usr/bin/python +""" +Tests for the OpenDaylight REST API interface + +Copyright 2013 The University of Wisconsin Board of Regents + + Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + + 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. + + Written by: Dale W. Carder, dwcarder@wisc.edu + Network Services Group + Division of Information Technology + University of Wisconsin at Madison + +This material is based upon work supported by the National Science +Foundation under Grant No. 1247322 +""" + +import time +import unittest +sys.path.append(".") + +from OpenDaylight import OpenDaylight +from OpenDaylight import OpenDaylightFlow +from OpenDaylight import OpenDaylightNode +from OpenDaylight import OpenDaylightError +from mininet.net import Mininet +#from mininet.util import dumpNodeConnections +#from mininet.log import setLogLevel +from mininet.topo import Topo +from mininet.node import RemoteController + +# Edit these as necessary for your organization +CONTROLLER = '10.10.10.1' +USERNAME = 'admin' +PASSWORD = 'admin' +SWITCH_1 = '99:99:99:00:00:00:01:00' +# This is chosen so that it does not conflict with any other +# switches that may be associated to a controller that is not +# dedicated explicitly for testing + +class TestSequenceFunctions(unittest.TestCase): + """Tests for OpenDaylight + + At this point, tests for OpenDaylightFlow and OpenDaylightNode + are intermingled. These could be seperated out into seperate + suites. + """ + + def setUp(self): + odl = OpenDaylight() + odl.setup['hostname'] = CONTROLLER + odl.setup['username'] = USERNAME + odl.setup['password'] = PASSWORD + self.flow = OpenDaylightFlow(odl) + self.node = OpenDaylightNode(odl) + + self.switch_id_1 = SWITCH_1 + + self.odl_test_flow_1 = {u'actions': u'DROP', + u'etherType': u'0x800', + u'ingressPort': u'1', + u'installInHw': u'true', + u'name': u'odl-test-flow1', + u'node': {u'@id': self.switch_id_1, u'@type': u'OF'}, + u'priority': u'500'} + + self.odl_test_flow_2 = {u'actions': u'DROP', + u'etherType': u'0x800', + u'ingressPort': u'2', + u'installInHw': u'true', + u'name': u'odl-test-flow2', + u'node': {u'@id': self.switch_id_1, u'@type': u'OF'}, + u'priority': u'500'} + + + def test_01_delete_flows(self): + """Clean up from any previous test run, just delete these + flows if they exist. + """ + try: + self.flow.delete(self.odl_test_flow_1['node']['@id'], + self.odl_test_flow_1['name']) + except: + pass + + try: + self.flow.delete(self.odl_test_flow_2['node']['@id'], + self.odl_test_flow_2['name']) + except: + pass + + def test_10_add_flow(self): + """Add a sample flow onto the controller + """ + self.flow.add(self.odl_test_flow_1) + self.assertEqual(self.flow.request.status_code, 201) + + def test_10_add_flow2(self): + """Add a sample flow onto the controller + """ + self.flow.add(self.odl_test_flow_2) + self.assertEqual(self.flow.request.status_code, 201) + + def test_15_add_flow2(self): + """Add a duplicate flow onto the controller + """ + try: + self.flow.add(self.odl_test_flow_2) + except OpenDaylightError: + pass + except e: + self.fail('Unexpected exception thrown:', e) + else: + self.fail('Expected Exception not thrown') + + def test_20_get_flow(self): + """Retrieve the specific flow back from the controller + """ + self.flow.get(node_id=self.switch_id_1, flow_name='odl-test-flow1') + self.assertEqual(self.flow.flows, self.odl_test_flow_1) + self.assertEqual(self.flow.request.status_code, 200) + + def test_20_get_flow2(self): + """Retrieve the specific flow back from the controller + """ + self.flow.get(node_id=self.switch_id_1, flow_name='odl-test-flow1') + self.assertEqual(self.flow.flows, self.odl_test_flow_1) + self.assertEqual(self.flow.request.status_code, 200) + + + def test_30_get_all_switch_flows(self): + """Retrieve all flows from this switch back from the controller + """ + self.flow.get(node_id=self.switch_id_1) + self.assertTrue(self.odl_test_flow_1 in self.flow.flows) + self.assertTrue(self.odl_test_flow_2 in self.flow.flows) + self.assertEqual(self.flow.request.status_code, 200) + + def test_30_get_all_flows(self): + """Retrieve all flows back from the controller + """ + self.flow.get() + self.assertTrue(self.odl_test_flow_1 in self.flow.flows) + self.assertTrue(self.odl_test_flow_2 in self.flow.flows) + self.assertEqual(self.flow.request.status_code, 200) + + def test_30_get_flows_invalid_switch(self): + """Try to get a flow from a non-existant switch + """ + try: + # This dpid is specifically chosen figuring that it + # would not be in use in a production system. Plus, I + # simply just like the number 53. + self.flow.get(node_id='53:53:53:53:53:53:53:53') + except OpenDaylightError: + pass + except e: + self.fail('Unexpected exception thrown:', e) + else: + self.fail('Expected Exception not thrown') + + def test_40_get_flows_invalid_flowname(self): + """Try to get a flow that does not exist. + """ + try: + self.flow.get(node_id=self.switch_id_1, flow_name='foo-foo-foo-bar') + except OpenDaylightError: + pass + except e: + self.fail('Unexpected exception thrown:', e) + else: + self.fail('Expected Exception not thrown') + + def test_50_delete_flow(self): + """Delete flow 1. + """ + self.flow.delete(self.odl_test_flow_1['node']['@id'], + self.odl_test_flow_1['name']) + self.assertEqual(self.flow.request.status_code, 200) + + + def test_51_deleted_flow_get(self): + """Verify that the deleted flow does not exist. + """ + try: + self.flow.get(node_id=self.switch_id_1, flow_name='odl-test-flow1') + except OpenDaylightError: + pass + except e: + self.fail('Unexpected exception thrown:', e) + else: + self.fail('Expected Exception not thrown') + + def test_55_delete_flow2(self): + """Delete flow 2 + """ + self.flow.delete(self.odl_test_flow_2['node']['@id'], + self.odl_test_flow_2['name']) + self.assertEqual(self.flow.request.status_code, 200) + + + #TODO: Add invalid flow that has a bad port + #TODO: Add invalid flow that has a non-existant switch + #TODO: Add invalid flow that has an invalid switch name (non-hexadecimal), + # see https://bugs.opendaylight.org/show_bug.cgi?id=27 + + def test_60_get_all_nodes(self): + """Get all of the nodes on the controller + + TODO: verify that SWITCH_1 is contained in the response + """ + self.node.get_nodes() + self.assertEqual(self.node.request.status_code, 200) + + def test_60_get_node_connector(self): + """Retrieve a list of all the node connectors and their properties + in a given node + + TODO: verify that SWITCH_1 is contained in the response + """ + self.node.get_node_connectors(SWITCH_1) + self.assertEqual(self.node.request.status_code, 200) + + + def test_60_get_bad_node_connector(self): + """Retrieve a list of all the node connectors and their properties + in a given node for a node that does not exist + """ + try: + self.node.get_node_connectors('53:53:53:53:53:53:53:53') + except OpenDaylightError: + pass + except e: + self.fail('Unexpected exception thrown:', e) + else: + self.fail('Expected Exception not thrown') + + + def test_60_save(self): + """Save the switch configurations. + It's not clear that this can be easily tested, so we just + see if this call works or not based on the http status code. + """ + self.node.save() + self.assertEqual(self.node.request.status_code, 200) + + +class SingleSwitchTopo(Topo): + "Single switch connected to n hosts." + def __init__(self, n=2, **opts): + # Initialize topology and default options + Topo.__init__(self, **opts) + # mininet/ovswitch does not want ':'s in the dpid + switch_id = SWITCH_1.translate(None, ':') + switch = self.addSwitch('s1', dpid=switch_id) + # Python's range(N) generates 0..N-1 + for h in range(n): + host = self.addHost('h%s' % (h + 1)) + self.addLink(host, switch) + +def setup_mininet_simpleTest(): + "Create and test a simple network" + topo = SingleSwitchTopo(n=4) + #net = Mininet(topo) + net = Mininet( topo=topo, controller=lambda name: RemoteController( + name, ip=CONTROLLER ) ) + net.start() + #print "Dumping host connections" + #dumpNodeConnections(net.hosts) + + #time.sleep(300) + + #print "Testing network connectivity" + #net.pingAll() + #net.stop() + +if __name__ == '__main__': + # Tell mininet to print useful information + #setLogLevel('info') + + setup_mininet_simpleTest() + time.sleep(10) + unittest.main() + + From 87b7edc6619db83d78ad96f1a59c92625fda33f9 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 20 Nov 2014 13:02:29 -0800 Subject: [PATCH 22/81] Created HPVAN class --- python/HPVAN.py | 299 +++++++++++++++++++++++ python/SciPassRest.py | 511 ---------------------------------------- python/SciPass_start.py | 507 +-------------------------------------- 3 files changed, 311 insertions(+), 1006 deletions(-) create mode 100644 python/HPVAN.py delete mode 100644 python/SciPassRest.py diff --git a/python/HPVAN.py b/python/HPVAN.py new file mode 100644 index 0000000..bd35c6e --- /dev/null +++ b/python/HPVAN.py @@ -0,0 +1,299 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2014 The Trustees of Indiana University +# +# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 +# +# 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 logging +from bottle import run, route + +import oslo.config.cfg as cfg +import hpsdnclient as hp + +from SciPass import SciPass + +ETH_TYPE_IP = 0x0800 + + +""" + Forwarding rule Priorities + BlackList + WhiteList + Balancer + Default +""" + + +class SciPassRest(): + def __init__(self, api): + self.api = api + + #GET /scipass/test + @route('/scipass/test') + def test(self): + return "test" + @route('/scipass/flows') + def test(self): + flows = self.api.get_food_flows + return flows + + def run_server(self): + run('scipass',host='localhost', port=8080) + +class HPVAN(): + + def __init__(self): + + self.datapaths = {} + + # Set Logger + self.logger = logging.getLogger(__name__) + + #--- register for configuration options + self.CONF = cfg.CONF + self.CONF.register_opts([ + cfg.StrOpt('SciPassConfig',default='/etc/SciPass/SciPass.xml', + help='where to find the SciPass config file'), + ]) + + self.logger.error("Starting SciPass") + # + + + api = SciPass(logger = self.logger, + config_file = self.CONF.SciPassConfig ) + + api.registerForwardingStateChangeHandler(self.changeSwitchForwardingState) + + self.api = api + + # Start REST interface + SciPassRest(api).run_server() + + def connect_controller(self, controller, port, username, password): + + + self.logger.debug("Connecting to VAN controller" + controller) + + auth = hp.XAuthToken(user=username, password=password, server=controller) + api = hp.Api(controller=controller, auth=auth) + + def changeSwitchForwardingState(self, dpid=None, header=None, actions=None, command=None, idle_timeout=None, hard_timeout=None, priority=1): + self.logger.debug("Changing switch forwarding state") + + if(not self.datapaths.has_key(dpid)): + self.logger.error("unable to find switch with dpid " + dpid) + self.logger.error(self.datapaths) + return + + datapath = self.datapaths[dpid] + + ofp = datapath.ofproto + parser = datapath.ofproto_parser + + obj = {} + + if(header.has_key('dl_type')): + if(header['dl_type'] == None): + obj['dl_type'] = None + else: + obj['dl_type'] = int(header['dl_type']) + else: + obj['dl_type'] = ETH_TYPE_IP + + if(header.has_key('phys_port')): + obj['in_port'] = int(header['phys_port']) + else: + obj['in_port'] = None + + if(header.has_key('nw_src')): + obj['nw_src'] = int(header['nw_src']) + else: + obj['nw_src'] = None + + if(header.has_key('nw_src_mask')): + obj['nw_src_mask'] = int(header['nw_src_mask']) + else: + obj['nw_src_mask'] = None + + if(header.has_key('nw_dst')): + obj['nw_dst'] = int(header['nw_dst']) + else: + obj['nw_dst'] = None + + if(header.has_key('nw_dst_mask')): + obj['nw_dst_mask'] = int(header['nw_dst_mask']) + else: + obj['nw_dst_mask'] = None + + if(header.has_key('tp_src')): + obj['tp_src'] = int(header['tp_src']) + else: + obj['tp_src'] = None + + if(header.has_key('tp_dst')): + obj['tp_dst'] = int(header['tp_dst']) + else: + obj['tp_dst'] = None + + if(obj['dl_type'] == None): + match = parser.OFPMatch( in_port = obj['in_port'], + nw_dst = obj['nw_dst'], + nw_dst_mask = obj['nw_dst_mask'], + nw_src = obj['nw_src'], + nw_src_mask = obj['nw_src_mask'], + tp_src = obj['tp_src'], + tp_dst = obj['tp_dst']) + else: + + match = parser.OFPMatch( in_port = obj['in_port'], + nw_dst = obj['nw_dst'], + nw_dst_mask = obj['nw_dst_mask'], + nw_src = obj['nw_src'], + nw_src_mask = obj['nw_src_mask'], + dl_type = obj['dl_type'], + tp_src = obj['tp_src'], + tp_dst = obj['tp_dst']) + + self.logger.debug("Match: " + str(match)) + + of_actions = [] + for action in actions: + if(action['type'] == "output"): + of_actions.append(parser.OFPActionOutput(int(action['port']),0)) + + self.logger.debug("Actions: " + str(of_actions)) + if(command == "ADD"): + command = ofp.OFPFC_ADD + elif(command == "DELETE_STRICT"): + command = ofp.OFPFC_DELETE_STRICT + else: + command = -1 + + self.logger.debug("Sending flow mod with command: " + str(command)) + self.logger.debug("Datpath: " + str(datapath)) + + mod = parser.OFPFlowMod( datapath = datapath, + priority = int(priority), + match = match, + cookie = 0, + command = command, + idle_timeout = int(idle_timeout), + hard_timeout = int(hard_timeout), + actions = of_actions) + + if(datapath.is_active == True): + datapath.send_msg(mod) + + def flushRules(self, dpid): + if(not self.datapaths.has_key(dpid)): + self.logger.error("unable to find switch with dpid " + dpid) + return + + datapath = self.datapaths[dpid] + ofp = datapath.ofproto + parser = datapath.ofproto_parser + + # --- create flowmod to control traffic from the prefix to the interwebs + match = parser.OFPMatch() + mod = parser.OFPFlowMod(datapath,match,0,ofp.OFPFC_DELETE) + + #--- remove mods in the flowmod cache + self.flowmods[dpid] = [] + + + #--- if dp is active then push the rules + if(datapath.is_active == True): + datapath.send_msg(mod) + + def synchRules(self, dpid): + #--- routine to syncronize the rules to the DP + #--- currently just pushes, somday should diff + + #--- yep thats a hack, need to think about what multiple switches means for scipass + if(not self.datapaths.has_key(dpid)): + self.logger.error("unable to find switch with dpid " + dpid) + return + + datapath = self.datapaths[dpid] + if(datapath.is_active == True): + for mod in self.flowmods: + datapath.send_msg(mod) + + def _stats_loop(self): + while 1: + #--- send stats request + for dp in self.datapaths.values(): + self._request_stats(dp) + + #--- sleep + hub.sleep(self.statsInterval) + + def _balance_loop(self): + while 1: + self.logger.debug("here!!") + #--- tell the system to rebalance + self.api.run_balancers() + #--- sleep + hub.sleep(self.balanceInterval) + + def _request_stats(self,datapath): + ofp = datapath.ofproto + parser = datapath.ofproto_parser + + cookie = cookie_mask = 0 + match = parser.OFPMatch() + req = parser.OFPFlowStatsRequest( datapath, + 0, + match, + #ofp.OFPTT_ALL, + 0xff, + ofp.OFPP_NONE) + #0xffffffff, + #cookie, + #cookie_mask, + #match) + datapath.send_msg(req) + + req = parser.OFPPortStatsRequest(datapath, 0, ofp.OFPP_NONE) + datapath.send_msg(req) + + #handle the remove flow event so we know what to sync up when we do this + @set_ev_cls(ofp_event.EventOFPFlowRemoved, MAIN_DISPATCHER) + def _remove_flow_handler(self, ev): + msg = ev.msg + self.api.remove_flow(msg) + for flow in self.flows: + if(flow.match == msg.match and flow.actions == msg.actions): + self.flows.delete(flow) + return + self.logger.error("A flow was removed but we didn't know it was there!") + + + + + + #--- update scipass utilization stats for ports + + #self.logger.info('datapath port ' + # 'rx-pkts rx-bytes rx-error ' + # 'tx-pkts tx-bytes tx-error') + #self.logger.info('---------------- -------- ' + # '-------- -------- -------- ' + # '-------- -------- --------') + #for stat in sorted(body, key=attrgetter('port_no')): + # self.logger.info('%016x %8x %8d %8d %8d %8d %8d %8d', + # ev.msg.datapath.id, stat.port_no, + # stat.rx_packets, stat.rx_bytes, stat.rx_errors, + # stat.tx_packets, stat.tx_bytes, stat.tx_errors) + diff --git a/python/SciPassRest.py b/python/SciPassRest.py deleted file mode 100644 index bd0c1e8..0000000 --- a/python/SciPassRest.py +++ /dev/null @@ -1,511 +0,0 @@ -# Copyright (C) 2014 Hewlett-Packard Development Company, L.P -# -# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 -# -# 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 logging -import struct -import time -import SciPassRest - -import json - - - - -class SciPassRest(ControllerBase): - def __init__(self, req, link, data, **config): - super(SciPassRest, self).__init__(req, link, data, **config) - self.api = data['api'] - - #POST /scipass/flows/good_flow - @route('scipass', '/scipass/flows/good_flow', methods=['PUT']) - def good_flow(self, req): - try: - obj = eval(req.body) - except SyntaxError: - self.logger.error("Syntax Error processing good_flow signal %s", req.body) - return Response(status=400) - - result = self.api.good_flow(obj) - return Response(content_type='application/json',body=json.dumps(result)) - - #POST /scipass/flows/bad_flow - @route('scipass', '/scipass/flows/bad_flow', methods=['PUT']) - def bad_flow(self, req): - try: - obj = eval(req.body) - except SyntaxError: - self.logger.error("Syntax Error processing bad_flow signal %s", req.body) - return Response(status=400) - result = self.api.bad_flow(obj) - return Response(content_type='application/json',body=json.dumps(result)) - - #GET /scipass/flows/get_good_flows - @route('scipass', '/scipass/flows/get_good_flows', methods=['GET']) - def get_good_flows(self, req): - result = self.api.get_good_flows() - return Response(content_type='application/json',body=json.dumps(result)) - - #GET /scipass/flows/get_bad_flows - @route('scipass', '/scipass/flows/get_bad_flows', methods=['GET']) - def get_bad_flows(self, req): - result = self.api.get_bad_flows() - return Response(content_type='application/json',body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/flows', methods=['GET'], requirements= {'dpid': dpid_lib.DPID_PATTERN}) - def get_switch_flows(self, req, **kwargs): - result = self.api.getSwitchFlows(dpid=kwargs['dpid']) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/sensor/load', methods=['PUT']) - def update_sensor_load(self, req): - try: - obj = eval(req.body) - except SyntaxError: - self.logger.error("Syntax Error processing update_sensor_status signal %s", req.body) - return Response(status=400) - result = self.api.setSensorStatus(obj['sensor_id'],obj['load']) - return Response(content_type='application/json',body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensor/{sensor_id}', methods=['GET'], requirements= {'dpid': dpid_lib.DPID_PATTERN}) - def get_sensor_load(self, req, **kwargs): - result = self.api.getSensorStatus(dpid=kwargs['dpid'], domain=kwargs['domain'], sensor_id=kwargs['sensor_id']) - return Response(content_type='application/json',body=json.dumps(result)) - - @route('scipass', '/scipass/switches', methods=['GET']) - def get_switches(self, req): - result = self.api.getSwitches() - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensors', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) - def get_domain_sensors(self, req, **kwargs): - result = self.api.getDomainSensors(dpid = kwargs['dpid'], domain = kwargs['domain'] ) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domains', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) - def get_switch_domains(self, req, **kwargs): - result = self.api.getSwitchDomains(dpid=kwargs['dpid']) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) - def get_domain_status(self, req, **kwargs): - result = self.api.getDomainStatus(dpid = kwargs['dpid'], domain = kwargs['domain']) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/flows',methods=['GET'],requirements = {'dpid': dpid_lib.DPID_PATTERN}) - def get_domain_flows(self,req, **kwargs): - result = self.api.getDomainFlows(dpid = kwargs['dpid'], domain = kwargs['domain']) - return Response(content_type='application/json', body=json.dumps(result)) - - -class ODL(): - def __init__(self, *args, **kwargs): - #--- register for configuration options - self.CONF.register_opts([ - cfg.StrOpt('SciPassConfig',default='/etc/SciPass/SciPass.xml', - help='where to find the SciPass config file'), - ]) - - self.logger.error("Starting SciPass") - self.datapaths = {} - self.isactive = 1 - self.statsInterval = 5 - self.balanceInterval = 15 - self.bal = None - - #--- ODL Specifics - self.odl_host = "127.0.0.1" - self.odl_port = "8080" - - self.stats_thread = hub.spawn(self._stats_loop) - self.balance_thread = hub.spawn(self._balance_loop) - - self.ports = defaultdict(dict); - self.prefix_bytes = defaultdict(lambda: defaultdict(int)) - self.lastStatsTime = None - self.flowmods = {} - - api = SciPass(logger = self.logger, - config_file = self.CONF.SciPassConfig ) - - api.registerForwardingStateChangeHandler(self.changeSwitchForwardingState) - - self.api = api - - wsgi = kwargs['wsgi'] - wsgi.register(SciPassRest, {'api' : self.api}) - - - - - def changeSwitchForwardingState(self, dpid=None, header=None, actions=None, command=None, idle_timeout=None, hard_timeout=None, priority=1): - self.logger.debug("Changing switch forwarding state") - - if(not self.datapaths.has_key(dpid)): - self.logger.error("unable to find switch with dpid " + dpid) - self.logger.error(self.datapaths) - return - - datapath = self.datapaths[dpid] - - ofp = datapath.ofproto - parser = datapath.ofproto_parser - - obj = {} - - if(header.has_key('dl_type')): - if(header['dl_type'] == None): - obj['dl_type'] = None - else: - obj['dl_type'] = int(header['dl_type']) - else: - obj['dl_type'] = ether.ETH_TYPE_IP - - if(header.has_key('phys_port')): - obj['in_port'] = int(header['phys_port']) - else: - obj['in_port'] = None - - if(header.has_key('nw_src')): - obj['nw_src'] = int(header['nw_src']) - else: - obj['nw_src'] = None - - if(header.has_key('nw_src_mask')): - obj['nw_src_mask'] = int(header['nw_src_mask']) - else: - obj['nw_src_mask'] = None - - if(header.has_key('nw_dst')): - obj['nw_dst'] = int(header['nw_dst']) - else: - obj['nw_dst'] = None - - if(header.has_key('nw_dst_mask')): - obj['nw_dst_mask'] = int(header['nw_dst_mask']) - else: - obj['nw_dst_mask'] = None - - if(header.has_key('tp_src')): - obj['tp_src'] = int(header['tp_src']) - else: - obj['tp_src'] = None - - if(header.has_key('tp_dst')): - obj['tp_dst'] = int(header['tp_dst']) - else: - obj['tp_dst'] = None - - if(obj['dl_type'] == None): - match = parser.OFPMatch( in_port = obj['in_port'], - nw_dst = obj['nw_dst'], - nw_dst_mask = obj['nw_dst_mask'], - nw_src = obj['nw_src'], - nw_src_mask = obj['nw_src_mask'], - tp_src = obj['tp_src'], - tp_dst = obj['tp_dst']) - else: - - match = parser.OFPMatch( in_port = obj['in_port'], - nw_dst = obj['nw_dst'], - nw_dst_mask = obj['nw_dst_mask'], - nw_src = obj['nw_src'], - nw_src_mask = obj['nw_src_mask'], - dl_type = obj['dl_type'], - tp_src = obj['tp_src'], - tp_dst = obj['tp_dst']) - - self.logger.debug("Match: " + str(match)) - - of_actions = [] - for action in actions: - if(action['type'] == "output"): - of_actions.append(parser.OFPActionOutput(int(action['port']),0)) - - self.logger.debug("Actions: " + str(of_actions)) - if(command == "ADD"): - command = ofp.OFPFC_ADD - elif(command == "DELETE_STRICT"): - command = ofp.OFPFC_DELETE_STRICT - else: - command = -1 - - self.logger.debug("Sending flow mod with command: " + str(command)) - self.logger.debug("Datpath: " + str(datapath)) - - mod = parser.OFPFlowMod( datapath = datapath, - priority = int(priority), - match = match, - cookie = 0, - command = command, - idle_timeout = int(idle_timeout), - hard_timeout = int(hard_timeout), - actions = of_actions) - - if(datapath.is_active == True): - datapath.send_msg(mod) - - def flushRules(self, dpid): - if(not self.datapaths.has_key(dpid)): - self.logger.error("unable to find switch with dpid " + dpid) - return - - datapath = self.datapaths[dpid] - ofp = datapath.ofproto - parser = datapath.ofproto_parser - - # --- create flowmod to control traffic from the prefix to the interwebs - match = parser.OFPMatch() - mod = parser.OFPFlowMod(datapath,match,0,ofp.OFPFC_DELETE) - - #--- remove mods in the flowmod cache - self.flowmods[dpid] = [] - - - #--- if dp is active then push the rules - if(datapath.is_active == True): - datapath.send_msg(mod) - - def synchRules(self, dpid): - #--- routine to syncronize the rules to the DP - #--- currently just pushes, somday should diff - - #--- yep thats a hack, need to think about what multiple switches means for scipass - if(not self.datapaths.has_key(dpid)): - self.logger.error("unable to find switch with dpid " + dpid) - return - - datapath = self.datapaths[dpid] - if(datapath.is_active == True): - for mod in self.flowmods: - datapath.send_msg(mod) - - def _stats_loop(self): - while 1: - #--- send stats request - for dp in self.datapaths.values(): - self._request_stats(dp) - - #--- sleep - hub.sleep(self.statsInterval) - - def _balance_loop(self): - while 1: - self.logger.debug("here!!") - #--- tell the system to rebalance - self.api.run_balancers() - #--- sleep - hub.sleep(self.balanceInterval) - - def _request_stats(self,datapath): - ofp = datapath.ofproto - parser = datapath.ofproto_parser - - cookie = cookie_mask = 0 - match = parser.OFPMatch() - req = parser.OFPFlowStatsRequest( datapath, - 0, - match, - #ofp.OFPTT_ALL, - 0xff, - ofp.OFPP_NONE) - #0xffffffff, - #cookie, - #cookie_mask, - #match) - datapath.send_msg(req) - - req = parser.OFPPortStatsRequest(datapath, 0, ofp.OFPP_NONE) - datapath.send_msg(req) - - #handle the remove flow event so we know what to sync up when we do this - @set_ev_cls(ofp_event.EventOFPFlowRemoved, MAIN_DISPATCHER) - def _remove_flow_handler(self, ev): - msg = ev.msg - self.api.remove_flow(msg) - for flow in self.flows: - if(flow.match == msg.match and flow.actions == msg.actions): - self.flows.delete(flow) - return - self.logger.error("A flow was removed but we didn't know it was there!") - - @set_ev_cls(ofp_event.EventOFPStateChange, - [MAIN_DISPATCHER, DEAD_DISPATCHER]) - def _state_change_handler(self, ev): - datapath = ev.datapath - if ev.state == MAIN_DISPATCHER: - if not datapath.id in self.datapaths: - self.logger.debug('register datapath: %016x', datapath.id) - dpid = "%016x" % datapath.id - self.datapaths[dpid] = datapath - if(not self.flowmods.has_key(dpid)): - self.flowmods[dpid] = [] - self.flushRules("%016x" % datapath.id) - #--- start the balancing act - self.api.switchJoined(datapath) - - elif ev.state == DEAD_DISPATCHER: - if datapath.id in self.datapaths: - self.logger.debug('unregister datapath: %016x', datapath.id) - del self.datapaths[datapath.id] - - @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) - def _port_status_handler(self, ev): - msg = ev.msg - reason = msg.reason - port_no = msg.desc.port_no - link_state = msg.desc.state - - ofproto = msg.datapath.ofproto - if reason == ofproto.OFPPR_ADD: - self.logger.info("port added %s", port_no) - elif reason == ofproto.OFPPR_DELETE: - self.logger.info("port deleted %s", port_no) - elif reason == ofproto.OFPPR_MODIFY: - self.logger.info("port modified %s state %s", port_no,link_state) - #--- need to check the state to see if port is down - - else: - self.logger.info("Illeagal port state %s %s", port_no, reason) - - - @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) - def _flow_stats_reply_handler(self, ev): - - #--- figure out the time since last stats - old_time = self.lastStatsTime - self.lastStatsTime = int(time.time()) - stats_et = None - if(old_time != None): - stats_et = self.lastStatsTime - old_time - - body = ev.msg.body - - ofproto = ev.msg.datapath.ofproto - flows = [] - prefix_bps = defaultdict(lambda: defaultdict(int)) - #--- update scipass utilization stats for forwarding rules - - for stat in body: - dur_sec = stat.duration_sec - in_port = stat.match.in_port - src_mask = 32 - ((stat.match.wildcards & ofproto.OFPFW_NW_SRC_MASK) >> ofproto.OFPFW_NW_SRC_SHIFT) - dst_mask = 32 - ((stat.match.wildcards & ofproto.OFPFW_NW_DST_MASK) >> ofproto.OFPFW_NW_DST_SHIFT) - - if(src_mask > 0): - #--- this is traffic TX from target prefix - id = ipaddr.IPv4Address(stat.match.nw_src) - prefix = ipaddr.IPv4Network(str(id)+"/"+str(src_mask)) - dir = "tx" - - elif(dst_mask > 0): - #--- this is traffic RX from target prefix - id = ipaddr.IPv4Address(stat.match.nw_dst) - prefix = ipaddr.IPv4Network(str(id)+"/"+str(dst_mask)) - dir = "rx" - else: - #--- no mask, lets skip - continue - - raw_bytes = stat.byte_count - old_bytes = self.prefix_bytes[prefix][dir] - self.prefix_bytes[prefix][dir] = raw_bytes - bytes = raw_bytes - old_bytes - et = stats_et - if(et == None or dur_sec < et): - et = dur_sec - - try: - rate = bytes / float(et) - except ZeroDivisionError: - rate = 0 - - prefix_bps[prefix][dir] = rate - - match = stat.match.__dict__ - wildcards = stat.match.wildcards - del match['dl_dst'] - del match['dl_src'] - del match['dl_type'] - del match['wildcards'] - - if(match['dl_vlan_pcp'] == 0): - del match['dl_vlan_pcp'] - - if(match['dl_vlan'] == 0): - del match['dl_vlan'] - - if(match['nw_proto'] == 0): - del match['nw_proto'] - - if(match['nw_tos'] == 0): - del match['nw_tos'] - - if(match['nw_src'] == 0): - del match['nw_src'] - - if(match['nw_dst'] == 0): - del match['nw_dst'] - - if(match['tp_src'] == 0): - del match['tp_src'] - - if(match['tp_dst'] == 0): - del match['tp_dst'] - - if(match['in_port'] == 0): - del match['in_port'] - else: - match['phys_port'] = int(match['in_port']) - del match['in_port'] - - mask = 32 - ((wildcards & ofproto_v1_0.OFPFW_NW_SRC_MASK) - >> ofproto_v1_0.OFPFW_NW_SRC_SHIFT) - match['nw_src_mask'] = mask - - mask = 32 - ((wildcards & ofproto_v1_0.OFPFW_NW_DST_MASK) - >> ofproto_v1_0.OFPFW_NW_DST_SHIFT) - match['nw_dst_mask'] = mask - - flows.append({'match': match, - 'wildcards': wildcards, - 'packet_count': stat.packet_count - }) - - #--- update the balancer - for prefix in prefix_bps.keys(): - rx = prefix_bps[prefix]["rx"] - tx = prefix_bps[prefix]["tx"] - self.api.updatePrefixBW("%016x" % ev.msg.datapath.id, prefix, tx, rx) - - self.api.TimeoutFlows("%016x" % ev.msg.datapath.id, flows) - - @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) - def _port_stats_reply_handler(self, ev): - body = ev.msg.body - - #--- update scipass utilization stats for ports - - #self.logger.info('datapath port ' - # 'rx-pkts rx-bytes rx-error ' - # 'tx-pkts tx-bytes tx-error') - #self.logger.info('---------------- -------- ' - # '-------- -------- -------- ' - # '-------- -------- --------') - #for stat in sorted(body, key=attrgetter('port_no')): - # self.logger.info('%016x %8x %8d %8d %8d %8d %8d %8d', - # ev.msg.datapath.id, stat.port_no, - # stat.rx_packets, stat.rx_bytes, stat.rx_errors, - # stat.tx_packets, stat.tx_bytes, stat.tx_errors) - diff --git a/python/SciPass_start.py b/python/SciPass_start.py index f4e117e..82eb5c3 100644 --- a/python/SciPass_start.py +++ b/python/SciPass_start.py @@ -1,6 +1,6 @@ -# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. -# Copyright (C) 2014 The Trustees of Indiana University -# Copyright (C) 2013 Hewlett-Packard Development Company, L.P +#!/usr/bin/python + +# Copyright (C) 2014 Hewlett-Packard Development Company, L.P # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,499 +15,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging -import struct -import time - -import bottle -import json - - - - -class SciPassRest(ControllerBase): - def __init__(self, req, link, data, **config): - super(SciPassRest, self).__init__(req, link, data, **config) - self.api = data['api'] - - #POST /scipass/flows/good_flow - @route('scipass', '/scipass/flows/good_flow', methods=['PUT']) - def good_flow(self, req): - try: - obj = eval(req.body) - except SyntaxError: - self.logger.error("Syntax Error processing good_flow signal %s", req.body) - return Response(status=400) - - result = self.api.good_flow(obj) - return Response(content_type='application/json',body=json.dumps(result)) - - #POST /scipass/flows/bad_flow - @route('scipass', '/scipass/flows/bad_flow', methods=['PUT']) - def bad_flow(self, req): - try: - obj = eval(req.body) - except SyntaxError: - self.logger.error("Syntax Error processing bad_flow signal %s", req.body) - return Response(status=400) - result = self.api.bad_flow(obj) - return Response(content_type='application/json',body=json.dumps(result)) - - #GET /scipass/flows/get_good_flows - @route('scipass', '/scipass/flows/get_good_flows', methods=['GET']) - def get_good_flows(self, req): - result = self.api.get_good_flows() - return Response(content_type='application/json',body=json.dumps(result)) - - #GET /scipass/flows/get_bad_flows - @route('scipass', '/scipass/flows/get_bad_flows', methods=['GET']) - def get_bad_flows(self, req): - result = self.api.get_bad_flows() - return Response(content_type='application/json',body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/flows', methods=['GET'], requirements= {'dpid': dpid_lib.DPID_PATTERN}) - def get_switch_flows(self, req, **kwargs): - result = self.api.getSwitchFlows(dpid=kwargs['dpid']) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/sensor/load', methods=['PUT']) - def update_sensor_load(self, req): - try: - obj = eval(req.body) - except SyntaxError: - self.logger.error("Syntax Error processing update_sensor_status signal %s", req.body) - return Response(status=400) - result = self.api.setSensorStatus(obj['sensor_id'],obj['load']) - return Response(content_type='application/json',body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensor/{sensor_id}', methods=['GET'], requirements= {'dpid': dpid_lib.DPID_PATTERN}) - def get_sensor_load(self, req, **kwargs): - result = self.api.getSensorStatus(dpid=kwargs['dpid'], domain=kwargs['domain'], sensor_id=kwargs['sensor_id']) - return Response(content_type='application/json',body=json.dumps(result)) - - @route('scipass', '/scipass/switches', methods=['GET']) - def get_switches(self, req): - result = self.api.getSwitches() - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensors', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) - def get_domain_sensors(self, req, **kwargs): - result = self.api.getDomainSensors(dpid = kwargs['dpid'], domain = kwargs['domain'] ) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domains', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) - def get_switch_domains(self, req, **kwargs): - result = self.api.getSwitchDomains(dpid=kwargs['dpid']) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) - def get_domain_status(self, req, **kwargs): - result = self.api.getDomainStatus(dpid = kwargs['dpid'], domain = kwargs['domain']) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/flows',methods=['GET'],requirements = {'dpid': dpid_lib.DPID_PATTERN}) - def get_domain_flows(self,req, **kwargs): - result = self.api.getDomainFlows(dpid = kwargs['dpid'], domain = kwargs['domain']) - return Response(content_type='application/json', body=json.dumps(result)) - - -class ODL(): - def __init__(self, *args, **kwargs): - #--- register for configuration options - self.CONF.register_opts([ - cfg.StrOpt('SciPassConfig',default='/etc/SciPass/SciPass.xml', - help='where to find the SciPass config file'), - ]) - - self.logger.error("Starting SciPass") - self.datapaths = {} - self.isactive = 1 - self.statsInterval = 5 - self.balanceInterval = 15 - self.bal = None - - #--- ODL Specifics - self.odl_host = "127.0.0.1" - self.odl_port = "8080" - - self.stats_thread = hub.spawn(self._stats_loop) - self.balance_thread = hub.spawn(self._balance_loop) - - self.ports = defaultdict(dict); - self.prefix_bytes = defaultdict(lambda: defaultdict(int)) - self.lastStatsTime = None - self.flowmods = {} - - api = SciPass(logger = self.logger, - config_file = self.CONF.SciPassConfig ) - - api.registerForwardingStateChangeHandler(self.changeSwitchForwardingState) - - self.api = api - - wsgi = kwargs['wsgi'] - wsgi.register(SciPassRest, {'api' : self.api}) - - - - - def changeSwitchForwardingState(self, dpid=None, header=None, actions=None, command=None, idle_timeout=None, hard_timeout=None, priority=1): - self.logger.debug("Changing switch forwarding state") - - if(not self.datapaths.has_key(dpid)): - self.logger.error("unable to find switch with dpid " + dpid) - self.logger.error(self.datapaths) - return - - datapath = self.datapaths[dpid] - - ofp = datapath.ofproto - parser = datapath.ofproto_parser - - obj = {} - - if(header.has_key('dl_type')): - if(header['dl_type'] == None): - obj['dl_type'] = None - else: - obj['dl_type'] = int(header['dl_type']) - else: - obj['dl_type'] = ether.ETH_TYPE_IP - - if(header.has_key('phys_port')): - obj['in_port'] = int(header['phys_port']) - else: - obj['in_port'] = None - - if(header.has_key('nw_src')): - obj['nw_src'] = int(header['nw_src']) - else: - obj['nw_src'] = None - - if(header.has_key('nw_src_mask')): - obj['nw_src_mask'] = int(header['nw_src_mask']) - else: - obj['nw_src_mask'] = None - - if(header.has_key('nw_dst')): - obj['nw_dst'] = int(header['nw_dst']) - else: - obj['nw_dst'] = None - - if(header.has_key('nw_dst_mask')): - obj['nw_dst_mask'] = int(header['nw_dst_mask']) - else: - obj['nw_dst_mask'] = None - - if(header.has_key('tp_src')): - obj['tp_src'] = int(header['tp_src']) - else: - obj['tp_src'] = None - - if(header.has_key('tp_dst')): - obj['tp_dst'] = int(header['tp_dst']) - else: - obj['tp_dst'] = None - - if(obj['dl_type'] == None): - match = parser.OFPMatch( in_port = obj['in_port'], - nw_dst = obj['nw_dst'], - nw_dst_mask = obj['nw_dst_mask'], - nw_src = obj['nw_src'], - nw_src_mask = obj['nw_src_mask'], - tp_src = obj['tp_src'], - tp_dst = obj['tp_dst']) - else: - - match = parser.OFPMatch( in_port = obj['in_port'], - nw_dst = obj['nw_dst'], - nw_dst_mask = obj['nw_dst_mask'], - nw_src = obj['nw_src'], - nw_src_mask = obj['nw_src_mask'], - dl_type = obj['dl_type'], - tp_src = obj['tp_src'], - tp_dst = obj['tp_dst']) - - self.logger.debug("Match: " + str(match)) - - of_actions = [] - for action in actions: - if(action['type'] == "output"): - of_actions.append(parser.OFPActionOutput(int(action['port']),0)) - - self.logger.debug("Actions: " + str(of_actions)) - if(command == "ADD"): - command = ofp.OFPFC_ADD - elif(command == "DELETE_STRICT"): - command = ofp.OFPFC_DELETE_STRICT - else: - command = -1 - - self.logger.debug("Sending flow mod with command: " + str(command)) - self.logger.debug("Datpath: " + str(datapath)) - - mod = parser.OFPFlowMod( datapath = datapath, - priority = int(priority), - match = match, - cookie = 0, - command = command, - idle_timeout = int(idle_timeout), - hard_timeout = int(hard_timeout), - actions = of_actions) - - if(datapath.is_active == True): - datapath.send_msg(mod) - - def flushRules(self, dpid): - if(not self.datapaths.has_key(dpid)): - self.logger.error("unable to find switch with dpid " + dpid) - return - - datapath = self.datapaths[dpid] - ofp = datapath.ofproto - parser = datapath.ofproto_parser - - # --- create flowmod to control traffic from the prefix to the interwebs - match = parser.OFPMatch() - mod = parser.OFPFlowMod(datapath,match,0,ofp.OFPFC_DELETE) - - #--- remove mods in the flowmod cache - self.flowmods[dpid] = [] - - - #--- if dp is active then push the rules - if(datapath.is_active == True): - datapath.send_msg(mod) - - def synchRules(self, dpid): - #--- routine to syncronize the rules to the DP - #--- currently just pushes, somday should diff - - #--- yep thats a hack, need to think about what multiple switches means for scipass - if(not self.datapaths.has_key(dpid)): - self.logger.error("unable to find switch with dpid " + dpid) - return - - datapath = self.datapaths[dpid] - if(datapath.is_active == True): - for mod in self.flowmods: - datapath.send_msg(mod) - - def _stats_loop(self): - while 1: - #--- send stats request - for dp in self.datapaths.values(): - self._request_stats(dp) - - #--- sleep - hub.sleep(self.statsInterval) - - def _balance_loop(self): - while 1: - self.logger.debug("here!!") - #--- tell the system to rebalance - self.api.run_balancers() - #--- sleep - hub.sleep(self.balanceInterval) - - def _request_stats(self,datapath): - ofp = datapath.ofproto - parser = datapath.ofproto_parser - - cookie = cookie_mask = 0 - match = parser.OFPMatch() - req = parser.OFPFlowStatsRequest( datapath, - 0, - match, - #ofp.OFPTT_ALL, - 0xff, - ofp.OFPP_NONE) - #0xffffffff, - #cookie, - #cookie_mask, - #match) - datapath.send_msg(req) - - req = parser.OFPPortStatsRequest(datapath, 0, ofp.OFPP_NONE) - datapath.send_msg(req) - - #handle the remove flow event so we know what to sync up when we do this - @set_ev_cls(ofp_event.EventOFPFlowRemoved, MAIN_DISPATCHER) - def _remove_flow_handler(self, ev): - msg = ev.msg - self.api.remove_flow(msg) - for flow in self.flows: - if(flow.match == msg.match and flow.actions == msg.actions): - self.flows.delete(flow) - return - self.logger.error("A flow was removed but we didn't know it was there!") - - @set_ev_cls(ofp_event.EventOFPStateChange, - [MAIN_DISPATCHER, DEAD_DISPATCHER]) - def _state_change_handler(self, ev): - datapath = ev.datapath - if ev.state == MAIN_DISPATCHER: - if not datapath.id in self.datapaths: - self.logger.debug('register datapath: %016x', datapath.id) - dpid = "%016x" % datapath.id - self.datapaths[dpid] = datapath - if(not self.flowmods.has_key(dpid)): - self.flowmods[dpid] = [] - self.flushRules("%016x" % datapath.id) - #--- start the balancing act - self.api.switchJoined(datapath) - - elif ev.state == DEAD_DISPATCHER: - if datapath.id in self.datapaths: - self.logger.debug('unregister datapath: %016x', datapath.id) - del self.datapaths[datapath.id] - - @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) - def _port_status_handler(self, ev): - msg = ev.msg - reason = msg.reason - port_no = msg.desc.port_no - link_state = msg.desc.state - - ofproto = msg.datapath.ofproto - if reason == ofproto.OFPPR_ADD: - self.logger.info("port added %s", port_no) - elif reason == ofproto.OFPPR_DELETE: - self.logger.info("port deleted %s", port_no) - elif reason == ofproto.OFPPR_MODIFY: - self.logger.info("port modified %s state %s", port_no,link_state) - #--- need to check the state to see if port is down - - else: - self.logger.info("Illeagal port state %s %s", port_no, reason) - - - @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) - def _flow_stats_reply_handler(self, ev): - - #--- figure out the time since last stats - old_time = self.lastStatsTime - self.lastStatsTime = int(time.time()) - stats_et = None - if(old_time != None): - stats_et = self.lastStatsTime - old_time - - body = ev.msg.body - - ofproto = ev.msg.datapath.ofproto - flows = [] - prefix_bps = defaultdict(lambda: defaultdict(int)) - #--- update scipass utilization stats for forwarding rules - - for stat in body: - dur_sec = stat.duration_sec - in_port = stat.match.in_port - src_mask = 32 - ((stat.match.wildcards & ofproto.OFPFW_NW_SRC_MASK) >> ofproto.OFPFW_NW_SRC_SHIFT) - dst_mask = 32 - ((stat.match.wildcards & ofproto.OFPFW_NW_DST_MASK) >> ofproto.OFPFW_NW_DST_SHIFT) - - if(src_mask > 0): - #--- this is traffic TX from target prefix - id = ipaddr.IPv4Address(stat.match.nw_src) - prefix = ipaddr.IPv4Network(str(id)+"/"+str(src_mask)) - dir = "tx" - - elif(dst_mask > 0): - #--- this is traffic RX from target prefix - id = ipaddr.IPv4Address(stat.match.nw_dst) - prefix = ipaddr.IPv4Network(str(id)+"/"+str(dst_mask)) - dir = "rx" - else: - #--- no mask, lets skip - continue - - raw_bytes = stat.byte_count - old_bytes = self.prefix_bytes[prefix][dir] - self.prefix_bytes[prefix][dir] = raw_bytes - bytes = raw_bytes - old_bytes - et = stats_et - if(et == None or dur_sec < et): - et = dur_sec - - try: - rate = bytes / float(et) - except ZeroDivisionError: - rate = 0 - - prefix_bps[prefix][dir] = rate - - match = stat.match.__dict__ - wildcards = stat.match.wildcards - del match['dl_dst'] - del match['dl_src'] - del match['dl_type'] - del match['wildcards'] - - if(match['dl_vlan_pcp'] == 0): - del match['dl_vlan_pcp'] - - if(match['dl_vlan'] == 0): - del match['dl_vlan'] - - if(match['nw_proto'] == 0): - del match['nw_proto'] - - if(match['nw_tos'] == 0): - del match['nw_tos'] - - if(match['nw_src'] == 0): - del match['nw_src'] - - if(match['nw_dst'] == 0): - del match['nw_dst'] - - if(match['tp_src'] == 0): - del match['tp_src'] - - if(match['tp_dst'] == 0): - del match['tp_dst'] - - if(match['in_port'] == 0): - del match['in_port'] - else: - match['phys_port'] = int(match['in_port']) - del match['in_port'] - - mask = 32 - ((wildcards & ofproto_v1_0.OFPFW_NW_SRC_MASK) - >> ofproto_v1_0.OFPFW_NW_SRC_SHIFT) - match['nw_src_mask'] = mask - - mask = 32 - ((wildcards & ofproto_v1_0.OFPFW_NW_DST_MASK) - >> ofproto_v1_0.OFPFW_NW_DST_SHIFT) - match['nw_dst_mask'] = mask - - flows.append({'match': match, - 'wildcards': wildcards, - 'packet_count': stat.packet_count - }) - - #--- update the balancer - for prefix in prefix_bps.keys(): - rx = prefix_bps[prefix]["rx"] - tx = prefix_bps[prefix]["tx"] - self.api.updatePrefixBW("%016x" % ev.msg.datapath.id, prefix, tx, rx) +import HPVAN - self.api.TimeoutFlows("%016x" % ev.msg.datapath.id, flows) - - @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) - def _port_stats_reply_handler(self, ev): - body = ev.msg.body +port='8080' - #--- update scipass utilization stats for ports +if __name__ == '__main__': + van = HPVAN() + controller = "127.0.0.1" + port = 8080 + username = "sdn" + password = "skyline" + van.connect_controller(controller,port) - #self.logger.info('datapath port ' - # 'rx-pkts rx-bytes rx-error ' - # 'tx-pkts tx-bytes tx-error') - #self.logger.info('---------------- -------- ' - # '-------- -------- -------- ' - # '-------- -------- --------') - #for stat in sorted(body, key=attrgetter('port_no')): - # self.logger.info('%016x %8x %8d %8d %8d %8d %8d %8d', - # ev.msg.datapath.id, stat.port_no, - # stat.rx_packets, stat.rx_bytes, stat.rx_errors, - # stat.tx_packets, stat.tx_bytes, stat.tx_errors) From 9b183fef0d75080358a2b89b69c0c7cc837622d5 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 20 Nov 2014 13:21:15 -0800 Subject: [PATCH 23/81] Removed some Ryu dependant code form HPVAN --- python/HPVAN.py | 30 +-- python/ODL.py | 527 ------------------------------------------------ 2 files changed, 1 insertion(+), 556 deletions(-) delete mode 100644 python/ODL.py diff --git a/python/HPVAN.py b/python/HPVAN.py index bd35c6e..eaf1523 100644 --- a/python/HPVAN.py +++ b/python/HPVAN.py @@ -268,32 +268,4 @@ def _request_stats(self,datapath): req = parser.OFPPortStatsRequest(datapath, 0, ofp.OFPP_NONE) datapath.send_msg(req) - #handle the remove flow event so we know what to sync up when we do this - @set_ev_cls(ofp_event.EventOFPFlowRemoved, MAIN_DISPATCHER) - def _remove_flow_handler(self, ev): - msg = ev.msg - self.api.remove_flow(msg) - for flow in self.flows: - if(flow.match == msg.match and flow.actions == msg.actions): - self.flows.delete(flow) - return - self.logger.error("A flow was removed but we didn't know it was there!") - - - - - - #--- update scipass utilization stats for ports - - #self.logger.info('datapath port ' - # 'rx-pkts rx-bytes rx-error ' - # 'tx-pkts tx-bytes tx-error') - #self.logger.info('---------------- -------- ' - # '-------- -------- -------- ' - # '-------- -------- --------') - #for stat in sorted(body, key=attrgetter('port_no')): - # self.logger.info('%016x %8x %8d %8d %8d %8d %8d %8d', - # ev.msg.datapath.id, stat.port_no, - # stat.rx_packets, stat.rx_bytes, stat.rx_errors, - # stat.tx_packets, stat.tx_bytes, stat.tx_errors) - + diff --git a/python/ODL.py b/python/ODL.py deleted file mode 100644 index a289412..0000000 --- a/python/ODL.py +++ /dev/null @@ -1,527 +0,0 @@ -# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. -# Copyright (C) 2014 The Trustees of Indiana University -# Copyright (C) 2013 Hewlett-Packard Development Company, L.P -# -# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 -# -# 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 logging -import struct -import time - -from operator import attrgetter -from webob import Response -import json - -import socket -import ipaddr - -from collections import defaultdict -from SimpleBalancer import SimpleBalancer -from SciPass import SciPass - -""" - Forwarding rule Priorities - BlackList - WhiteList - Balancer - Default -""" - - -class SciPassRest(ControllerBase): - def __init__(self, req, link, data, **config): - super(SciPassRest, self).__init__(req, link, data, **config) - self.api = data['api'] - - #POST /scipass/flows/good_flow - @route('scipass', '/scipass/flows/good_flow', methods=['PUT']) - def good_flow(self, req): - try: - obj = eval(req.body) - except SyntaxError: - self.logger.error("Syntax Error processing good_flow signal %s", req.body) - return Response(status=400) - - result = self.api.good_flow(obj) - return Response(content_type='application/json',body=json.dumps(result)) - - #POST /scipass/flows/bad_flow - @route('scipass', '/scipass/flows/bad_flow', methods=['PUT']) - def bad_flow(self, req): - try: - obj = eval(req.body) - except SyntaxError: - self.logger.error("Syntax Error processing bad_flow signal %s", req.body) - return Response(status=400) - result = self.api.bad_flow(obj) - return Response(content_type='application/json',body=json.dumps(result)) - - #GET /scipass/flows/get_good_flows - @route('scipass', '/scipass/flows/get_good_flows', methods=['GET']) - def get_good_flows(self, req): - result = self.api.get_good_flows() - return Response(content_type='application/json',body=json.dumps(result)) - - #GET /scipass/flows/get_bad_flows - @route('scipass', '/scipass/flows/get_bad_flows', methods=['GET']) - def get_bad_flows(self, req): - result = self.api.get_bad_flows() - return Response(content_type='application/json',body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/flows', methods=['GET'], requirements= {'dpid': dpid_lib.DPID_PATTERN}) - def get_switch_flows(self, req, **kwargs): - result = self.api.getSwitchFlows(dpid=kwargs['dpid']) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/sensor/load', methods=['PUT']) - def update_sensor_load(self, req): - try: - obj = eval(req.body) - except SyntaxError: - self.logger.error("Syntax Error processing update_sensor_status signal %s", req.body) - return Response(status=400) - result = self.api.setSensorStatus(obj['sensor_id'],obj['load']) - return Response(content_type='application/json',body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensor/{sensor_id}', methods=['GET'], requirements= {'dpid': dpid_lib.DPID_PATTERN}) - def get_sensor_load(self, req, **kwargs): - result = self.api.getSensorStatus(dpid=kwargs['dpid'], domain=kwargs['domain'], sensor_id=kwargs['sensor_id']) - return Response(content_type='application/json',body=json.dumps(result)) - - @route('scipass', '/scipass/switches', methods=['GET']) - def get_switches(self, req): - result = self.api.getSwitches() - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensors', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) - def get_domain_sensors(self, req, **kwargs): - result = self.api.getDomainSensors(dpid = kwargs['dpid'], domain = kwargs['domain'] ) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domains', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) - def get_switch_domains(self, req, **kwargs): - result = self.api.getSwitchDomains(dpid=kwargs['dpid']) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}', methods=['GET'], requirements = {'dpid': dpid_lib.DPID_PATTERN}) - def get_domain_status(self, req, **kwargs): - result = self.api.getDomainStatus(dpid = kwargs['dpid'], domain = kwargs['domain']) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/flows',methods=['GET'],requirements = {'dpid': dpid_lib.DPID_PATTERN}) - def get_domain_flows(self,req, **kwargs): - result = self.api.getDomainFlows(dpid = kwargs['dpid'], domain = kwargs['domain']) - return Response(content_type='application/json', body=json.dumps(result)) - - -class ODL(): - def __init__(self, *args, **kwargs): - #--- register for configuration options - self.CONF.register_opts([ - cfg.StrOpt('SciPassConfig',default='/etc/SciPass/SciPass.xml', - help='where to find the SciPass config file'), - ]) - - self.logger.error("Starting SciPass") - self.datapaths = {} - self.isactive = 1 - self.statsInterval = 5 - self.balanceInterval = 15 - self.bal = None - - #--- ODL Specifics - self.odl_host = "127.0.0.1" - self.odl_port = "8080" - - self.stats_thread = hub.spawn(self._stats_loop) - self.balance_thread = hub.spawn(self._balance_loop) - - self.ports = defaultdict(dict); - self.prefix_bytes = defaultdict(lambda: defaultdict(int)) - self.lastStatsTime = None - self.flowmods = {} - - api = SciPass(logger = self.logger, - config_file = self.CONF.SciPassConfig ) - - api.registerForwardingStateChangeHandler(self.changeSwitchForwardingState) - - self.api = api - - wsgi = kwargs['wsgi'] - wsgi.register(SciPassRest, {'api' : self.api}) - - - - - def changeSwitchForwardingState(self, dpid=None, header=None, actions=None, command=None, idle_timeout=None, hard_timeout=None, priority=1): - self.logger.debug("Changing switch forwarding state") - - if(not self.datapaths.has_key(dpid)): - self.logger.error("unable to find switch with dpid " + dpid) - self.logger.error(self.datapaths) - return - - datapath = self.datapaths[dpid] - - ofp = datapath.ofproto - parser = datapath.ofproto_parser - - obj = {} - - if(header.has_key('dl_type')): - if(header['dl_type'] == None): - obj['dl_type'] = None - else: - obj['dl_type'] = int(header['dl_type']) - else: - obj['dl_type'] = ether.ETH_TYPE_IP - - if(header.has_key('phys_port')): - obj['in_port'] = int(header['phys_port']) - else: - obj['in_port'] = None - - if(header.has_key('nw_src')): - obj['nw_src'] = int(header['nw_src']) - else: - obj['nw_src'] = None - - if(header.has_key('nw_src_mask')): - obj['nw_src_mask'] = int(header['nw_src_mask']) - else: - obj['nw_src_mask'] = None - - if(header.has_key('nw_dst')): - obj['nw_dst'] = int(header['nw_dst']) - else: - obj['nw_dst'] = None - - if(header.has_key('nw_dst_mask')): - obj['nw_dst_mask'] = int(header['nw_dst_mask']) - else: - obj['nw_dst_mask'] = None - - if(header.has_key('tp_src')): - obj['tp_src'] = int(header['tp_src']) - else: - obj['tp_src'] = None - - if(header.has_key('tp_dst')): - obj['tp_dst'] = int(header['tp_dst']) - else: - obj['tp_dst'] = None - - if(obj['dl_type'] == None): - match = parser.OFPMatch( in_port = obj['in_port'], - nw_dst = obj['nw_dst'], - nw_dst_mask = obj['nw_dst_mask'], - nw_src = obj['nw_src'], - nw_src_mask = obj['nw_src_mask'], - tp_src = obj['tp_src'], - tp_dst = obj['tp_dst']) - else: - - match = parser.OFPMatch( in_port = obj['in_port'], - nw_dst = obj['nw_dst'], - nw_dst_mask = obj['nw_dst_mask'], - nw_src = obj['nw_src'], - nw_src_mask = obj['nw_src_mask'], - dl_type = obj['dl_type'], - tp_src = obj['tp_src'], - tp_dst = obj['tp_dst']) - - self.logger.debug("Match: " + str(match)) - - of_actions = [] - for action in actions: - if(action['type'] == "output"): - of_actions.append(parser.OFPActionOutput(int(action['port']),0)) - - self.logger.debug("Actions: " + str(of_actions)) - if(command == "ADD"): - command = ofp.OFPFC_ADD - elif(command == "DELETE_STRICT"): - command = ofp.OFPFC_DELETE_STRICT - else: - command = -1 - - self.logger.debug("Sending flow mod with command: " + str(command)) - self.logger.debug("Datpath: " + str(datapath)) - - mod = parser.OFPFlowMod( datapath = datapath, - priority = int(priority), - match = match, - cookie = 0, - command = command, - idle_timeout = int(idle_timeout), - hard_timeout = int(hard_timeout), - actions = of_actions) - - if(datapath.is_active == True): - datapath.send_msg(mod) - - def flushRules(self, dpid): - if(not self.datapaths.has_key(dpid)): - self.logger.error("unable to find switch with dpid " + dpid) - return - - datapath = self.datapaths[dpid] - ofp = datapath.ofproto - parser = datapath.ofproto_parser - - # --- create flowmod to control traffic from the prefix to the interwebs - match = parser.OFPMatch() - mod = parser.OFPFlowMod(datapath,match,0,ofp.OFPFC_DELETE) - - #--- remove mods in the flowmod cache - self.flowmods[dpid] = [] - - - #--- if dp is active then push the rules - if(datapath.is_active == True): - datapath.send_msg(mod) - - def synchRules(self, dpid): - #--- routine to syncronize the rules to the DP - #--- currently just pushes, somday should diff - - #--- yep thats a hack, need to think about what multiple switches means for scipass - if(not self.datapaths.has_key(dpid)): - self.logger.error("unable to find switch with dpid " + dpid) - return - - datapath = self.datapaths[dpid] - if(datapath.is_active == True): - for mod in self.flowmods: - datapath.send_msg(mod) - - def _stats_loop(self): - while 1: - #--- send stats request - for dp in self.datapaths.values(): - self._request_stats(dp) - - #--- sleep - hub.sleep(self.statsInterval) - - def _balance_loop(self): - while 1: - self.logger.debug("here!!") - #--- tell the system to rebalance - self.api.run_balancers() - #--- sleep - hub.sleep(self.balanceInterval) - - def _request_stats(self,datapath): - ofp = datapath.ofproto - parser = datapath.ofproto_parser - - cookie = cookie_mask = 0 - match = parser.OFPMatch() - req = parser.OFPFlowStatsRequest( datapath, - 0, - match, - #ofp.OFPTT_ALL, - 0xff, - ofp.OFPP_NONE) - #0xffffffff, - #cookie, - #cookie_mask, - #match) - datapath.send_msg(req) - - req = parser.OFPPortStatsRequest(datapath, 0, ofp.OFPP_NONE) - datapath.send_msg(req) - - #handle the remove flow event so we know what to sync up when we do this - @set_ev_cls(ofp_event.EventOFPFlowRemoved, MAIN_DISPATCHER) - def _remove_flow_handler(self, ev): - msg = ev.msg - self.api.remove_flow(msg) - for flow in self.flows: - if(flow.match == msg.match and flow.actions == msg.actions): - self.flows.delete(flow) - return - self.logger.error("A flow was removed but we didn't know it was there!") - - @set_ev_cls(ofp_event.EventOFPStateChange, - [MAIN_DISPATCHER, DEAD_DISPATCHER]) - def _state_change_handler(self, ev): - datapath = ev.datapath - if ev.state == MAIN_DISPATCHER: - if not datapath.id in self.datapaths: - self.logger.debug('register datapath: %016x', datapath.id) - dpid = "%016x" % datapath.id - self.datapaths[dpid] = datapath - if(not self.flowmods.has_key(dpid)): - self.flowmods[dpid] = [] - self.flushRules("%016x" % datapath.id) - #--- start the balancing act - self.api.switchJoined(datapath) - - elif ev.state == DEAD_DISPATCHER: - if datapath.id in self.datapaths: - self.logger.debug('unregister datapath: %016x', datapath.id) - del self.datapaths[datapath.id] - - @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) - def _port_status_handler(self, ev): - msg = ev.msg - reason = msg.reason - port_no = msg.desc.port_no - link_state = msg.desc.state - - ofproto = msg.datapath.ofproto - if reason == ofproto.OFPPR_ADD: - self.logger.info("port added %s", port_no) - elif reason == ofproto.OFPPR_DELETE: - self.logger.info("port deleted %s", port_no) - elif reason == ofproto.OFPPR_MODIFY: - self.logger.info("port modified %s state %s", port_no,link_state) - #--- need to check the state to see if port is down - - else: - self.logger.info("Illeagal port state %s %s", port_no, reason) - - - @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) - def _flow_stats_reply_handler(self, ev): - - #--- figure out the time since last stats - old_time = self.lastStatsTime - self.lastStatsTime = int(time.time()) - stats_et = None - if(old_time != None): - stats_et = self.lastStatsTime - old_time - - body = ev.msg.body - - ofproto = ev.msg.datapath.ofproto - flows = [] - prefix_bps = defaultdict(lambda: defaultdict(int)) - #--- update scipass utilization stats for forwarding rules - - for stat in body: - dur_sec = stat.duration_sec - in_port = stat.match.in_port - src_mask = 32 - ((stat.match.wildcards & ofproto.OFPFW_NW_SRC_MASK) >> ofproto.OFPFW_NW_SRC_SHIFT) - dst_mask = 32 - ((stat.match.wildcards & ofproto.OFPFW_NW_DST_MASK) >> ofproto.OFPFW_NW_DST_SHIFT) - - if(src_mask > 0): - #--- this is traffic TX from target prefix - id = ipaddr.IPv4Address(stat.match.nw_src) - prefix = ipaddr.IPv4Network(str(id)+"/"+str(src_mask)) - dir = "tx" - - elif(dst_mask > 0): - #--- this is traffic RX from target prefix - id = ipaddr.IPv4Address(stat.match.nw_dst) - prefix = ipaddr.IPv4Network(str(id)+"/"+str(dst_mask)) - dir = "rx" - else: - #--- no mask, lets skip - continue - - raw_bytes = stat.byte_count - old_bytes = self.prefix_bytes[prefix][dir] - self.prefix_bytes[prefix][dir] = raw_bytes - bytes = raw_bytes - old_bytes - et = stats_et - if(et == None or dur_sec < et): - et = dur_sec - - try: - rate = bytes / float(et) - except ZeroDivisionError: - rate = 0 - - prefix_bps[prefix][dir] = rate - - match = stat.match.__dict__ - wildcards = stat.match.wildcards - del match['dl_dst'] - del match['dl_src'] - del match['dl_type'] - del match['wildcards'] - - if(match['dl_vlan_pcp'] == 0): - del match['dl_vlan_pcp'] - - if(match['dl_vlan'] == 0): - del match['dl_vlan'] - - if(match['nw_proto'] == 0): - del match['nw_proto'] - - if(match['nw_tos'] == 0): - del match['nw_tos'] - - if(match['nw_src'] == 0): - del match['nw_src'] - - if(match['nw_dst'] == 0): - del match['nw_dst'] - - if(match['tp_src'] == 0): - del match['tp_src'] - - if(match['tp_dst'] == 0): - del match['tp_dst'] - - if(match['in_port'] == 0): - del match['in_port'] - else: - match['phys_port'] = int(match['in_port']) - del match['in_port'] - - mask = 32 - ((wildcards & ofproto_v1_0.OFPFW_NW_SRC_MASK) - >> ofproto_v1_0.OFPFW_NW_SRC_SHIFT) - match['nw_src_mask'] = mask - - mask = 32 - ((wildcards & ofproto_v1_0.OFPFW_NW_DST_MASK) - >> ofproto_v1_0.OFPFW_NW_DST_SHIFT) - match['nw_dst_mask'] = mask - - flows.append({'match': match, - 'wildcards': wildcards, - 'packet_count': stat.packet_count - }) - - #--- update the balancer - for prefix in prefix_bps.keys(): - rx = prefix_bps[prefix]["rx"] - tx = prefix_bps[prefix]["tx"] - self.api.updatePrefixBW("%016x" % ev.msg.datapath.id, prefix, tx, rx) - - self.api.TimeoutFlows("%016x" % ev.msg.datapath.id, flows) - - @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) - def _port_stats_reply_handler(self, ev): - body = ev.msg.body - - #--- update scipass utilization stats for ports - - #self.logger.info('datapath port ' - # 'rx-pkts rx-bytes rx-error ' - # 'tx-pkts tx-bytes tx-error') - #self.logger.info('---------------- -------- ' - # '-------- -------- -------- ' - # '-------- -------- --------') - #for stat in sorted(body, key=attrgetter('port_no')): - # self.logger.info('%016x %8x %8d %8d %8d %8d %8d %8d', - # ev.msg.datapath.id, stat.port_no, - # stat.rx_packets, stat.rx_bytes, stat.rx_errors, - # stat.tx_packets, stat.tx_bytes, stat.tx_errors) - From cc3fc9bd9855d814bbeeecea384c403baeb9eef7 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 20 Nov 2014 13:45:41 -0800 Subject: [PATCH 24/81] Removed more unused code from HPVAN --- python/HPVAN.py | 76 ++++++++++------------------------------- python/SciPass_start.py | 11 +++--- 2 files changed, 25 insertions(+), 62 deletions(-) diff --git a/python/HPVAN.py b/python/HPVAN.py index eaf1523..63902f5 100644 --- a/python/HPVAN.py +++ b/python/HPVAN.py @@ -47,8 +47,8 @@ def test(self): flows = self.api.get_food_flows return flows - def run_server(self): - run('scipass',host='localhost', port=8080) + def run_server(self,host,port): + run('scipass',host=host, port=port) class HPVAN(): @@ -68,25 +68,30 @@ def __init__(self): self.logger.error("Starting SciPass") # - - api = SciPass(logger = self.logger, config_file = self.CONF.SciPassConfig ) - api.registerForwardingStateChangeHandler(self.changeSwitchForwardingState) self.api = api - # Start REST interface - SciPassRest(api).run_server() def connect_controller(self, controller, port, username, password): self.logger.debug("Connecting to VAN controller" + controller) - auth = hp.XAuthToken(user=username, password=password, server=controller) api = hp.Api(controller=controller, auth=auth) + + + def start_rest_interface(self, host, port): + + """ + + :rtype : object + """ + SciPassRest(self.api).run_server(host, port) + + def changeSwitchForwardingState(self, dpid=None, header=None, actions=None, command=None, idle_timeout=None, hard_timeout=None, priority=1): self.logger.debug("Changing switch forwarding state") @@ -209,7 +214,7 @@ def flushRules(self, dpid): mod = parser.OFPFlowMod(datapath,match,0,ofp.OFPFC_DELETE) #--- remove mods in the flowmod cache - self.flowmods[dpid] = [] + #self.flowmods[dpid] = [] #--- if dp is active then push the rules @@ -220,52 +225,7 @@ def synchRules(self, dpid): #--- routine to syncronize the rules to the DP #--- currently just pushes, somday should diff - #--- yep thats a hack, need to think about what multiple switches means for scipass - if(not self.datapaths.has_key(dpid)): - self.logger.error("unable to find switch with dpid " + dpid) - return - - datapath = self.datapaths[dpid] - if(datapath.is_active == True): - for mod in self.flowmods: - datapath.send_msg(mod) - - def _stats_loop(self): - while 1: - #--- send stats request - for dp in self.datapaths.values(): - self._request_stats(dp) - - #--- sleep - hub.sleep(self.statsInterval) - - def _balance_loop(self): - while 1: - self.logger.debug("here!!") - #--- tell the system to rebalance - self.api.run_balancers() - #--- sleep - hub.sleep(self.balanceInterval) - - def _request_stats(self,datapath): - ofp = datapath.ofproto - parser = datapath.ofproto_parser - - cookie = cookie_mask = 0 - match = parser.OFPMatch() - req = parser.OFPFlowStatsRequest( datapath, - 0, - match, - #ofp.OFPTT_ALL, - 0xff, - ofp.OFPP_NONE) - #0xffffffff, - #cookie, - #cookie_mask, - #match) - datapath.send_msg(req) - - req = parser.OFPPortStatsRequest(datapath, 0, ofp.OFPP_NONE) - datapath.send_msg(req) - - + + + + diff --git a/python/SciPass_start.py b/python/SciPass_start.py index 82eb5c3..a9f0947 100644 --- a/python/SciPass_start.py +++ b/python/SciPass_start.py @@ -15,16 +15,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -import HPVAN +import HPVAN as van -port='8080' +def start_scipass_van(): -if __name__ == '__main__': - van = HPVAN() controller = "127.0.0.1" port = 8080 username = "sdn" password = "skyline" van.connect_controller(controller,port) + van.start_rest_interface('localhost',8080) + + +if __name__ == '__main__': + start_scipass_van() From 0dd70fcd8fedcb527a80e0eeda28452f86dc936d Mon Sep 17 00:00:00 2001 From: Chris Small Date: Thu, 20 Nov 2014 16:26:02 -0800 Subject: [PATCH 25/81] Use metaclasses for HPVAN --- python/HPVAN.py | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/python/HPVAN.py b/python/HPVAN.py index 63902f5..1141b4d 100644 --- a/python/HPVAN.py +++ b/python/HPVAN.py @@ -42,21 +42,23 @@ def __init__(self, api): @route('/scipass/test') def test(self): return "test" + @route('/scipass/flows') def test(self): flows = self.api.get_food_flows return flows - def run_server(self,host,port): - run('scipass',host=host, port=port) + def run_server(self, host, port): + run('scipass', host=host, port=port) + +class HPVAN(type): -class HPVAN(): - - def __init__(self): + def __init__(self, controller, port, username, password): + super(HPVAN,self).__init__(controller, port, username) self.datapaths = {} - # Set Logger + logging.basicConfig() self.logger = logging.getLogger(__name__) #--- register for configuration options @@ -74,10 +76,6 @@ def __init__(self): self.api = api - - def connect_controller(self, controller, port, username, password): - - self.logger.debug("Connecting to VAN controller" + controller) auth = hp.XAuthToken(user=username, password=password, server=controller) api = hp.Api(controller=controller, auth=auth) @@ -85,10 +83,6 @@ def connect_controller(self, controller, port, username, password): def start_rest_interface(self, host, port): - """ - - :rtype : object - """ SciPassRest(self.api).run_server(host, port) @@ -220,12 +214,5 @@ def flushRules(self, dpid): #--- if dp is active then push the rules if(datapath.is_active == True): datapath.send_msg(mod) - - def synchRules(self, dpid): - #--- routine to syncronize the rules to the DP - #--- currently just pushes, somday should diff - - - From e0c5f67eddc7ce482ddf0b678642c2f42b886744 Mon Sep 17 00:00:00 2001 From: Chri Small Date: Fri, 21 Nov 2014 05:03:21 +0000 Subject: [PATCH 26/81] Deleted OpenDayLight --- python/OpenDaylight.py | 369 ----------------------------------------- 1 file changed, 369 deletions(-) delete mode 100644 python/OpenDaylight.py diff --git a/python/OpenDaylight.py b/python/OpenDaylight.py deleted file mode 100644 index d6e9e1c..0000000 --- a/python/OpenDaylight.py +++ /dev/null @@ -1,369 +0,0 @@ -""" -OpenDaylight REST API - -Copyright 2013 The University of Wisconsin Board of Regents - - Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 - - 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. - - - Written by: Dale W. Carder, dwcarder@wisc.edu - Network Services Group - Division of Information Technology - University of Wisconsin at Madison - -This material is based upon work supported by the National Science Foundation -under Grant No. 1247322. -""" - -from __future__ import print_function -import json -import requests -from requests.auth import HTTPBasicAuth - -class OpenDaylight(object): - """An object holding details to talk to the OpenDaylight REST API - - OpenDaylight.setup is a dictionary loaded with the following - default values: - {'hostname':'localhost', - 'port':'8080', - 'username':'admin', - 'password':'admin', - 'path':'/controller/nb/v2/', - 'container':'default', - 'http':'http://' } - - Your code should change these as required for your installation. - OpenDaylight.url holds the url for each REST query. Typically - you would let OpenDaylight.prepare() build this for you. - - OpenDaylight.auth holds an auth object for Requests to use - for each REST query. Typically you would also let - OpenDaylight.prepare() build this for you. - """ - - def __init__(self): - """Set some mostly reasonable defaults. - """ - self.setup = {'hostname':'localhost', - 'port':'8080', - 'username':'admin', - 'password':'admin', - 'path':'/controller/nb/v2/', - 'container':'default', - 'http':'http://'} - - self._base_url = None - self.url = None - self.auth = None - - def prepare(self, app, path): - """Sets up the necessary details for the REST connection by calling - prepare_url and prepare_auth. - - Arguments: - 'app' - which OpenDaylight northbound api component (application) - we want to talk to. - 'path' - the specific rest query for the application. - """ - self.prepare_url(app, path) - self.prepare_auth() - - def prepare_url(self, app, path): - """Build the URL for this REST connection which is then stored as - OpenDaylight.url - - If you use prepare(), you shouldn't need to call prepare_url() - yourself. However, if there were a URL you wanted to construct that - was so whacked out custom, then by all means build it yourself and don't - bother to call this function. - - Arguments: - 'app' - which OpenDaylight northbound api component (application) - we want to talk to. - 'path' - the specific rest query for the application. - - Note that other attributes, including 'container' are specified - in the OpenDaylight.setup dictionary. - """ - - # the base url we will use for the connection - self._base_url = self.setup['http'] + self.setup['hostname'] + ':' + \ - self.setup['port'] + self.setup['path'] - - # the specific path we are building - self.url = self._base_url + app + '/' + self.setup['container'] + path - - def prepare_auth(self): - """Set up the credentials for the REST connection by creating - an auth object for Requests and shoving it into OpenDaylight.auth - - Currently, as far as I know, the OpenDaylight controller uses - http basic auth. If/when that changes this function should be - updated. - - If you use prepare(), you shouldn't need to call prepare_auth() - yourself. However, if there were something you wanted to do - that was so whacked out custom, then by all means build it yourself - and don't bother to call this function. - """ - - # stuff an HTTPBasicAuth object in here ready for use - self.auth = HTTPBasicAuth(self.setup['username'], - self.setup['password']) - #print("Prepare set up auth: " + self.setup['username'] + ', ' + \ - # self.setup['password']) - - -class OpenDaylightFlow(object): - """OpenDaylightFlow is an object that talks to the OpenDaylight - Flow Programmer application REST API - - OpenDaylight.odl holds an OpenDaylight object containing details - on how to communicate with the controller. - - OpenDaylightFlow.request holds a Requests object for the REST - session. Take a look at the Requests documentation for all of - the methods available, but here are a few handy examples: - OpenDaylightFlow.request.status_code - returns the http code - OpenDaylightFlow.request.text - returns the response as text - - OpenDaylightFlow.flows holds a dictionary that corresponds to - the flowConfig element in the OpenDaylight REST API. Note that - we don't statically define what those fields are here in this - object. This makes this library code more flexible as flowConfig - changes over time. After all, this is REST, not RPC. - """ - - def __init__(self, odl): - """Mandatory argument: - odl - an OpenDaylight object - """ - self.odl = odl - self.__app = 'flow' - self.request = None - self.flows = None - - def get(self, node_id=None, flow_name=None): - """Get Flows specified on the Controller and stuffs the results into - the OpenDaylightFlow.flows dictionary. - - Optional Arguments: - node_id - returns flows just for that switch dpid - flow_name - returns the specifically named flow on that switch - """ - - # clear out any remaining crud from previous calls - if hasattr(self, 'request'): - del self.request - if hasattr(self, 'flows'): - del self.flows - - if node_id is None: - self.odl.prepare(self.__app, '/') - elif flow_name is None: - self.odl.prepare(self.__app, '/' + 'OF/' + node_id + '/') - else: - self.odl.prepare(self.__app, '/' + 'OF/' + node_id + '/' - + flow_name + '/') - - self.request = requests.get(url=self.odl.url, auth=self.odl.auth) - - if self.request.status_code == 200: - self.flows = self.request.json() - if 'flowConfig' in self.flows: - self.flows = self.flows.get('flowConfig') - else: - raise OpenDaylightError({'url':self.odl.url, - 'http_code':self.request.status_code, - 'msg':self.request.text}) - - - def add(self, flow): - """Given a dictionary corresponding to a flowConfig, add this flow to - the Controller. Note that the switch dpid and the flow's name is - specified in the flowConfig passed in. - """ - if hasattr(self, 'request'): - del self.request - #print(flow) - self.odl.prepare(self.__app, '/' + flow['node']['@type'] + '/' + - flow['node']['@id'] + '/' + flow['name'] + '/') - headers = {'Content-type': 'application/json'} - body = json.dumps(flow) - self.request = requests.post(url=self.odl.url, auth=self.odl.auth, - data=body, headers=headers) - - if self.request.status_code != 201: - raise OpenDaylightError({'url':self.odl.url, - 'http_code':self.request.status_code, - 'msg':self.request.text}) - - #def update(self): - # """Update a flow to a Node on the Controller - # """ - # raise NotImplementedError("update()") - - def delete(self, node_id, flow_name): - """Delete a flow to a Node on the Controller - - Mandatory Arguments: - node_id - the switch dpid - flow_name - the specifically named flow on that switch - """ - if hasattr(self, 'request'): - del self.request - - self.odl.prepare(self.__app, '/' + 'OF/' + node_id + '/' + - flow_name + '/') - self.request = requests.delete(url=self.odl.url, auth=self.odl.auth) - - # note, if you wanted to pass in a flowConfig style dictionary, - # this is how you would do it. This is what I did initially, but - # it seemed clunky to pass in an entire flow. - #self.prepare(self.__app, '/' + flow['node']['@type'] + '/' + - # flow['node']['@id'] + '/' + flow['name'] + '/') - - if self.request.status_code != 200: - raise OpenDaylightError({'url':self.odl.url, - 'http_code':self.request.status_code, - 'msg':self.request.text}) - - -#pylint: disable=R0921 -class OpenDaylightNode(object): - """A way to talk to the OpenDaylight Switch Manager REST API - - OpenDaylight.odl holds an OpenDaylight object containing details - on how to communicate with the controller. - - OpenDaylightNode.request holds a Requests object for the REST - session. Take a look at the Requests documentation for all of - the methods available, but here are a few handy examples: - OpenDaylightNode.request.status_code - returns the http code - OpenDaylightNode.request.text - returns the response as text - - OpenDaylightNode.nodes holds a dictionary that corresponds to - the 'nodes' element in the OpenDaylight REST API. - - OpenDaylightNode.node_connectors holds a dictionary that corresponds to - the 'nodeConnectors' element in the OpenDaylight REST API. - - Note that we don't statically define what those fields are contained - in the 'nodes' or 'nodeConnectors' elements here in this object. - """ - - # Just a note that there are more functions available on - # the controller that could be implemented, but it is not - # clear at this time if that is useful - - def __init__(self, odl): - """Mandatory argument: - odl - an OpenDaylight object - """ - self.odl = odl - self.__app = 'switch' - self.nodes = None - self.node_connectors = None - self.request = None - - def get_nodes(self): - """Get information about Nodes on the Controller and stuffs the - result into the OpenDaylightNode.notes dictionary. - - """ - if hasattr(self, 'request'): - del self.request - if hasattr(self, 'nodes'): - del self.nodes - - self.odl.prepare(self.__app, '/nodes/') - self.request = requests.get(url=self.odl.url, auth=self.odl.auth) - - if self.request.status_code == 200: - self.nodes = self.request.json() - if 'nodeProperties' in self.nodes: - self.nodes = self.nodes.get('nodeProperties') - else: - raise OpenDaylightError({'url':self.odl.url, - 'http_code':self.request.status_code, - 'msg':self.request.text}) - - def get_node_connectors(self, node_id): - """Get information about NodeConnectors on the Controller and stuffs the - result into the OpenDaylightNode.node_connectors dictionary. - - Mandatory Arguments: - node_id - returns flows just for that switch dpid - """ - - if hasattr(self, 'request'): - del self.request - if hasattr(self, 'node_connectors'): - del self.node_connectors - - self.odl.prepare(self.__app, '/node/' + 'OF/' + node_id + '/') - self.request = requests.get(url=self.odl.url, auth=self.odl.auth) - if self.request.status_code == 200: - self.node_connectors = self.request.json() - if 'nodeConnectorProperties' in self.node_connectors: - self.node_connectors = self.node_connectors.get( - 'nodeConnectorProperties') - else: - raise OpenDaylightError({'url':self.odl.url, - 'http_code':self.request.status_code, - 'msg':self.request.text}) - - def save(self): - """Save current switch configurations - - The REST API documentation says: - "Save the current switch configurations", but I am not sure what - that actually means. If you think you do, then here you go. - """ - - if hasattr(self, 'request'): - del self.request - - self.odl.prepare(self.__app, '/switch-config/') - self.request = requests.post(url=self.odl.url, auth=self.odl.auth) - if self.request.status_code != 200: - raise OpenDaylightError({'url':self.odl.url, - 'http_code':self.request.status_code, - 'msg':self.request.text}) - - def delete_node_property(self): - """Delete a property of a Node on the Controller - """ - raise NotImplementedError("delete_node_property()") - - def add_node_property(self): - """Add a property of a Node on the Controller - """ - raise NotImplementedError("add_node_property()") - - def delete_node_connector_property(self): - """Delete a property of a Node on the Controller - """ - raise NotImplementedError("delete_node_connector_property()") - - def add_node_connector_property(self): - """Add a property of a Node on the Controller - """ - raise NotImplementedError("add_node_connector_property()") - - -class OpenDaylightError(Exception): - """OpenDaylight Exception Class - """ - pass From 36b44aea1cff1d4ccbeae75c231942b5e536e361 Mon Sep 17 00:00:00 2001 From: Chri Small Date: Fri, 21 Nov 2014 05:15:11 +0000 Subject: [PATCH 27/81] Added Circle.yml --- circle.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 circle.yml diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..b30fb86 --- /dev/null +++ b/circle.yml @@ -0,0 +1,3 @@ +tests: + override: + - nosetests python/t From 65818f915d4c020c68eda11f241d940cefbbb585 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 05:19:51 +0000 Subject: [PATCH 28/81] Fixed typo in circle.yml --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index b30fb86..4cef257 100644 --- a/circle.yml +++ b/circle.yml @@ -1,3 +1,3 @@ -tests: +test: override: - nosetests python/t From b2462ba9d3e78c146cb9fbbb5c8a2705ae03d8db Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 18:38:41 +0000 Subject: [PATCH 29/81] Added build_dir to circle.yml --- circle.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 4cef257..b48607c 100644 --- a/circle.yml +++ b/circle.yml @@ -1,3 +1,5 @@ +general: + build_dir: python test: override: - - nosetests python/t + - nosetests t//SciPassTest_mininet.py From a38586e19d3824c8cb33b913b6adf7f554185030 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 20:09:03 +0000 Subject: [PATCH 30/81] Added requirements.txt --- requirements.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4d0fee7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +Routes>=1.12.3 +bottle>=0.10.6 +coverage>=3.7.1 +hp-sdn-client>=1.1.0 +lxml>=2.3.2 +mock>=1.0.1 From 02ef4310e1fd1a0a6ef6c352b1de8153e5288654 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 20:18:54 +0000 Subject: [PATCH 31/81] Added requirements.txt to python dir to see if Circlci will find it --- python/requirements.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 python/requirements.txt diff --git a/python/requirements.txt b/python/requirements.txt new file mode 100644 index 0000000..4d0fee7 --- /dev/null +++ b/python/requirements.txt @@ -0,0 +1,6 @@ +Routes>=1.12.3 +bottle>=0.10.6 +coverage>=3.7.1 +hp-sdn-client>=1.1.0 +lxml>=2.3.2 +mock>=1.0.1 From 9107efa3e31ed7d1e7c331330704f8d5837a2508 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 20:31:34 +0000 Subject: [PATCH 32/81] More requirements added --- python/requirements.txt | 1 + requirements.txt | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 requirements.txt diff --git a/python/requirements.txt b/python/requirements.txt index 4d0fee7..ff1864f 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -4,3 +4,4 @@ coverage>=3.7.1 hp-sdn-client>=1.1.0 lxml>=2.3.2 mock>=1.0.1 +ipaddr>=2.1.10 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 4d0fee7..0000000 --- a/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -Routes>=1.12.3 -bottle>=0.10.6 -coverage>=3.7.1 -hp-sdn-client>=1.1.0 -lxml>=2.3.2 -mock>=1.0.1 From 822da0d62fbf75b03b6dd408646f48755978123b Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 20:36:06 +0000 Subject: [PATCH 33/81] Added libxml2 to requirements --- python/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/python/requirements.txt b/python/requirements.txt index ff1864f..d92a562 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -5,3 +5,4 @@ hp-sdn-client>=1.1.0 lxml>=2.3.2 mock>=1.0.1 ipaddr>=2.1.10 +libxml2>=2.6 From 7a07d69b97ed113ae5b696948c53b6a9e649c7a4 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 20:48:50 +0000 Subject: [PATCH 34/81] Added apt-get install python-libxml2 to circle.yml --- circle.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/circle.yml b/circle.yml index b48607c..9f264c5 100644 --- a/circle.yml +++ b/circle.yml @@ -1,5 +1,8 @@ general: build_dir: python +dependencies: + pre: + - apt-get install python-libxml2 test: override: - nosetests t//SciPassTest_mininet.py From 87707666f3702160fa8e6405ae4f7bc32c316436 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 20:50:50 +0000 Subject: [PATCH 35/81] Added sudo apt-get install --- circle.yml | 2 +- python/requirements.txt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index 9f264c5..8fc1bac 100644 --- a/circle.yml +++ b/circle.yml @@ -2,7 +2,7 @@ general: build_dir: python dependencies: pre: - - apt-get install python-libxml2 + - sudo apt-get install python-libxml2 test: override: - nosetests t//SciPassTest_mininet.py diff --git a/python/requirements.txt b/python/requirements.txt index d92a562..ff1864f 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -5,4 +5,3 @@ hp-sdn-client>=1.1.0 lxml>=2.3.2 mock>=1.0.1 ipaddr>=2.1.10 -libxml2>=2.6 From 71c1ec8e7c4576ab9f1d82f46533996afdc79aa1 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 20:59:04 +0000 Subject: [PATCH 36/81] More circle.yml --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 8fc1bac..df5c93c 100644 --- a/circle.yml +++ b/circle.yml @@ -2,7 +2,7 @@ general: build_dir: python dependencies: pre: - - sudo apt-get install python-libxml2 + - sudo apt-get install -y python-libxml2 test: override: - nosetests t//SciPassTest_mininet.py From 5a9e44252118e90b444b8f82f1c17b7141065de4 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 13:40:10 -0800 Subject: [PATCH 37/81] Added coveralls badge to Readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 9146ca1..faf02cd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ SciPass ======= + +[![Coverage Status](https://img.shields.io/coveralls/chrissmall22/SciPass.svg)](https://coveralls.io/r/chrissmall22/SciPass) + SciPass is a SDN powered Science DMZ and IDS Load Balancer. From bb31f96245303b914c55a5525f96538a6e8c4fd7 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 13:44:31 -0800 Subject: [PATCH 38/81] Added .travis.yml file --- .travis.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..580a6b9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: python +python: + - "2.7" + - "2.6" + +install: + "pip install -r python/requirements.txt" + +script: + - make test_mininet + +after_success: + - coveralls From ffe82bc89ae16b997f8b4b4faae9620c861d0a79 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 13:49:42 -0800 Subject: [PATCH 39/81] Travis-ci badge in Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index faf02cd..ba94aa2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ SciPass ======= - +https://travis-ci.org/chrissmall22/SciPass.svg?branch=master [![Coverage Status](https://img.shields.io/coveralls/chrissmall22/SciPass.svg)](https://coveralls.io/r/chrissmall22/SciPass) SciPass is a SDN powered Science DMZ and IDS Load Balancer. From 64e0670bf51027c99de0aa7d860c9d12733ee6da Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 13:56:54 -0800 Subject: [PATCH 40/81] Fixed Travis image link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ba94aa2..4ced1aa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ SciPass ======= -https://travis-ci.org/chrissmall22/SciPass.svg?branch=master +[![Travis Build status](https://travis-ci.org/chrissmall22/SciPass.svg?branch=master)](https://travis-ci.org/chrissmall22/SciPass) [![Coverage Status](https://img.shields.io/coveralls/chrissmall22/SciPass.svg)](https://coveralls.io/r/chrissmall22/SciPass) SciPass is a SDN powered Science DMZ and IDS Load Balancer. From 33a01bf17946ac6ac1c52fe96c72f526ba8e5173 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 22:30:38 +0000 Subject: [PATCH 41/81] Changed builds to 2.7 and 3.3 Python in travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 580a6b9..9a90144 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: python python: - "2.7" - - "2.6" + - "3.3" install: "pip install -r python/requirements.txt" From e27cb0ed1e1e9cab9c580b10a34ef56b617fff64 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 14:45:09 -0800 Subject: [PATCH 42/81] Added 2.6 and changed the coverage call in the MakeFile --- .travis.yml | 1 + Makefile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9a90144..9d9850c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: python python: + - "2.6" - "2.7" - "3.3" diff --git a/Makefile b/Makefile index 6427f91..3dbb589 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ test: cd python; coverage run --source=./ --omit=__init__.py,*Test.py,t/SciPass.py t/SciPassTest.py; coverage report -m; coverage xml;coverage annotate; coverage html; test_mininet: - cd python; python -m coverage run --source=./ --omit=__init__.py,*Test.py,t/SciPass.py t/SciPassTest_mininet.py; python -m coverage report -m; python -m coverage xml;python -m coverage annotate; python -m coverage html; + cd python; coverage run --source=./ --omit=__init__.py,*Test.py,t/SciPass.py t/SciPassTest_mininet.py; coverage report -m; python -m coverage xml;coverage annotate; coverage html; dist: rm -rf dist/$(NAME)-$(VERSION) From c10f379222d7e4794d52d55ad5a3afd6afb10127 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 14:46:50 -0800 Subject: [PATCH 43/81] Add Coveralls install to .travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 9d9850c..54feb9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ python: install: "pip install -r python/requirements.txt" + "pip install coveralls" script: - make test_mininet From 02d8dfce378c7e305ccec41c1af11cba2b7d8b76 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 14:52:12 -0800 Subject: [PATCH 44/81] Fixed typo in travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 54feb9f..af4b810 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,8 @@ python: - "3.3" install: - "pip install -r python/requirements.txt" - "pip install coveralls" + - pip install -r python/requirements.txt + - pip install coveralls script: - make test_mininet From 91e2520fd7bb42c0e4892c7f9f9238cfcef212b8 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 15:03:42 -0800 Subject: [PATCH 45/81] Removed Python 3.3 because ipaddr module won't build --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index af4b810..c1c5abb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: python python: - "2.6" - "2.7" - - "3.3" install: - pip install -r python/requirements.txt From e20c79ea0a67d34f6e12236df3f34a38bb2b8b81 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 15:18:18 -0800 Subject: [PATCH 46/81] Removed lxml from requirements --- python/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/python/requirements.txt b/python/requirements.txt index ff1864f..374dfa4 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -2,6 +2,5 @@ Routes>=1.12.3 bottle>=0.10.6 coverage>=3.7.1 hp-sdn-client>=1.1.0 -lxml>=2.3.2 mock>=1.0.1 ipaddr>=2.1.10 From ee67d30c806a510724820e7802a81e46b1a30092 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 18:51:14 -0800 Subject: [PATCH 47/81] Added libxml2-dev --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index c1c5abb..55f6e12 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,11 @@ python: - "2.6" - "2.7" +before_install: + - sudo apt-get update -qq + - sudo apt-get install -y libxml2-dev + - sudo apt-get install -y python-libxml2 + install: - pip install -r python/requirements.txt - pip install coveralls From 084c4b82973a07976e52a8e78e9361d40c104bed Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 18:58:12 -0800 Subject: [PATCH 48/81] Added python-dev and libxstl-dev to travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 55f6e12..fa43bfa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ python: before_install: - sudo apt-get update -qq - sudo apt-get install -y libxml2-dev - - sudo apt-get install -y python-libxml2 + - sudo apt-get install -y python-libxml2 python-dev libxslt-dev install: - pip install -r python/requirements.txt From 8c32ee5667173f2c8fed75d58c5fb3b6e6531c29 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 19:23:41 -0800 Subject: [PATCH 49/81] More python dependanicies for the travis file --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fa43bfa..088571f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,7 @@ python: before_install: - sudo apt-get update -qq - sudo apt-get install -y libxml2-dev - - sudo apt-get install -y python-libxml2 python-dev libxslt-dev - + - sudo apt-get install -y python-libxml2 python-dev libxslt-dev python-all python-mock python-ipaddr install: - pip install -r python/requirements.txt - pip install coveralls From ed6dcab08523291fbbc8f4ca1659d59dfe64f906 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 19:38:03 -0800 Subject: [PATCH 50/81] Added python-lxml to travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 088571f..d808d30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ python: before_install: - sudo apt-get update -qq - - sudo apt-get install -y libxml2-dev + - sudo apt-get install -y libxml2-dev python-lxml - sudo apt-get install -y python-libxml2 python-dev libxslt-dev python-all python-mock python-ipaddr install: - pip install -r python/requirements.txt From e99a2c4fa4a4cbd34d7891f5ff1e80f7a703a7b2 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 20:11:20 -0800 Subject: [PATCH 51/81] Remove packages to try to debug libxml2 issues --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d808d30..343fe16 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,7 @@ python: before_install: - sudo apt-get update -qq - - sudo apt-get install -y libxml2-dev python-lxml - - sudo apt-get install -y python-libxml2 python-dev libxslt-dev python-all python-mock python-ipaddr + - sudo apt-get install -y libxml2-dev python-libxml2 install: - pip install -r python/requirements.txt - pip install coveralls From 454a7494ff128f636973b93a26a28a56c0a71638 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 20:21:28 -0800 Subject: [PATCH 52/81] Remove the python-libxml2 dependancy --- circle.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/circle.yml b/circle.yml index df5c93c..b48607c 100644 --- a/circle.yml +++ b/circle.yml @@ -1,8 +1,5 @@ general: build_dir: python -dependencies: - pre: - - sudo apt-get install -y python-libxml2 test: override: - nosetests t//SciPassTest_mininet.py From eef49d563d5bc05e8ad080c7b297740d4e4ccc7c Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 20:52:49 -0800 Subject: [PATCH 53/81] ipddr version change --- python/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/requirements.txt b/python/requirements.txt index 374dfa4..f0e7855 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -3,4 +3,4 @@ bottle>=0.10.6 coverage>=3.7.1 hp-sdn-client>=1.1.0 mock>=1.0.1 -ipaddr>=2.1.10 +ipaddr>=2.1.0 From fe398932506959f6d036e7b0c3298debcb7a53b0 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 21:57:54 -0800 Subject: [PATCH 54/81] Build libxml python from source in .travis.yml --- .travis.yml | 3 +++ circle.yml | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 343fe16..004101a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,9 @@ python: before_install: - sudo apt-get update -qq - sudo apt-get install -y libxml2-dev python-libxml2 + - wget ftp://xmlsoft.org/libxml2/python/libxml2-python-2.6.15.tar.gz + - tar -xzvf libxml2-python-2.6.15.tar.gz + - cd libxml2-python-2.6.15 && python setup.py install install: - pip install -r python/requirements.txt - pip install coveralls diff --git a/circle.yml b/circle.yml index b48607c..88a6e36 100644 --- a/circle.yml +++ b/circle.yml @@ -1,5 +1,12 @@ general: build_dir: python +install: + - wget ftp://xmlsoft.org/libxml2/python/libxml2-python-2.6.15.tar.gz + - tar -xzvf libxml2-python-2.6.15.tar.gz + - cd libxml2-python-2.6.15 && python setup.py install +dependencies: + pre: + - sudo apt-get install -y libxml2-dev libslt1 test: override: - - nosetests t//SciPassTest_mininet.py + - nosetests t/SciPassTest_mininet.py From b74b70091693e68050198e23ed238be9d03d0c6d Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 22:01:24 -0800 Subject: [PATCH 55/81] Fixed cd in .travis.xml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 004101a..82170b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ before_install: - tar -xzvf libxml2-python-2.6.15.tar.gz - cd libxml2-python-2.6.15 && python setup.py install install: + - cd .. - pip install -r python/requirements.txt - pip install coveralls From 836f9d47308e2a2a18e1ca81c102c56b0710d2fc Mon Sep 17 00:00:00 2001 From: Chris Small Date: Fri, 21 Nov 2014 22:23:31 -0800 Subject: [PATCH 56/81] Moved coverage call to .travis.yml file --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 82170b4..6477184 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,8 @@ install: - pip install coveralls script: - - make test_mininet + - cd python + - coverage run --source=./ --omit=__init__.py,*Test.py,t/SciPass.py t/SciPassTest_mininet.py after_success: - - coveralls + coveralls From d63388653698a3345a1abaaaef03af3244603da2 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Sat, 22 Nov 2014 00:18:21 -0800 Subject: [PATCH 57/81] Moved HPVAN back to a metaclass --- python/HPVAN.py | 23 ++++++++++++++++++----- python/SciPass_start.py | 10 +++------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/python/HPVAN.py b/python/HPVAN.py index 1141b4d..fe0b72b 100644 --- a/python/HPVAN.py +++ b/python/HPVAN.py @@ -51,12 +51,16 @@ def test(self): def run_server(self, host, port): run('scipass', host=host, port=port) + class HPVAN(type): - def __init__(self, controller, port, username, password): - super(HPVAN,self).__init__(controller, port, username) + def __init__(self, *args, **kwargs): + super(HPVAN, self).__init__(*args, **kwargs) self.datapaths = {} + self.username = "sdn" + self.password = "skyline" + self.controller = "15.126.229.78" logging.basicConfig() self.logger = logging.getLogger(__name__) @@ -76,9 +80,9 @@ def __init__(self, controller, port, username, password): self.api = api - self.logger.debug("Connecting to VAN controller" + controller) - auth = hp.XAuthToken(user=username, password=password, server=controller) - api = hp.Api(controller=controller, auth=auth) + self.logger.debug("Connecting to VAN controller" + self.controller) + auth = hp.XAuthToken(user=self.username, password=self.password, server=self.controller) + api = hp.Api(controller=self.controller, auth=auth) def start_rest_interface(self, host, port): @@ -216,3 +220,12 @@ def flushRules(self, dpid): datapath.send_msg(mod) +def start_scipass_van(): + + scipass = HPVAN() + scipass.start_rest_interface('localhost',8080) + + +if __name__ == '__main__': + start_scipass_van() + diff --git a/python/SciPass_start.py b/python/SciPass_start.py index a9f0947..f16bce6 100644 --- a/python/SciPass_start.py +++ b/python/SciPass_start.py @@ -15,16 +15,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import HPVAN as van +import HPVAN def start_scipass_van(): - controller = "127.0.0.1" - port = 8080 - username = "sdn" - password = "skyline" - van.connect_controller(controller,port) - van.start_rest_interface('localhost',8080) + scipass = HPVAN() + scipass.start_rest_interface('localhost',8080) if __name__ == '__main__': From 690ae2d985cc4473545b73fb0a4f96c41f8ef228 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Sat, 22 Nov 2014 16:43:29 -0800 Subject: [PATCH 58/81] Added test_struct file to test class reorg --- python/test_struct.py | 148 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 python/test_struct.py diff --git a/python/test_struct.py b/python/test_struct.py new file mode 100644 index 0000000..4863c20 --- /dev/null +++ b/python/test_struct.py @@ -0,0 +1,148 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2014 The Trustees of Indiana University +# +# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 +# +# 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 logging +from bottle import run, route + +import oslo.config.cfg as cfg +import hpsdnclient as hp + +from SciPass import SciPass + +ETH_TYPE_IP = 0x0800 + + +""" + Forwarding rule Priorities + BlackList + WhiteList + Balancer + Default +""" + + +class TestRest(): + def __init__(self, api): + self.api = api + + #GET /scipass/test + @route('/scipass/test') + def test(self): + return "test" + + @route('/scipass/flows') + def test(self): + flows = self.api.get_food_flows + return flows + + +class TestVAN(): + + def __init__(self, user, pw, controller): + + logging.basicConfig() + self.logger = logging.getLogger(__name__) + + #--- register for configuration options + self.CONF = cfg.CONF + self.CONF.register_opts([ + cfg.StrOpt('SciPassConfig',default='../etc/SciPass/SciPass.xml', + help='where to find the SciPass config file'), + ]) + + self.logger.error("Starting SciPass") + # + api = SciPass(logger = self.logger, + config_file = self.CONF.SciPassConfig ) + api.registerForwardingStateChangeHandler(self.changeSwitchForwardingState) + + self.api = api + + self.logger.debug("Connecting to VAN controller" + controller) + auth = hp.XAuthToken(user=user, password=pw, server=controller) + api = hp.Api(controller=controller, auth=auth) + + run("scipass", host="127.0.0.1", port=8080) + + + + def changeSwitchForwardingState(self, dpid=None, header=None, actions=None, command=None, idle_timeout=None, hard_timeout=None, priority=1): + self.logger.debug("Changing switch forwarding state") + + + obj = {} + + if(header.has_key('dl_type')): + if(header['dl_type'] == None): + obj['dl_type'] = None + else: + obj['dl_type'] = int(header['dl_type']) + else: + obj['dl_type'] = ETH_TYPE_IP + + if(header.has_key('phys_port')): + obj['in_port'] = int(header['phys_port']) + else: + obj['in_port'] = None + + if(header.has_key('nw_src')): + obj['nw_src'] = int(header['nw_src']) + else: + obj['nw_src'] = None + + if(header.has_key('nw_src_mask')): + obj['nw_src_mask'] = int(header['nw_src_mask']) + else: + obj['nw_src_mask'] = None + + if(header.has_key('nw_dst')): + obj['nw_dst'] = int(header['nw_dst']) + else: + obj['nw_dst'] = None + + if(header.has_key('nw_dst_mask')): + obj['nw_dst_mask'] = int(header['nw_dst_mask']) + else: + obj['nw_dst_mask'] = None + + if(header.has_key('tp_src')): + obj['tp_src'] = int(header['tp_src']) + else: + obj['tp_src'] = None + + if(header.has_key('tp_dst')): + obj['tp_dst'] = int(header['tp_dst']) + else: + obj['tp_dst'] = None + + + def flushRules(self, dpid): + print dpid + + + +def start_scipass_van(): + + username = "sdn" + password = "skyline" + controller = "15.126.229.78" + scipass = TestVAN(username, password, controller) + + + +if __name__ == '__main__': + start_scipass_van() + From 4944b6ea8db9259eb5df3a8702a20485d2de632b Mon Sep 17 00:00:00 2001 From: Chris Small Date: Sat, 22 Nov 2014 22:23:41 -0800 Subject: [PATCH 59/81] Fixed circle.yml --- circle.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/circle.yml b/circle.yml index 88a6e36..f55f1f4 100644 --- a/circle.yml +++ b/circle.yml @@ -1,12 +1,12 @@ general: build_dir: python -install: - - wget ftp://xmlsoft.org/libxml2/python/libxml2-python-2.6.15.tar.gz - - tar -xzvf libxml2-python-2.6.15.tar.gz - - cd libxml2-python-2.6.15 && python setup.py install dependencies: pre: - sudo apt-get install -y libxml2-dev libslt1 + - wget ftp://xmlsoft.org/libxml2/python/libxml2-python-2.6.15.tar.gz + - tar -xzvf libxml2-python-2.6.15.tar.gz + - cd libxml2-python-2.6.15 && python setup.py install + - cd .. test: override: - nosetests t/SciPassTest_mininet.py From 5a866d5ad4c57b526bd2f79d2096f8ef412781b9 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Sun, 23 Nov 2014 06:34:03 +0000 Subject: [PATCH 60/81] Trying new HPVAN setup with init call --- python/HPVAN.py | 18 ++++++++++-------- python/SciPass_start.py | 7 +++++-- python/test_struct.py | 11 +++++++---- 3 files changed, 22 insertions(+), 14 deletions(-) mode change 100644 => 100755 python/test_struct.py diff --git a/python/HPVAN.py b/python/HPVAN.py index fe0b72b..a8a1b8c 100644 --- a/python/HPVAN.py +++ b/python/HPVAN.py @@ -15,7 +15,8 @@ # limitations under the License. import logging -from bottle import run, route +from bottle import run as bottle_run +from bottle import route import oslo.config.cfg as cfg import hpsdnclient as hp @@ -52,10 +53,9 @@ def run_server(self, host, port): run('scipass', host=host, port=port) -class HPVAN(type): +class HPVAN(): - def __init__(self, *args, **kwargs): - super(HPVAN, self).__init__(*args, **kwargs) + def __init__(self, user, pw, controller): self.datapaths = {} self.username = "sdn" @@ -85,9 +85,9 @@ def __init__(self, *args, **kwargs): api = hp.Api(controller=self.controller, auth=auth) - def start_rest_interface(self, host, port): - - SciPassRest(self.api).run_server(host, port) + def start_rest(self, host, port): + module = "SciPass" + bottle_run(module,host=host,port=port) @@ -222,7 +222,9 @@ def flushRules(self, dpid): def start_scipass_van(): - scipass = HPVAN() + log = logging.getLogger(__name__) + + scipass = HPVAN(logger=log,config='/etc/SciPass/SciPass.xml') scipass.start_rest_interface('localhost',8080) diff --git a/python/SciPass_start.py b/python/SciPass_start.py index f16bce6..7ce8f3d 100644 --- a/python/SciPass_start.py +++ b/python/SciPass_start.py @@ -19,8 +19,11 @@ def start_scipass_van(): - scipass = HPVAN() - scipass.start_rest_interface('localhost',8080) + username = "sdn" + password = "skyline" + controller = "15.126.229.78" + van = HPVAN(username, password, controller) + van.start_rest("127.0.0.1",8090) if __name__ == '__main__': diff --git a/python/test_struct.py b/python/test_struct.py old mode 100644 new mode 100755 index 4863c20..1539b0f --- a/python/test_struct.py +++ b/python/test_struct.py @@ -15,7 +15,8 @@ # limitations under the License. import logging -from bottle import run, route +from bottle import run as bottle_run +from bottle import route import oslo.config.cfg as cfg import hpsdnclient as hp @@ -75,9 +76,10 @@ def __init__(self, user, pw, controller): auth = hp.XAuthToken(user=user, password=pw, server=controller) api = hp.Api(controller=controller, auth=auth) - run("scipass", host="127.0.0.1", port=8080) - + def start_rest(self, host, port): + module = "SciPass" + bottle_run(module, host=host, port=port) def changeSwitchForwardingState(self, dpid=None, header=None, actions=None, command=None, idle_timeout=None, hard_timeout=None, priority=1): self.logger.debug("Changing switch forwarding state") @@ -139,7 +141,8 @@ def start_scipass_van(): username = "sdn" password = "skyline" controller = "15.126.229.78" - scipass = TestVAN(username, password, controller) + van = TestVAN(username, password, controller) + van.start_rest("127.0.0.1",8090) From afacb60b3668b74885682d1fb1602d312d798362 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Sun, 23 Nov 2014 07:17:55 +0000 Subject: [PATCH 61/81] Fixed SciPassRest start_rest call --- python/HPVAN.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/HPVAN.py b/python/HPVAN.py index a8a1b8c..54fcf83 100644 --- a/python/HPVAN.py +++ b/python/HPVAN.py @@ -222,10 +222,11 @@ def flushRules(self, dpid): def start_scipass_van(): - log = logging.getLogger(__name__) - - scipass = HPVAN(logger=log,config='/etc/SciPass/SciPass.xml') - scipass.start_rest_interface('localhost',8080) + username = "sdn" + password = "skyline" + controller = "15.126.229.78" + scipass = HPVAN(username, password, controller) + scipass.start_rest('localhost',8090) if __name__ == '__main__': From 6f09129f01fc6e8ce384fa2c9579975de0597814 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Sat, 22 Nov 2014 23:20:18 -0800 Subject: [PATCH 62/81] Removed libslt2 from circl.yml dependancies --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index f55f1f4..797c085 100644 --- a/circle.yml +++ b/circle.yml @@ -2,7 +2,7 @@ general: build_dir: python dependencies: pre: - - sudo apt-get install -y libxml2-dev libslt1 + - sudo apt-get install -y libxml2-dev - wget ftp://xmlsoft.org/libxml2/python/libxml2-python-2.6.15.tar.gz - tar -xzvf libxml2-python-2.6.15.tar.gz - cd libxml2-python-2.6.15 && python setup.py install From fd8e9b8a104c4500aba42d3706b82ec5102ac1fa Mon Sep 17 00:00:00 2001 From: Chris Small Date: Sun, 23 Nov 2014 00:36:12 -0800 Subject: [PATCH 63/81] Added REST calls --- python/HPVAN.py | 100 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 87 insertions(+), 13 deletions(-) diff --git a/python/HPVAN.py b/python/HPVAN.py index 54fcf83..f0c55b4 100644 --- a/python/HPVAN.py +++ b/python/HPVAN.py @@ -17,13 +17,19 @@ import logging from bottle import run as bottle_run from bottle import route +from webob import Response import oslo.config.cfg as cfg import hpsdnclient as hp +import json from SciPass import SciPass ETH_TYPE_IP = 0x0800 +_DPID_LEN = 16 +_DPID_LEN_STR = str(_DPID_LEN) +_DPID_FMT = '%0' + _DPID_LEN_STR + 'x' +DPID_PATTERN = r'[0-9a-f]{%d}' % _DPID_LEN """ @@ -38,19 +44,87 @@ class SciPassRest(): def __init__(self, api): self.api = api - - #GET /scipass/test - @route('/scipass/test') - def test(self): - return "test" - - @route('/scipass/flows') - def test(self): - flows = self.api.get_food_flows - return flows - - def run_server(self, host, port): - run('scipass', host=host, port=port) + self.logger = api.logger + + #POST /scipass/flows/good_flow + @route('/scipass/flows/good_flow') + def good_flow(self, req): + try: + obj = eval(req.body) + except SyntaxError: + self.logger.error("Syntax Error processing good_flow signal %s", req.body) + return Response(status=400) + + result = self.api.good_flow(obj) + return Response(content_type='application/json',body=json.dumps(result)) + + #POST /scipass/flows/bad_flow + @route('scipass', '/scipass/flows/bad_flow', methods=['PUT']) + def bad_flow(self, req): + try: + obj = eval(req.body) + except SyntaxError: + self.logger.error("Syntax Error processing bad_flow signal %s", req.body) + return Response(status=400) + result = self.api.bad_flow(obj) + return Response(content_type='application/json',body=json.dumps(result)) + + #GET /scipass/flows/get_good_flows + @route('scipass', '/scipass/flows/get_good_flows', methods=['GET']) + def get_good_flows(self, req): + result = self.api.get_good_flows() + return Response(content_type='application/json',body=json.dumps(result)) + + #GET /scipass/flows/get_bad_flows + @route('scipass', '/scipass/flows/get_bad_flows', methods=['GET']) + def get_bad_flows(self, req): + result = self.api.get_bad_flows() + return Response(content_type='application/json',body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/flows', methods=['GET'], requirements= {'dpid': DPID_PATTERN}) + def get_switch_flows(self, req, **kwargs): + result = self.api.getSwitchFlows(dpid=kwargs['dpid']) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/sensor/load', methods=['PUT']) + def update_sensor_load(self, req): + try: + obj = eval(req.body) + except SyntaxError: + self.logger.error("Syntax Error processing update_sensor_status signal %s", req.body) + return Response(status=400) + result = self.api.setSensorStatus(obj['sensor_id'],obj['load']) + return Response(content_type='application/json',body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensor/{sensor_id}', methods=['GET'], requirements= {'dpid': DPID_PATTERN}) + def get_sensor_load(self, req, **kwargs): + result = self.api.getSensorStatus(dpid=kwargs['dpid'], domain=kwargs['domain'], sensor_id=kwargs['sensor_id']) + return Response(content_type='application/json',body=json.dumps(result)) + + @route('scipass', '/scipass/switches', methods=['GET']) + def get_switches(self, req): + result = self.api.getSwitches() + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensors', methods=['GET'], requirements = {'dpid': DPID_PATTERN}) + def get_domain_sensors(self, req, **kwargs): + result = self.api.getDomainSensors(dpid = kwargs['dpid'], domain = kwargs['domain'] ) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domains', methods=['GET'], requirements = {'dpid': DPID_PATTERN}) + def get_switch_domains(self, req, **kwargs): + result = self.api.getSwitchDomains(dpid=kwargs['dpid']) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}', methods=['GET'], requirements = {'dpid': DPID_PATTERN}) + def get_domain_status(self, req, **kwargs): + result = self.api.getDomainStatus(dpid = kwargs['dpid'], domain = kwargs['domain']) + return Response(content_type='application/json', body=json.dumps(result)) + + @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/flows',methods=['GET'],requirements = {'dpid': DPID_PATTERN}) + def get_domain_flows(self,req, **kwargs): + result = self.api.getDomainFlows(dpid = kwargs['dpid'], domain = kwargs['domain']) + return Response(content_type='application/json', body=json.dumps(result)) class HPVAN(): From 9fea91666c313741ffd919cd8be71e4d055b0dc3 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Sun, 23 Nov 2014 22:42:53 -0800 Subject: [PATCH 64/81] Added new REST interface test --- circle.yml | 2 +- python/t/SciPassTest_mininet_REST.py | 64 ++++++++++++++++++++++++++++ python/t/resources/good_flow | 1 + 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 python/t/SciPassTest_mininet_REST.py create mode 100644 python/t/resources/good_flow diff --git a/circle.yml b/circle.yml index 797c085..2db5979 100644 --- a/circle.yml +++ b/circle.yml @@ -2,7 +2,7 @@ general: build_dir: python dependencies: pre: - - sudo apt-get install -y libxml2-dev + - sudo apt-get install -y libxml2-dev libxsl - wget ftp://xmlsoft.org/libxml2/python/libxml2-python-2.6.15.tar.gz - tar -xzvf libxml2-python-2.6.15.tar.gz - cd libxml2-python-2.6.15 && python setup.py install diff --git a/python/t/SciPassTest_mininet_REST.py b/python/t/SciPassTest_mininet_REST.py new file mode 100644 index 0000000..6ec74a6 --- /dev/null +++ b/python/t/SciPassTest_mininet_REST.py @@ -0,0 +1,64 @@ +# Version of the SciPass tests that intergrates a mininet instance +# Provides an easy way to test without a physical switch and/or with more complex topologies +import os.path +import unittest +from urlparse import urlparse + +import sys +sys.path.append(".") +from HPVAN import SciPassRest +from mock import patch + + +def fake_urlopen(url): + """ + A stub urlopen() implementation that load json responses from + the filesystem. + """ + # Map path from url to a file + parsed_url = urlparse(url) + resource_file = os.path.normpath('t/resources%s' % parsed_url.path) + # Must return a file-like object + return open(resource_file, mode='rb') + + +class SciPassRestTestCase(unittest.TestCase): + """Test case for the SCI Pass REST interface methods.""" + + def setUp(self): + #self.patcher = patch('client.urlopen', fake_urlopen) + #self.patcher.start() + self.scipassrest = SciPassRest() + + #def tearDown(self): + # self.patcher.stop() + + def test_good_flows(self): + """Push a sample good flow into rest interface then get the same result back""" + + id = 1 + req = '{"nw_src": "10.0.20.2/32", "nw_dst":"156.56.6.1/32", "tp_src":1, "tp_dst":2}' + flows = self.scipassrest.good_flow(req) + print "Flows %s" % flows + self.assertEquals(len(flows),2) + flow = flows[0] + self.assertEqual(int(flow['hard_timeout']),0) + self.assertEqual(int(flow['idle_timeout']),90) + self.assertEqual(flow['actions'],[{'type': 'output', 'port': '3'}]) + self.assertEqual(flow['header'],{'phys_port': 2, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_src': 167777282, 'tp_dst': 2, 'tp_src': 1, 'nw_dst': 2620917249}) + self.assertEqual(int(flow['priority']),900) + self.assertEqual(flow['command'],"ADD") + self.assertEqual(flow['dpid'],"%016x" % id) + flow = flows[1] + self.assertEqual(int(flow['hard_timeout']),0) + self.assertEqual(int(flow['idle_timeout']),90) + self.assertEqual(flow['actions'],[{'type': 'output', 'port': '2'}]) + self.assertEqual(flow['header'],{'phys_port': 3, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) + self.assertEqual(int(flow['priority']),900) + self.assertEqual(flow['command'],"ADD") + self.assertEqual(flow['dpid'],"%016x" % id) + + + + + diff --git a/python/t/resources/good_flow b/python/t/resources/good_flow new file mode 100644 index 0000000..7c1b460 --- /dev/null +++ b/python/t/resources/good_flow @@ -0,0 +1 @@ +{"nw_src": "10.0.20.2/32", "nw_dst":"156.56.6.1/32", "tp_src":1, "tp_dst":2} \ No newline at end of file From 6313cec61a9cf61fb8a6d48248d72d604f1f10dd Mon Sep 17 00:00:00 2001 From: Chris Small Date: Mon, 24 Nov 2014 07:15:28 +0000 Subject: [PATCH 65/81] Moved to nosetests in travis instead of coverage --- .travis.yml | 2 +- python/.coveragerc | 2 + python/requirements.txt | 1 + python/t/SciPassTest_mininet_REST.py | 250 +++++++++++++++++++++++---- 4 files changed, 224 insertions(+), 31 deletions(-) create mode 100644 python/.coveragerc diff --git a/.travis.yml b/.travis.yml index 6477184..4fad0db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ install: script: - cd python - - coverage run --source=./ --omit=__init__.py,*Test.py,t/SciPass.py t/SciPassTest_mininet.py + - nosetests --with-coverage t/SimpleBalancerTest.py t/SciPassTest_mininet.py t/SciPassTest.py after_success: coveralls diff --git a/python/.coveragerc b/python/.coveragerc new file mode 100644 index 0000000..ef8801d --- /dev/null +++ b/python/.coveragerc @@ -0,0 +1,2 @@ +[report] +omit=*mock*,*libxml2*,*json*,*ipaddr*,*encodings*,*xmlrunner*,t/*.py diff --git a/python/requirements.txt b/python/requirements.txt index f0e7855..15ae8da 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -4,3 +4,4 @@ coverage>=3.7.1 hp-sdn-client>=1.1.0 mock>=1.0.1 ipaddr>=2.1.0 +xmlrunner>=1.7.7 diff --git a/python/t/SciPassTest_mininet_REST.py b/python/t/SciPassTest_mininet_REST.py index 6ec74a6..5835b01 100644 --- a/python/t/SciPassTest_mininet_REST.py +++ b/python/t/SciPassTest_mininet_REST.py @@ -1,45 +1,199 @@ # Version of the SciPass tests that intergrates a mininet instance # Provides an easy way to test without a physical switch and/or with more complex topologies -import os.path -import unittest -from urlparse import urlparse + import sys sys.path.append(".") -from HPVAN import SciPassRest -from mock import patch +import pprint +import unittest +from mock import Mock +import logging +import ipaddr +import os +from SciPass import SciPass +import libxml2 + +logging.basicConfig() + + +class TestInit(unittest.TestCase): + + def test_valid_config(self): + api = SciPass( logger = logging.getLogger(__name__), + config = str(os.getcwd()) + "/t/etc/SciPass-mininet.xml" + ) + self.assertTrue(isinstance(api,SciPass)) + +# def test_no_config(self): +# self.assertRaises(libxml2.parserError,SciPass) +# +# api = SciPass( logger = logging.getLogger(__name__), +# config = str(os.getcwd()) + "/t/etc/no_config.xml" ) + -def fake_urlopen(url): - """ - A stub urlopen() implementation that load json responses from - the filesystem. - """ - # Map path from url to a file - parsed_url = urlparse(url) - resource_file = os.path.normpath('t/resources%s' % parsed_url.path) - # Must return a file-like object - return open(resource_file, mode='rb') +# def test_invalid_config(self): +# self.assertRaises(libxml2.parserError,SciPass) +# +# api = SciPass( logger = logging.getLogger(__name__), +# config = str(os.getcwd()) + "/t/etc/InvalidConfig.xml" ) + def test_switch_init(self): + api = SciPass( logger = logging.getLogger(__name__), + config = str(os.getcwd()) + "/t/etc/SciPass-mininet.xml" ) -class SciPassRestTestCase(unittest.TestCase): - """Test case for the SCI Pass REST interface methods.""" + #first setup the handler to get all the flows that were sent + flows = [] + def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): + flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) + api.registerForwardingStateChangeHandler(flowSent) + datapath = Mock(id=1) + api.switchJoined(datapath) + + self.assertTrue( len(flows) == 33) + #verify all of the 'flow details are set properly' + for flow in flows: + self.assertEquals(flow['dpid'], "%016x" % datapath.id) + self.assertEquals(flow['hard_timeout'], 0) + self.assertEquals(flow['idle_timeout'], 0) + + flow = flows[0] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 1, 'dl_type': None}) + self.assertEquals(flow['priority'], 5) + flow = flows[1] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':4, 'nw_dst': 167776512, 'nw_dst_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[2] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':1, 'nw_src': 167776512, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[3] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':4, 'nw_dst': 167776768, 'nw_dst_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[4] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':1, 'nw_src': 167776768, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[5] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 2, 'dl_type': None}) + self.assertEquals(flow['priority'], 5) + flow = flows[6] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 2}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':4, 'nw_dst': 167777024, 'nw_dst_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[7] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':2, 'nw_src': 167777024, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[8] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 2}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':4, 'nw_dst': 167777280, 'nw_dst_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[9] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port':2, 'nw_src': 167777280, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 10) + flow = flows[10] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}, {'type': 'output', 'port': 2}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 4, 'dl_type': None}) + self.assertEquals(flow['priority'], 3) + flow = flows[11] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 3}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 5, 'dl_type': None}) + self.assertEquals(flow['priority'], 10) + flow = flows[12] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 3, 'dl_type': None}) + self.assertEquals(flow['priority'], 10) + flow = flows[13] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '6'}, {'type': 'output', 'port': '4'}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 500) + flow = flows[14] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '6'}, {'type': 'output', 'port': '5'}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 3, 'nw_dst': 167776512, 'nw_dst_mask': 24}) + self.assertEquals(flow['priority'], 500) + flow = flows[15] + self.assertEquals(flow['actions'],[]) + self.assertEquals(flow['command'],"DELETE_STRICT") + self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 500) + flow = flows[16] + self.assertEquals(flow['actions'],[]) + self.assertEquals(flow['command'],"DELETE_STRICT") + self.assertEquals(flow['header'], {'phys_port': 3, 'nw_dst': 167776512, 'nw_dst_mask': 24}) + self.assertEquals(flow['priority'], 500) + flow = flows[17] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '8'}, {'type': 'output', 'port': '4'}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 500) + flow = flows[18] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '8'}, {'type': 'output', 'port': '5'}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 3, 'nw_dst': 167776512, 'nw_dst_mask': 24}) + self.assertEquals(flow['priority'], 500) + flow = flows[19] + self.assertEquals(flow['actions'],[{'type': 'output', 'port': '6'}, {'type': 'output', 'port': '4'}]) + self.assertEquals(flow['command'],"ADD") + self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776768, 'nw_src_mask': 24}) + self.assertEquals(flow['priority'], 500) + flow = flows[20] + +class TestFunctionality(unittest.TestCase): def setUp(self): - #self.patcher = patch('client.urlopen', fake_urlopen) - #self.patcher.start() - self.scipassrest = SciPassRest() + self.api = SciPass( logger = logging.getLogger(__name__), + config = str(os.getcwd()) + "/t/etc/SciPass-mininet.xml" ) + + def test_update_prefix_bw(self): + #first setup the handler to get all the flows that were sent + flows = [] + def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): + flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) + + self.api.registerForwardingStateChangeHandler(flowSent) + datapath = Mock(id=1) + self.api.switchJoined(datapath) - #def tearDown(self): - # self.patcher.stop() + self.assertEquals( len(flows), 33) + self.api.updatePrefixBW("%016x" % datapath.id, ipaddr.IPv4Network("10.0.19.0/24"), 500,500) + self.assertTrue(self.api.getBalancer("%016x" % datapath.id, "R&E").getPrefixBW(ipaddr.IPv4Network("10.0.19.0/24")), 1000) + self.api.updatePrefixBW("%016x" % datapath.id, ipaddr.IPv4Network("10.0.17.0/24"), 500,500) + self.assertTrue(self.api.getBalancer("%016x" % datapath.id, "R&E").getPrefixBW(ipaddr.IPv4Network("10.0.17.0/24")), 1000) + - def test_good_flows(self): - """Push a sample good flow into rest interface then get the same result back""" + def test_good_flow(self): + flows = [] + def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): + flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) - id = 1 - req = '{"nw_src": "10.0.20.2/32", "nw_dst":"156.56.6.1/32", "tp_src":1, "tp_dst":2}' - flows = self.scipassrest.good_flow(req) - print "Flows %s" % flows + self.api.registerForwardingStateChangeHandler(flowSent) + datapath = Mock(id=1) + self.api.switchJoined(datapath) + #self.logger.error("testing good flow") + self.assertEquals(len(flows),33) + flows = [] + self.api.good_flow({"nw_src": "10.0.20.2/32", "nw_dst":"156.56.6.1/32", "tp_src":1, "tp_dst":2}) self.assertEquals(len(flows),2) flow = flows[0] self.assertEqual(int(flow['hard_timeout']),0) @@ -48,7 +202,7 @@ def test_good_flows(self): self.assertEqual(flow['header'],{'phys_port': 2, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_src': 167777282, 'tp_dst': 2, 'tp_src': 1, 'nw_dst': 2620917249}) self.assertEqual(int(flow['priority']),900) self.assertEqual(flow['command'],"ADD") - self.assertEqual(flow['dpid'],"%016x" % id) + self.assertEqual(flow['dpid'],"%016x" % datapath.id) flow = flows[1] self.assertEqual(int(flow['hard_timeout']),0) self.assertEqual(int(flow['idle_timeout']),90) @@ -56,9 +210,45 @@ def test_good_flows(self): self.assertEqual(flow['header'],{'phys_port': 3, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) self.assertEqual(int(flow['priority']),900) self.assertEqual(flow['command'],"ADD") - self.assertEqual(flow['dpid'],"%016x" % id) + self.assertEqual(flow['dpid'],"%016x" % datapath.id) + + + def test_bad_flow(self): + flows = [] + def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): + flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) + self.api.registerForwardingStateChangeHandler(flowSent) + datapath = Mock(id=1) + self.api.switchJoined(datapath) + #self.logger.error("testing good flow") + self.assertEquals(len(flows),33) + flows = [] + self.api.bad_flow({"nw_src": "10.0.20.2/32", "nw_dst":"156.56.6.1/32", "tp_src":1, "tp_dst":2}) + self.assertEquals(len(flows),2) + flow = flows[0] + self.assertEqual(int(flow['hard_timeout']),0) + self.assertEqual(int(flow['idle_timeout']),90) + self.assertEqual(flow['actions'],[]) + self.assertEqual(flow['header'],{'phys_port': 2, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_src': 167777282, 'tp_dst': 2, 'tp_src': 1, 'nw_dst': 2620917249}) + self.assertEqual(int(flow['priority']),900) + self.assertEqual(flow['command'],"ADD") + self.assertEqual(flow['dpid'],"%016x" % datapath.id) + flow = flows[1] + self.assertEqual(int(flow['hard_timeout']),0) + self.assertEqual(int(flow['idle_timeout']),90) + self.assertEqual(flow['actions'],[]) + self.assertEqual(flow['header'],{'phys_port': 3, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) + self.assertEqual(int(flow['priority']),900) + self.assertEqual(flow['command'],"ADD") + self.assertEqual(flow['dpid'],"%016x" % datapath.id) +def suite(): + suite = unittest.TestLoader().loadTestsFromTestCase(TestInit) + suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestFunctionality)) + return suite +if __name__ == '__main__': + unittest.TextTestRunner(verbosity=2).run(suite()) From 9066949d862f3763d4a62f5e8b1233160bf9d75c Mon Sep 17 00:00:00 2001 From: Chris Small Date: Sun, 23 Nov 2014 23:25:08 -0800 Subject: [PATCH 66/81] Added Python 3.x to travis builds --- .travis.yml | 3 +++ python/t/SciPassTest_mininet.py | 1 + 2 files changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4fad0db..b2678a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ language: python python: - "2.6" - "2.7" + - "3.2" + - "3.3" + - "3.4" before_install: - sudo apt-get update -qq diff --git a/python/t/SciPassTest_mininet.py b/python/t/SciPassTest_mininet.py index 5835b01..3a85499 100644 --- a/python/t/SciPassTest_mininet.py +++ b/python/t/SciPassTest_mininet.py @@ -11,6 +11,7 @@ import ipaddr import os from SciPass import SciPass +from HPVAN import HPVAN import libxml2 logging.basicConfig() From 6b1ea1bfcc4589fd32ae8c40d11b631aec92bdf1 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Sun, 23 Nov 2014 23:32:42 -0800 Subject: [PATCH 67/81] Added WebOb to requirements --- python/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/python/requirements.txt b/python/requirements.txt index 15ae8da..4874f40 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -5,3 +5,4 @@ hp-sdn-client>=1.1.0 mock>=1.0.1 ipaddr>=2.1.0 xmlrunner>=1.7.7 +WebOb>=1.1.1 \ No newline at end of file From 858eb7b49e5066216117972472720ba2c7b9ed85 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Sun, 23 Nov 2014 23:41:00 -0800 Subject: [PATCH 68/81] Added oslo,config to requirements --- python/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/requirements.txt b/python/requirements.txt index 4874f40..a3b386c 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -5,4 +5,5 @@ hp-sdn-client>=1.1.0 mock>=1.0.1 ipaddr>=2.1.0 xmlrunner>=1.7.7 -WebOb>=1.1.1 \ No newline at end of file +WebOb>=1.1.1 +oslo.config.cfg>=1.2.1 \ No newline at end of file From c40b88d9cabb36a2f5cdc625459249d67bfe0424 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Mon, 24 Nov 2014 00:07:04 -0800 Subject: [PATCH 69/81] Fixed oslo.config requirements.txt entry --- python/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/requirements.txt b/python/requirements.txt index a3b386c..72c55a0 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -6,4 +6,4 @@ mock>=1.0.1 ipaddr>=2.1.0 xmlrunner>=1.7.7 WebOb>=1.1.1 -oslo.config.cfg>=1.2.1 \ No newline at end of file +oslo.config>=1.2.1 \ No newline at end of file From 8c5ff25adab55236d4f5261df97d1f40d9463b7a Mon Sep 17 00:00:00 2001 From: Chris Small Date: Mon, 24 Nov 2014 11:27:22 -0800 Subject: [PATCH 70/81] Removed 3.x Python builds from travis because libxml2 bindings don't work with 3.x --- .travis.yml | 4 +- python/requirements.txt | 3 +- python/t/unit/test_rest.py | 355 +++++++++++++++++++++++++++++++++++++ 3 files changed, 358 insertions(+), 4 deletions(-) create mode 100644 python/t/unit/test_rest.py diff --git a/.travis.yml b/.travis.yml index b2678a4..832a9cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,7 @@ language: python python: - "2.6" - "2.7" - - "3.2" - - "3.3" - - "3.4" + before_install: - sudo apt-get update -qq diff --git a/python/requirements.txt b/python/requirements.txt index 72c55a0..a19eb29 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -6,4 +6,5 @@ mock>=1.0.1 ipaddr>=2.1.0 xmlrunner>=1.7.7 WebOb>=1.1.1 -oslo.config>=1.2.1 \ No newline at end of file +oslo.config>=1.2.1 +ecdsa>=0.11 diff --git a/python/t/unit/test_rest.py b/python/t/unit/test_rest.py new file mode 100644 index 0000000..f7b4214 --- /dev/null +++ b/python/t/unit/test_rest.py @@ -0,0 +1,355 @@ +#!/usr/bin/env python +# +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 +# +# 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 json +import os +import re +import unittest +#PY3.3 +import io +try: + from unittest.mock import MagicMock +except ImportError: + from mock import MagicMock + +import httpretty +import requests + +from hpsdnclient.auth import XAuthToken +from hpsdnclient.datatypes import Datapath +from hpsdnclient.error import NotFound +from hpsdnclient.rest import RestClient, UA +from hpsdnclient.tests.data import AUTH, DATAPATH + + +class RestClientTests(unittest.TestCase): + def setUp(self): + self.auth = XAuthToken('10.10.10.10', 'sdn', 'skyline') + self.client = RestClient(self.auth) + response_ok = requests.Response() + response_ok.status_code = 200 + self.response_ok = response_ok + + def test_restclient_instantiation(self): + self.assertEqual(self.client.args["auth"], self.auth) + self.assertEqual(self.client.args["headers"], UA) + self.assertEqual(self.client.args["verify"], False) + self.assertEqual(self.client.args['timeout'], 30) + + def test_user_agent_string(self): + exp = ("^(hpsdnclient/[0-9]\\.[0-9]\\.[0-9] " + + "python-requests/[0-9]\\.[0-9]\\.[0-9])$") + self.assertTrue(re.search(exp, UA['user-agent'], re.S)) + + def test__download_args(self): + args = self.client._download_args() + self.assertEqual(args['headers']['content-type'], 'application/zip') + self.assertEqual(self.client.args['headers']['content-type'], + 'application/json') + self.assertEquals(args["timeout"], 60) + self.assertEquals(args["stream"],True) + + def test__upload_args(self): + filename = "test.txt" + args = self.client._upload_args(filename) + self.assertEqual(args['headers']['content-type'], 'application/zip') + self.assertEqual(self.client.args['headers']['content-type'], + 'application/json') + self.assertEquals(args["headers"]["Filename"], filename) + self.assertEquals(args["timeout"], 60) + + @httpretty.activate + def test__get_json(self): + httpretty.register_uri(httpretty.POST, + 'https://10.10.10.10:8443/sdn/v2.0/auth', + body=AUTH, + status=201) + httpretty.register_uri(httpretty.GET, + 'http://foo.bar', + status=200) + + response = self.client._get('http://foo.bar', False) + + self.assertEqual(response.request.headers['content-type'], + 'application/json') + + @httpretty.activate + def test__get_file(self): + httpretty.register_uri(httpretty.POST, + 'https://10.10.10.10:8443/sdn/v2.0/auth', + body=AUTH, + status=201) + httpretty.register_uri(httpretty.GET, + 'http://foo.bar', + + status=200) + + response = self.client._get('http://foo.bar', True) + + self.assertTrue(isinstance(response, requests.Response)) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.request.headers['content-type'], + 'application/zip') + + @httpretty.activate + def test__put(self): + httpretty.register_uri(httpretty.POST, + 'https://10.10.10.10:8443/sdn/v2.0/auth', + body=AUTH, + status=201) + httpretty.register_uri(httpretty.PUT, + 'http://foo.bar', + status=201) + + response = self.client._put('http://foo.bar', + json.dumps({"some": "data"})) + + self.assertTrue(isinstance(response, requests.Response)) + self.assertEqual(response.status_code, 201) + self.assertEqual(response.request.headers['content-type'], + 'application/json') + self.assertEqual(response.request.body, + json.dumps({"some": "data"})) + + @httpretty.activate + def test__post_json(self): + httpretty.register_uri(httpretty.POST, + 'https://10.10.10.10:8443/sdn/v2.0/auth', + body=AUTH, + status=201) + httpretty.register_uri(httpretty.POST, + 'http://foo.bar', + status=201) + + response = self.client._post('http://foo.bar', + json.dumps({"some": "data"}), + False) + + self.assertTrue(isinstance(response, requests.Response)) + self.assertEqual(response.status_code, 201) + self.assertEqual(response.request.headers['content-type'], + 'application/json') + self.assertEqual(response.request.body, + json.dumps({"some": "data"})) + + @httpretty.activate + def test__post_file(self): + httpretty.register_uri(httpretty.POST, + 'https://10.10.10.10:8443/sdn/v2.0/auth', + body=AUTH, + status=201) + httpretty.register_uri(httpretty.POST, + 'http://foo.bar', + status=201) + + f = open("test.txt", "wb") + f.close() + + response = self.client._post('http://foo.bar', + "test.txt", + True) + + self.assertTrue(isinstance(response, requests.Response)) + self.assertEqual(response.status_code, 201) + self.assertEqual(response.request.headers['content-type'], + 'application/zip') + self.assertEqual(response.request.headers['filename'], + 'test.txt') + + os.remove("test.txt") + + @httpretty.activate + def test__delete_no_data(self): + httpretty.register_uri(httpretty.POST, + 'https://10.10.10.10:8443/sdn/v2.0/auth', + body=AUTH, + status=201) + httpretty.register_uri(httpretty.DELETE, + 'http://foo.bar', + status=201) + + response = self.client._delete('http://foo.bar') + + self.assertTrue(isinstance(response, requests.Response)) + self.assertEqual(response.status_code, 201) + self.assertEqual(response.request.headers['content-type'], + 'application/json') + + @httpretty.activate + def test__delete_with_data(self): + httpretty.register_uri(httpretty.POST, + 'https://10.10.10.10:8443/sdn/v2.0/auth', + body=AUTH, + status=201) + httpretty.register_uri(httpretty.DELETE, + 'http://foo.bar', + status=201) + + response = self.client._delete('http://foo.bar', + json.dumps({"some": "data"})) + + self.assertTrue(isinstance(response, requests.Response)) + self.assertEqual(response.status_code, 201) + self.assertEqual(response.request.headers['content-type'], + 'application/json') + self.assertEqual(response.request.body, + json.dumps({"some": "data"})) + + @httpretty.activate + def test__head(self): + httpretty.register_uri(httpretty.POST, + 'https://10.10.10.10:8443/sdn/v2.0/auth', + body=AUTH, + status=201) + httpretty.register_uri(httpretty.HEAD, + 'http://foo.bar', + status=201) + + response = self.client._head('http://foo.bar') + + self.assertTrue(isinstance(response, requests.Response)) + self.assertEqual(response.status_code, 201) + self.assertEqual(response.request.headers['content-type'], + 'application/json') + + def test_get_json_valid_datatype(self): + data = json.dumps({"version": "1.0.0", "datapath": DATAPATH}) + response = requests.Response() + response._content = data.encode("UTF-8") + response.status_code = 201 + response.headers['content-type'] = 'application/json' + self.client._get = MagicMock(name="_get", return_value=response) + + r = self.client.get('http://foo.bar') + + self.client._get.assert_called_with('http://foo.bar') + self.assertTrue(isinstance(r, Datapath)) + + def test_get_json_valid_datatypes(self): + data = json.dumps({"version": "1.0.0", + "datapaths": [DATAPATH, DATAPATH]}) + response = requests.Response() + response._content = data.encode("UTF-8") + response.status_code = 201 + response.headers['content-type'] = 'application/json' + self.client._get = MagicMock(name="_get", return_value=response) + + r = self.client.get('http://foo.bar') + + self.client._get.assert_called_with('http://foo.bar') + self.assertTrue(isinstance(r, list)) + for item in r: + self.assertTrue(isinstance(item, Datapath)) + + def test_get_json_invalid_datatype(self): + data = json.dumps({"version": "1.0.0", "datapathz": DATAPATH}) + response = requests.Response() + response._content = data.encode("UTF-8") + response.status_code = 201 + response.headers['content-type'] = 'application/json' + self.client._get = MagicMock(name="_get", return_value=response) + + self.assertRaises(NotFound, self.client.get, 'http://foo.bar') + + def test_get_file(self): + with open("test.txt", "wb") as f: + f.write("Hello World!".encode("UTF-8")) + f.flush() + f.close() + + response = requests.Response() + response.raw = io.open("test.txt") + response.status_code = 201 + response.headers['content-type'] = 'application/zip' + response.headers['content-disposition'] = 'attachment; filename=test1.txt' + self.client._get = MagicMock(name="_get", return_value=response) + + r = self.client.get('http://foo.bar', is_file=True) + + self.client._get.assert_called_with('http://foo.bar', is_file=True) + self.assertEqual(r, 'test1.txt') + f = open('test1.txt', 'rb') + self.assertEqual(f.read().decode("UTF-8"), "Hello World!") + f.close() + os.remove('test.txt') + os.remove('test1.txt') + + def test_get_none(self): + response = requests.Response() + response.status_code = 201 + response.headers['Content-Type'] = 'application/none' + self.client._get = MagicMock(name="_get", return_value=response) + + r = self.client.get('http://foo.bar') + + self.client._get.assert_called_with('http://foo.bar') + self.assertTrue(r is None) + + def test_post(self): + self.client._post = MagicMock(name="_post", + return_value=self.response_ok) + + r = self.client.post('http://foo.bar', json.dumps({"some": "data"})) + + self.client._post.assert_called_with('http://foo.bar', + json.dumps({"some": "data"}), + False) + + self.assertTrue(isinstance(r, requests.Response)) + + def test_put(self): + self.client._put = MagicMock(name="_put", + return_value=self.response_ok) + + r = self.client.put('http://foo.bar', json.dumps({"some": "data"})) + + self.client._put.assert_called_with('http://foo.bar', + json.dumps({"some": "data"})) + + self.assertTrue(isinstance(r, requests.Response)) + + def test_delete_data(self): + self.client._delete = MagicMock(name="_delete", + return_value=self.response_ok) + + r = self.client.delete('http://foo.bar', json.dumps({"some": "data"})) + + self.client._delete.assert_called_with('http://foo.bar', + json.dumps({"some": "data"})) + + self.assertTrue(isinstance(r, requests.Response)) + + + def test_delete_no_data(self): + self.client._delete = MagicMock(name="_delete", + return_value=self.response_ok) + + r = self.client.delete('http://foo.bar') + + self.client._delete.assert_called_with('http://foo.bar', None) + + self.assertTrue(isinstance(r, requests.Response)) + + def test_head(self): + self.client._head = MagicMock(name="_head", + return_value=self.response_ok) + #self.raise_errors = MagicMock(name="raise_errors") + + r = self.client.head('http://foo.bar') + + self.client._head.assert_called_with('http://foo.bar') + + self.assertTrue(isinstance(r, requests.Response)) From fe61f4b36a90a29e63da1ad5d43344c4515c2bce Mon Sep 17 00:00:00 2001 From: Chris Small Date: Mon, 24 Nov 2014 15:46:54 -0800 Subject: [PATCH 71/81] Removed HP-VAN and ODL related files from master branch --- circle.yml | 12 - python/HPVAN.py | 308 ----------------------- python/SciPass_start.py | 32 --- python/t/SciPassTest_mininet_REST.py | 254 ------------------- python/t/resources/good_flow | 1 - python/t/unit/test_rest.py | 355 --------------------------- python/test_struct.py | 151 ------------ 7 files changed, 1113 deletions(-) delete mode 100644 circle.yml delete mode 100644 python/HPVAN.py delete mode 100644 python/SciPass_start.py delete mode 100644 python/t/SciPassTest_mininet_REST.py delete mode 100644 python/t/resources/good_flow delete mode 100644 python/t/unit/test_rest.py delete mode 100755 python/test_struct.py diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 2db5979..0000000 --- a/circle.yml +++ /dev/null @@ -1,12 +0,0 @@ -general: - build_dir: python -dependencies: - pre: - - sudo apt-get install -y libxml2-dev libxsl - - wget ftp://xmlsoft.org/libxml2/python/libxml2-python-2.6.15.tar.gz - - tar -xzvf libxml2-python-2.6.15.tar.gz - - cd libxml2-python-2.6.15 && python setup.py install - - cd .. -test: - override: - - nosetests t/SciPassTest_mininet.py diff --git a/python/HPVAN.py b/python/HPVAN.py deleted file mode 100644 index f0c55b4..0000000 --- a/python/HPVAN.py +++ /dev/null @@ -1,308 +0,0 @@ -# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. -# Copyright (C) 2014 The Trustees of Indiana University -# -# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 -# -# 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 logging -from bottle import run as bottle_run -from bottle import route -from webob import Response - -import oslo.config.cfg as cfg -import hpsdnclient as hp -import json - -from SciPass import SciPass - -ETH_TYPE_IP = 0x0800 -_DPID_LEN = 16 -_DPID_LEN_STR = str(_DPID_LEN) -_DPID_FMT = '%0' + _DPID_LEN_STR + 'x' -DPID_PATTERN = r'[0-9a-f]{%d}' % _DPID_LEN - - -""" - Forwarding rule Priorities - BlackList - WhiteList - Balancer - Default -""" - - -class SciPassRest(): - def __init__(self, api): - self.api = api - self.logger = api.logger - - #POST /scipass/flows/good_flow - @route('/scipass/flows/good_flow') - def good_flow(self, req): - try: - obj = eval(req.body) - except SyntaxError: - self.logger.error("Syntax Error processing good_flow signal %s", req.body) - return Response(status=400) - - result = self.api.good_flow(obj) - return Response(content_type='application/json',body=json.dumps(result)) - - #POST /scipass/flows/bad_flow - @route('scipass', '/scipass/flows/bad_flow', methods=['PUT']) - def bad_flow(self, req): - try: - obj = eval(req.body) - except SyntaxError: - self.logger.error("Syntax Error processing bad_flow signal %s", req.body) - return Response(status=400) - result = self.api.bad_flow(obj) - return Response(content_type='application/json',body=json.dumps(result)) - - #GET /scipass/flows/get_good_flows - @route('scipass', '/scipass/flows/get_good_flows', methods=['GET']) - def get_good_flows(self, req): - result = self.api.get_good_flows() - return Response(content_type='application/json',body=json.dumps(result)) - - #GET /scipass/flows/get_bad_flows - @route('scipass', '/scipass/flows/get_bad_flows', methods=['GET']) - def get_bad_flows(self, req): - result = self.api.get_bad_flows() - return Response(content_type='application/json',body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/flows', methods=['GET'], requirements= {'dpid': DPID_PATTERN}) - def get_switch_flows(self, req, **kwargs): - result = self.api.getSwitchFlows(dpid=kwargs['dpid']) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/sensor/load', methods=['PUT']) - def update_sensor_load(self, req): - try: - obj = eval(req.body) - except SyntaxError: - self.logger.error("Syntax Error processing update_sensor_status signal %s", req.body) - return Response(status=400) - result = self.api.setSensorStatus(obj['sensor_id'],obj['load']) - return Response(content_type='application/json',body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensor/{sensor_id}', methods=['GET'], requirements= {'dpid': DPID_PATTERN}) - def get_sensor_load(self, req, **kwargs): - result = self.api.getSensorStatus(dpid=kwargs['dpid'], domain=kwargs['domain'], sensor_id=kwargs['sensor_id']) - return Response(content_type='application/json',body=json.dumps(result)) - - @route('scipass', '/scipass/switches', methods=['GET']) - def get_switches(self, req): - result = self.api.getSwitches() - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/sensors', methods=['GET'], requirements = {'dpid': DPID_PATTERN}) - def get_domain_sensors(self, req, **kwargs): - result = self.api.getDomainSensors(dpid = kwargs['dpid'], domain = kwargs['domain'] ) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domains', methods=['GET'], requirements = {'dpid': DPID_PATTERN}) - def get_switch_domains(self, req, **kwargs): - result = self.api.getSwitchDomains(dpid=kwargs['dpid']) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}', methods=['GET'], requirements = {'dpid': DPID_PATTERN}) - def get_domain_status(self, req, **kwargs): - result = self.api.getDomainStatus(dpid = kwargs['dpid'], domain = kwargs['domain']) - return Response(content_type='application/json', body=json.dumps(result)) - - @route('scipass', '/scipass/switch/{dpid}/domain/{domain}/flows',methods=['GET'],requirements = {'dpid': DPID_PATTERN}) - def get_domain_flows(self,req, **kwargs): - result = self.api.getDomainFlows(dpid = kwargs['dpid'], domain = kwargs['domain']) - return Response(content_type='application/json', body=json.dumps(result)) - - -class HPVAN(): - - def __init__(self, user, pw, controller): - - self.datapaths = {} - self.username = "sdn" - self.password = "skyline" - self.controller = "15.126.229.78" - - logging.basicConfig() - self.logger = logging.getLogger(__name__) - - #--- register for configuration options - self.CONF = cfg.CONF - self.CONF.register_opts([ - cfg.StrOpt('SciPassConfig',default='/etc/SciPass/SciPass.xml', - help='where to find the SciPass config file'), - ]) - - self.logger.error("Starting SciPass") - # - api = SciPass(logger = self.logger, - config_file = self.CONF.SciPassConfig ) - api.registerForwardingStateChangeHandler(self.changeSwitchForwardingState) - - self.api = api - - self.logger.debug("Connecting to VAN controller" + self.controller) - auth = hp.XAuthToken(user=self.username, password=self.password, server=self.controller) - api = hp.Api(controller=self.controller, auth=auth) - - - def start_rest(self, host, port): - module = "SciPass" - bottle_run(module,host=host,port=port) - - - - def changeSwitchForwardingState(self, dpid=None, header=None, actions=None, command=None, idle_timeout=None, hard_timeout=None, priority=1): - self.logger.debug("Changing switch forwarding state") - - if(not self.datapaths.has_key(dpid)): - self.logger.error("unable to find switch with dpid " + dpid) - self.logger.error(self.datapaths) - return - - datapath = self.datapaths[dpid] - - ofp = datapath.ofproto - parser = datapath.ofproto_parser - - obj = {} - - if(header.has_key('dl_type')): - if(header['dl_type'] == None): - obj['dl_type'] = None - else: - obj['dl_type'] = int(header['dl_type']) - else: - obj['dl_type'] = ETH_TYPE_IP - - if(header.has_key('phys_port')): - obj['in_port'] = int(header['phys_port']) - else: - obj['in_port'] = None - - if(header.has_key('nw_src')): - obj['nw_src'] = int(header['nw_src']) - else: - obj['nw_src'] = None - - if(header.has_key('nw_src_mask')): - obj['nw_src_mask'] = int(header['nw_src_mask']) - else: - obj['nw_src_mask'] = None - - if(header.has_key('nw_dst')): - obj['nw_dst'] = int(header['nw_dst']) - else: - obj['nw_dst'] = None - - if(header.has_key('nw_dst_mask')): - obj['nw_dst_mask'] = int(header['nw_dst_mask']) - else: - obj['nw_dst_mask'] = None - - if(header.has_key('tp_src')): - obj['tp_src'] = int(header['tp_src']) - else: - obj['tp_src'] = None - - if(header.has_key('tp_dst')): - obj['tp_dst'] = int(header['tp_dst']) - else: - obj['tp_dst'] = None - - if(obj['dl_type'] == None): - match = parser.OFPMatch( in_port = obj['in_port'], - nw_dst = obj['nw_dst'], - nw_dst_mask = obj['nw_dst_mask'], - nw_src = obj['nw_src'], - nw_src_mask = obj['nw_src_mask'], - tp_src = obj['tp_src'], - tp_dst = obj['tp_dst']) - else: - - match = parser.OFPMatch( in_port = obj['in_port'], - nw_dst = obj['nw_dst'], - nw_dst_mask = obj['nw_dst_mask'], - nw_src = obj['nw_src'], - nw_src_mask = obj['nw_src_mask'], - dl_type = obj['dl_type'], - tp_src = obj['tp_src'], - tp_dst = obj['tp_dst']) - - self.logger.debug("Match: " + str(match)) - - of_actions = [] - for action in actions: - if(action['type'] == "output"): - of_actions.append(parser.OFPActionOutput(int(action['port']),0)) - - self.logger.debug("Actions: " + str(of_actions)) - if(command == "ADD"): - command = ofp.OFPFC_ADD - elif(command == "DELETE_STRICT"): - command = ofp.OFPFC_DELETE_STRICT - else: - command = -1 - - self.logger.debug("Sending flow mod with command: " + str(command)) - self.logger.debug("Datpath: " + str(datapath)) - - mod = parser.OFPFlowMod( datapath = datapath, - priority = int(priority), - match = match, - cookie = 0, - command = command, - idle_timeout = int(idle_timeout), - hard_timeout = int(hard_timeout), - actions = of_actions) - - if(datapath.is_active == True): - datapath.send_msg(mod) - - def flushRules(self, dpid): - if(not self.datapaths.has_key(dpid)): - self.logger.error("unable to find switch with dpid " + dpid) - return - - datapath = self.datapaths[dpid] - ofp = datapath.ofproto - parser = datapath.ofproto_parser - - # --- create flowmod to control traffic from the prefix to the interwebs - match = parser.OFPMatch() - mod = parser.OFPFlowMod(datapath,match,0,ofp.OFPFC_DELETE) - - #--- remove mods in the flowmod cache - #self.flowmods[dpid] = [] - - - #--- if dp is active then push the rules - if(datapath.is_active == True): - datapath.send_msg(mod) - - -def start_scipass_van(): - - username = "sdn" - password = "skyline" - controller = "15.126.229.78" - scipass = HPVAN(username, password, controller) - scipass.start_rest('localhost',8090) - - -if __name__ == '__main__': - start_scipass_van() - diff --git a/python/SciPass_start.py b/python/SciPass_start.py deleted file mode 100644 index 7ce8f3d..0000000 --- a/python/SciPass_start.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/python - -# Copyright (C) 2014 Hewlett-Packard Development Company, L.P -# -# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 -# -# 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 HPVAN - -def start_scipass_van(): - - username = "sdn" - password = "skyline" - controller = "15.126.229.78" - van = HPVAN(username, password, controller) - van.start_rest("127.0.0.1",8090) - - -if __name__ == '__main__': - start_scipass_van() - - diff --git a/python/t/SciPassTest_mininet_REST.py b/python/t/SciPassTest_mininet_REST.py deleted file mode 100644 index 5835b01..0000000 --- a/python/t/SciPassTest_mininet_REST.py +++ /dev/null @@ -1,254 +0,0 @@ -# Version of the SciPass tests that intergrates a mininet instance -# Provides an easy way to test without a physical switch and/or with more complex topologies - - -import sys -sys.path.append(".") -import pprint -import unittest -from mock import Mock -import logging -import ipaddr -import os -from SciPass import SciPass -import libxml2 - -logging.basicConfig() - - -class TestInit(unittest.TestCase): - - def test_valid_config(self): - api = SciPass( logger = logging.getLogger(__name__), - config = str(os.getcwd()) + "/t/etc/SciPass-mininet.xml" - ) - self.assertTrue(isinstance(api,SciPass)) - - -# def test_no_config(self): -# self.assertRaises(libxml2.parserError,SciPass) -# -# api = SciPass( logger = logging.getLogger(__name__), -# config = str(os.getcwd()) + "/t/etc/no_config.xml" ) - - -# def test_invalid_config(self): -# self.assertRaises(libxml2.parserError,SciPass) -# -# api = SciPass( logger = logging.getLogger(__name__), -# config = str(os.getcwd()) + "/t/etc/InvalidConfig.xml" ) - - def test_switch_init(self): - api = SciPass( logger = logging.getLogger(__name__), - config = str(os.getcwd()) + "/t/etc/SciPass-mininet.xml" ) - - #first setup the handler to get all the flows that were sent - flows = [] - def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): - flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) - - api.registerForwardingStateChangeHandler(flowSent) - datapath = Mock(id=1) - api.switchJoined(datapath) - - self.assertTrue( len(flows) == 33) - #verify all of the 'flow details are set properly' - for flow in flows: - self.assertEquals(flow['dpid'], "%016x" % datapath.id) - self.assertEquals(flow['hard_timeout'], 0) - self.assertEquals(flow['idle_timeout'], 0) - - flow = flows[0] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 1, 'dl_type': None}) - self.assertEquals(flow['priority'], 5) - flow = flows[1] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port':4, 'nw_dst': 167776512, 'nw_dst_mask': 24}) - self.assertEquals(flow['priority'], 10) - flow = flows[2] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port':1, 'nw_src': 167776512, 'nw_src_mask': 24}) - self.assertEquals(flow['priority'], 10) - flow = flows[3] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port':4, 'nw_dst': 167776768, 'nw_dst_mask': 24}) - self.assertEquals(flow['priority'], 10) - flow = flows[4] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port':1, 'nw_src': 167776768, 'nw_src_mask': 24}) - self.assertEquals(flow['priority'], 10) - flow = flows[5] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 2, 'dl_type': None}) - self.assertEquals(flow['priority'], 5) - flow = flows[6] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 2}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port':4, 'nw_dst': 167777024, 'nw_dst_mask': 24}) - self.assertEquals(flow['priority'], 10) - flow = flows[7] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port':2, 'nw_src': 167777024, 'nw_src_mask': 24}) - self.assertEquals(flow['priority'], 10) - flow = flows[8] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 2}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port':4, 'nw_dst': 167777280, 'nw_dst_mask': 24}) - self.assertEquals(flow['priority'], 10) - flow = flows[9] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 4}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port':2, 'nw_src': 167777280, 'nw_src_mask': 24}) - self.assertEquals(flow['priority'], 10) - flow = flows[10] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 1}, {'type': 'output', 'port': 2}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 4, 'dl_type': None}) - self.assertEquals(flow['priority'], 3) - flow = flows[11] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 3}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 5, 'dl_type': None}) - self.assertEquals(flow['priority'], 10) - flow = flows[12] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': 5}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 3, 'dl_type': None}) - self.assertEquals(flow['priority'], 10) - flow = flows[13] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': '6'}, {'type': 'output', 'port': '4'}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) - self.assertEquals(flow['priority'], 500) - flow = flows[14] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': '6'}, {'type': 'output', 'port': '5'}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 3, 'nw_dst': 167776512, 'nw_dst_mask': 24}) - self.assertEquals(flow['priority'], 500) - flow = flows[15] - self.assertEquals(flow['actions'],[]) - self.assertEquals(flow['command'],"DELETE_STRICT") - self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) - self.assertEquals(flow['priority'], 500) - flow = flows[16] - self.assertEquals(flow['actions'],[]) - self.assertEquals(flow['command'],"DELETE_STRICT") - self.assertEquals(flow['header'], {'phys_port': 3, 'nw_dst': 167776512, 'nw_dst_mask': 24}) - self.assertEquals(flow['priority'], 500) - flow = flows[17] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': '8'}, {'type': 'output', 'port': '4'}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776512, 'nw_src_mask': 24}) - self.assertEquals(flow['priority'], 500) - flow = flows[18] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': '8'}, {'type': 'output', 'port': '5'}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 3, 'nw_dst': 167776512, 'nw_dst_mask': 24}) - self.assertEquals(flow['priority'], 500) - flow = flows[19] - self.assertEquals(flow['actions'],[{'type': 'output', 'port': '6'}, {'type': 'output', 'port': '4'}]) - self.assertEquals(flow['command'],"ADD") - self.assertEquals(flow['header'], {'phys_port': 1, 'nw_src': 167776768, 'nw_src_mask': 24}) - self.assertEquals(flow['priority'], 500) - flow = flows[20] - -class TestFunctionality(unittest.TestCase): - def setUp(self): - self.api = SciPass( logger = logging.getLogger(__name__), - config = str(os.getcwd()) + "/t/etc/SciPass-mininet.xml" ) - - def test_update_prefix_bw(self): - #first setup the handler to get all the flows that were sent - flows = [] - def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): - flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) - - self.api.registerForwardingStateChangeHandler(flowSent) - datapath = Mock(id=1) - self.api.switchJoined(datapath) - - self.assertEquals( len(flows), 33) - self.api.updatePrefixBW("%016x" % datapath.id, ipaddr.IPv4Network("10.0.19.0/24"), 500,500) - self.assertTrue(self.api.getBalancer("%016x" % datapath.id, "R&E").getPrefixBW(ipaddr.IPv4Network("10.0.19.0/24")), 1000) - self.api.updatePrefixBW("%016x" % datapath.id, ipaddr.IPv4Network("10.0.17.0/24"), 500,500) - self.assertTrue(self.api.getBalancer("%016x" % datapath.id, "R&E").getPrefixBW(ipaddr.IPv4Network("10.0.17.0/24")), 1000) - - - def test_good_flow(self): - flows = [] - def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): - flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) - - self.api.registerForwardingStateChangeHandler(flowSent) - datapath = Mock(id=1) - self.api.switchJoined(datapath) - #self.logger.error("testing good flow") - self.assertEquals(len(flows),33) - flows = [] - self.api.good_flow({"nw_src": "10.0.20.2/32", "nw_dst":"156.56.6.1/32", "tp_src":1, "tp_dst":2}) - self.assertEquals(len(flows),2) - flow = flows[0] - self.assertEqual(int(flow['hard_timeout']),0) - self.assertEqual(int(flow['idle_timeout']),90) - self.assertEqual(flow['actions'],[{'type': 'output', 'port': '3'}]) - self.assertEqual(flow['header'],{'phys_port': 2, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_src': 167777282, 'tp_dst': 2, 'tp_src': 1, 'nw_dst': 2620917249}) - self.assertEqual(int(flow['priority']),900) - self.assertEqual(flow['command'],"ADD") - self.assertEqual(flow['dpid'],"%016x" % datapath.id) - flow = flows[1] - self.assertEqual(int(flow['hard_timeout']),0) - self.assertEqual(int(flow['idle_timeout']),90) - self.assertEqual(flow['actions'],[{'type': 'output', 'port': '2'}]) - self.assertEqual(flow['header'],{'phys_port': 3, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) - self.assertEqual(int(flow['priority']),900) - self.assertEqual(flow['command'],"ADD") - self.assertEqual(flow['dpid'],"%016x" % datapath.id) - - - - def test_bad_flow(self): - flows = [] - def flowSent(dpid = None, header = None, actions = None,command = None, priority = None, idle_timeout = None, hard_timeout = None): - flows.append({'dpid': dpid, 'header': header, 'actions': actions, 'command': command, 'priority': priority, 'idle_timeout': idle_timeout, 'hard_timeout': hard_timeout}) - - self.api.registerForwardingStateChangeHandler(flowSent) - datapath = Mock(id=1) - self.api.switchJoined(datapath) - #self.logger.error("testing good flow") - self.assertEquals(len(flows),33) - flows = [] - self.api.bad_flow({"nw_src": "10.0.20.2/32", "nw_dst":"156.56.6.1/32", "tp_src":1, "tp_dst":2}) - self.assertEquals(len(flows),2) - flow = flows[0] - self.assertEqual(int(flow['hard_timeout']),0) - self.assertEqual(int(flow['idle_timeout']),90) - self.assertEqual(flow['actions'],[]) - self.assertEqual(flow['header'],{'phys_port': 2, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_src': 167777282, 'tp_dst': 2, 'tp_src': 1, 'nw_dst': 2620917249}) - self.assertEqual(int(flow['priority']),900) - self.assertEqual(flow['command'],"ADD") - self.assertEqual(flow['dpid'],"%016x" % datapath.id) - flow = flows[1] - self.assertEqual(int(flow['hard_timeout']),0) - self.assertEqual(int(flow['idle_timeout']),90) - self.assertEqual(flow['actions'],[]) - self.assertEqual(flow['header'],{'phys_port': 3, 'nw_src_mask': 32, 'nw_dst_mask': 32, 'nw_dst': 167777282, 'tp_dst': 1, 'tp_src': 2, 'nw_src': 2620917249}) - self.assertEqual(int(flow['priority']),900) - self.assertEqual(flow['command'],"ADD") - self.assertEqual(flow['dpid'],"%016x" % datapath.id) - -def suite(): - suite = unittest.TestLoader().loadTestsFromTestCase(TestInit) - suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestFunctionality)) - return suite - - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/python/t/resources/good_flow b/python/t/resources/good_flow deleted file mode 100644 index 7c1b460..0000000 --- a/python/t/resources/good_flow +++ /dev/null @@ -1 +0,0 @@ -{"nw_src": "10.0.20.2/32", "nw_dst":"156.56.6.1/32", "tp_src":1, "tp_dst":2} \ No newline at end of file diff --git a/python/t/unit/test_rest.py b/python/t/unit/test_rest.py deleted file mode 100644 index f7b4214..0000000 --- a/python/t/unit/test_rest.py +++ /dev/null @@ -1,355 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2014 Hewlett-Packard Development Company, L.P. -# -# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 -# -# 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 json -import os -import re -import unittest -#PY3.3 -import io -try: - from unittest.mock import MagicMock -except ImportError: - from mock import MagicMock - -import httpretty -import requests - -from hpsdnclient.auth import XAuthToken -from hpsdnclient.datatypes import Datapath -from hpsdnclient.error import NotFound -from hpsdnclient.rest import RestClient, UA -from hpsdnclient.tests.data import AUTH, DATAPATH - - -class RestClientTests(unittest.TestCase): - def setUp(self): - self.auth = XAuthToken('10.10.10.10', 'sdn', 'skyline') - self.client = RestClient(self.auth) - response_ok = requests.Response() - response_ok.status_code = 200 - self.response_ok = response_ok - - def test_restclient_instantiation(self): - self.assertEqual(self.client.args["auth"], self.auth) - self.assertEqual(self.client.args["headers"], UA) - self.assertEqual(self.client.args["verify"], False) - self.assertEqual(self.client.args['timeout'], 30) - - def test_user_agent_string(self): - exp = ("^(hpsdnclient/[0-9]\\.[0-9]\\.[0-9] " + - "python-requests/[0-9]\\.[0-9]\\.[0-9])$") - self.assertTrue(re.search(exp, UA['user-agent'], re.S)) - - def test__download_args(self): - args = self.client._download_args() - self.assertEqual(args['headers']['content-type'], 'application/zip') - self.assertEqual(self.client.args['headers']['content-type'], - 'application/json') - self.assertEquals(args["timeout"], 60) - self.assertEquals(args["stream"],True) - - def test__upload_args(self): - filename = "test.txt" - args = self.client._upload_args(filename) - self.assertEqual(args['headers']['content-type'], 'application/zip') - self.assertEqual(self.client.args['headers']['content-type'], - 'application/json') - self.assertEquals(args["headers"]["Filename"], filename) - self.assertEquals(args["timeout"], 60) - - @httpretty.activate - def test__get_json(self): - httpretty.register_uri(httpretty.POST, - 'https://10.10.10.10:8443/sdn/v2.0/auth', - body=AUTH, - status=201) - httpretty.register_uri(httpretty.GET, - 'http://foo.bar', - status=200) - - response = self.client._get('http://foo.bar', False) - - self.assertEqual(response.request.headers['content-type'], - 'application/json') - - @httpretty.activate - def test__get_file(self): - httpretty.register_uri(httpretty.POST, - 'https://10.10.10.10:8443/sdn/v2.0/auth', - body=AUTH, - status=201) - httpretty.register_uri(httpretty.GET, - 'http://foo.bar', - - status=200) - - response = self.client._get('http://foo.bar', True) - - self.assertTrue(isinstance(response, requests.Response)) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.request.headers['content-type'], - 'application/zip') - - @httpretty.activate - def test__put(self): - httpretty.register_uri(httpretty.POST, - 'https://10.10.10.10:8443/sdn/v2.0/auth', - body=AUTH, - status=201) - httpretty.register_uri(httpretty.PUT, - 'http://foo.bar', - status=201) - - response = self.client._put('http://foo.bar', - json.dumps({"some": "data"})) - - self.assertTrue(isinstance(response, requests.Response)) - self.assertEqual(response.status_code, 201) - self.assertEqual(response.request.headers['content-type'], - 'application/json') - self.assertEqual(response.request.body, - json.dumps({"some": "data"})) - - @httpretty.activate - def test__post_json(self): - httpretty.register_uri(httpretty.POST, - 'https://10.10.10.10:8443/sdn/v2.0/auth', - body=AUTH, - status=201) - httpretty.register_uri(httpretty.POST, - 'http://foo.bar', - status=201) - - response = self.client._post('http://foo.bar', - json.dumps({"some": "data"}), - False) - - self.assertTrue(isinstance(response, requests.Response)) - self.assertEqual(response.status_code, 201) - self.assertEqual(response.request.headers['content-type'], - 'application/json') - self.assertEqual(response.request.body, - json.dumps({"some": "data"})) - - @httpretty.activate - def test__post_file(self): - httpretty.register_uri(httpretty.POST, - 'https://10.10.10.10:8443/sdn/v2.0/auth', - body=AUTH, - status=201) - httpretty.register_uri(httpretty.POST, - 'http://foo.bar', - status=201) - - f = open("test.txt", "wb") - f.close() - - response = self.client._post('http://foo.bar', - "test.txt", - True) - - self.assertTrue(isinstance(response, requests.Response)) - self.assertEqual(response.status_code, 201) - self.assertEqual(response.request.headers['content-type'], - 'application/zip') - self.assertEqual(response.request.headers['filename'], - 'test.txt') - - os.remove("test.txt") - - @httpretty.activate - def test__delete_no_data(self): - httpretty.register_uri(httpretty.POST, - 'https://10.10.10.10:8443/sdn/v2.0/auth', - body=AUTH, - status=201) - httpretty.register_uri(httpretty.DELETE, - 'http://foo.bar', - status=201) - - response = self.client._delete('http://foo.bar') - - self.assertTrue(isinstance(response, requests.Response)) - self.assertEqual(response.status_code, 201) - self.assertEqual(response.request.headers['content-type'], - 'application/json') - - @httpretty.activate - def test__delete_with_data(self): - httpretty.register_uri(httpretty.POST, - 'https://10.10.10.10:8443/sdn/v2.0/auth', - body=AUTH, - status=201) - httpretty.register_uri(httpretty.DELETE, - 'http://foo.bar', - status=201) - - response = self.client._delete('http://foo.bar', - json.dumps({"some": "data"})) - - self.assertTrue(isinstance(response, requests.Response)) - self.assertEqual(response.status_code, 201) - self.assertEqual(response.request.headers['content-type'], - 'application/json') - self.assertEqual(response.request.body, - json.dumps({"some": "data"})) - - @httpretty.activate - def test__head(self): - httpretty.register_uri(httpretty.POST, - 'https://10.10.10.10:8443/sdn/v2.0/auth', - body=AUTH, - status=201) - httpretty.register_uri(httpretty.HEAD, - 'http://foo.bar', - status=201) - - response = self.client._head('http://foo.bar') - - self.assertTrue(isinstance(response, requests.Response)) - self.assertEqual(response.status_code, 201) - self.assertEqual(response.request.headers['content-type'], - 'application/json') - - def test_get_json_valid_datatype(self): - data = json.dumps({"version": "1.0.0", "datapath": DATAPATH}) - response = requests.Response() - response._content = data.encode("UTF-8") - response.status_code = 201 - response.headers['content-type'] = 'application/json' - self.client._get = MagicMock(name="_get", return_value=response) - - r = self.client.get('http://foo.bar') - - self.client._get.assert_called_with('http://foo.bar') - self.assertTrue(isinstance(r, Datapath)) - - def test_get_json_valid_datatypes(self): - data = json.dumps({"version": "1.0.0", - "datapaths": [DATAPATH, DATAPATH]}) - response = requests.Response() - response._content = data.encode("UTF-8") - response.status_code = 201 - response.headers['content-type'] = 'application/json' - self.client._get = MagicMock(name="_get", return_value=response) - - r = self.client.get('http://foo.bar') - - self.client._get.assert_called_with('http://foo.bar') - self.assertTrue(isinstance(r, list)) - for item in r: - self.assertTrue(isinstance(item, Datapath)) - - def test_get_json_invalid_datatype(self): - data = json.dumps({"version": "1.0.0", "datapathz": DATAPATH}) - response = requests.Response() - response._content = data.encode("UTF-8") - response.status_code = 201 - response.headers['content-type'] = 'application/json' - self.client._get = MagicMock(name="_get", return_value=response) - - self.assertRaises(NotFound, self.client.get, 'http://foo.bar') - - def test_get_file(self): - with open("test.txt", "wb") as f: - f.write("Hello World!".encode("UTF-8")) - f.flush() - f.close() - - response = requests.Response() - response.raw = io.open("test.txt") - response.status_code = 201 - response.headers['content-type'] = 'application/zip' - response.headers['content-disposition'] = 'attachment; filename=test1.txt' - self.client._get = MagicMock(name="_get", return_value=response) - - r = self.client.get('http://foo.bar', is_file=True) - - self.client._get.assert_called_with('http://foo.bar', is_file=True) - self.assertEqual(r, 'test1.txt') - f = open('test1.txt', 'rb') - self.assertEqual(f.read().decode("UTF-8"), "Hello World!") - f.close() - os.remove('test.txt') - os.remove('test1.txt') - - def test_get_none(self): - response = requests.Response() - response.status_code = 201 - response.headers['Content-Type'] = 'application/none' - self.client._get = MagicMock(name="_get", return_value=response) - - r = self.client.get('http://foo.bar') - - self.client._get.assert_called_with('http://foo.bar') - self.assertTrue(r is None) - - def test_post(self): - self.client._post = MagicMock(name="_post", - return_value=self.response_ok) - - r = self.client.post('http://foo.bar', json.dumps({"some": "data"})) - - self.client._post.assert_called_with('http://foo.bar', - json.dumps({"some": "data"}), - False) - - self.assertTrue(isinstance(r, requests.Response)) - - def test_put(self): - self.client._put = MagicMock(name="_put", - return_value=self.response_ok) - - r = self.client.put('http://foo.bar', json.dumps({"some": "data"})) - - self.client._put.assert_called_with('http://foo.bar', - json.dumps({"some": "data"})) - - self.assertTrue(isinstance(r, requests.Response)) - - def test_delete_data(self): - self.client._delete = MagicMock(name="_delete", - return_value=self.response_ok) - - r = self.client.delete('http://foo.bar', json.dumps({"some": "data"})) - - self.client._delete.assert_called_with('http://foo.bar', - json.dumps({"some": "data"})) - - self.assertTrue(isinstance(r, requests.Response)) - - - def test_delete_no_data(self): - self.client._delete = MagicMock(name="_delete", - return_value=self.response_ok) - - r = self.client.delete('http://foo.bar') - - self.client._delete.assert_called_with('http://foo.bar', None) - - self.assertTrue(isinstance(r, requests.Response)) - - def test_head(self): - self.client._head = MagicMock(name="_head", - return_value=self.response_ok) - #self.raise_errors = MagicMock(name="raise_errors") - - r = self.client.head('http://foo.bar') - - self.client._head.assert_called_with('http://foo.bar') - - self.assertTrue(isinstance(r, requests.Response)) diff --git a/python/test_struct.py b/python/test_struct.py deleted file mode 100755 index 1539b0f..0000000 --- a/python/test_struct.py +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. -# Copyright (C) 2014 The Trustees of Indiana University -# -# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 -# -# 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 logging -from bottle import run as bottle_run -from bottle import route - -import oslo.config.cfg as cfg -import hpsdnclient as hp - -from SciPass import SciPass - -ETH_TYPE_IP = 0x0800 - - -""" - Forwarding rule Priorities - BlackList - WhiteList - Balancer - Default -""" - - -class TestRest(): - def __init__(self, api): - self.api = api - - #GET /scipass/test - @route('/scipass/test') - def test(self): - return "test" - - @route('/scipass/flows') - def test(self): - flows = self.api.get_food_flows - return flows - - -class TestVAN(): - - def __init__(self, user, pw, controller): - - logging.basicConfig() - self.logger = logging.getLogger(__name__) - - #--- register for configuration options - self.CONF = cfg.CONF - self.CONF.register_opts([ - cfg.StrOpt('SciPassConfig',default='../etc/SciPass/SciPass.xml', - help='where to find the SciPass config file'), - ]) - - self.logger.error("Starting SciPass") - # - api = SciPass(logger = self.logger, - config_file = self.CONF.SciPassConfig ) - api.registerForwardingStateChangeHandler(self.changeSwitchForwardingState) - - self.api = api - - self.logger.debug("Connecting to VAN controller" + controller) - auth = hp.XAuthToken(user=user, password=pw, server=controller) - api = hp.Api(controller=controller, auth=auth) - - - def start_rest(self, host, port): - module = "SciPass" - bottle_run(module, host=host, port=port) - - def changeSwitchForwardingState(self, dpid=None, header=None, actions=None, command=None, idle_timeout=None, hard_timeout=None, priority=1): - self.logger.debug("Changing switch forwarding state") - - - obj = {} - - if(header.has_key('dl_type')): - if(header['dl_type'] == None): - obj['dl_type'] = None - else: - obj['dl_type'] = int(header['dl_type']) - else: - obj['dl_type'] = ETH_TYPE_IP - - if(header.has_key('phys_port')): - obj['in_port'] = int(header['phys_port']) - else: - obj['in_port'] = None - - if(header.has_key('nw_src')): - obj['nw_src'] = int(header['nw_src']) - else: - obj['nw_src'] = None - - if(header.has_key('nw_src_mask')): - obj['nw_src_mask'] = int(header['nw_src_mask']) - else: - obj['nw_src_mask'] = None - - if(header.has_key('nw_dst')): - obj['nw_dst'] = int(header['nw_dst']) - else: - obj['nw_dst'] = None - - if(header.has_key('nw_dst_mask')): - obj['nw_dst_mask'] = int(header['nw_dst_mask']) - else: - obj['nw_dst_mask'] = None - - if(header.has_key('tp_src')): - obj['tp_src'] = int(header['tp_src']) - else: - obj['tp_src'] = None - - if(header.has_key('tp_dst')): - obj['tp_dst'] = int(header['tp_dst']) - else: - obj['tp_dst'] = None - - - def flushRules(self, dpid): - print dpid - - - -def start_scipass_van(): - - username = "sdn" - password = "skyline" - controller = "15.126.229.78" - van = TestVAN(username, password, controller) - van.start_rest("127.0.0.1",8090) - - - -if __name__ == '__main__': - start_scipass_van() - From 8bd208f5485514b27f81322e9503312471b6bb99 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Mon, 24 Nov 2014 16:01:12 -0800 Subject: [PATCH 72/81] Moved back to coverage tests instead of nosetests in .travis.yml --- .travis.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 832a9cd..37eed9c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,10 @@ language: python + +branches: + only: + - master + - hp-van + python: - "2.6" - "2.7" @@ -17,7 +23,7 @@ install: script: - cd python - - nosetests --with-coverage t/SimpleBalancerTest.py t/SciPassTest_mininet.py t/SciPassTest.py + - coverage run --source=./ --omit=__init__.py,*Test.py,t/SciPass.py t/SciPassTest_mininet.py; coverage report -m; python -m coverage xml;coverage annotate; coverage html; after_success: coveralls From 489314fbc29ebc7e36d4f8191254fff9a16bd430 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Mon, 24 Nov 2014 16:12:24 -0800 Subject: [PATCH 73/81] Removed HPVAN from SciTest_mininet.py --- python/t/SciPassTest_mininet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/t/SciPassTest_mininet.py b/python/t/SciPassTest_mininet.py index 3a85499..5835b01 100644 --- a/python/t/SciPassTest_mininet.py +++ b/python/t/SciPassTest_mininet.py @@ -11,7 +11,6 @@ import ipaddr import os from SciPass import SciPass -from HPVAN import HPVAN import libxml2 logging.basicConfig() From 59218a70589e6278d2fd5223b5dc4eace7624295 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Mon, 24 Nov 2014 17:26:19 -0800 Subject: [PATCH 74/81] Pointed Coveralls badge at master branch --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ced1aa..faaa194 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ SciPass ======= [![Travis Build status](https://travis-ci.org/chrissmall22/SciPass.svg?branch=master)](https://travis-ci.org/chrissmall22/SciPass) -[![Coverage Status](https://img.shields.io/coveralls/chrissmall22/SciPass.svg)](https://coveralls.io/r/chrissmall22/SciPass) +[![Coverage Status](https://img.shields.io/coveralls/chrissmall22/SciPass.svg)](https://coveralls.io/r/chrissmall22/SciPass?branch=master) SciPass is a SDN powered Science DMZ and IDS Load Balancer. From b020ba7e61111f9df13f695a22d13015164319ec Mon Sep 17 00:00:00 2001 From: Chris Small Date: Mon, 24 Nov 2014 17:14:53 -0800 Subject: [PATCH 75/81] Moved Vagrant manifest to use the GlobalNOC git repository --- python/requirements.txt | 4 +--- resources/puppet/manifests/scipass.pp | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/python/requirements.txt b/python/requirements.txt index a19eb29..d422751 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -1,10 +1,8 @@ Routes>=1.12.3 bottle>=0.10.6 coverage>=3.7.1 -hp-sdn-client>=1.1.0 mock>=1.0.1 ipaddr>=2.1.0 xmlrunner>=1.7.7 WebOb>=1.1.1 -oslo.config>=1.2.1 -ecdsa>=0.11 + diff --git a/resources/puppet/manifests/scipass.pp b/resources/puppet/manifests/scipass.pp index ee62b3c..61dbe34 100644 --- a/resources/puppet/manifests/scipass.pp +++ b/resources/puppet/manifests/scipass.pp @@ -68,7 +68,7 @@ ensure => present, provider => git, user => 'vagrant', - source => 'https://github.com/chrissmall22/SciPass', + source => 'https://github.com/GlobalNOC/SciPass', revision => 'odl' } From a626b2cccfb599ed19ed90bf111fac80ff04001f Mon Sep 17 00:00:00 2001 From: Chris Small Date: Tue, 25 Nov 2014 11:12:23 -0800 Subject: [PATCH 76/81] Vagrant VM pulls from GlobalNOC source --- resources/puppet/manifests/scipass.pp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/puppet/manifests/scipass.pp b/resources/puppet/manifests/scipass.pp index 61dbe34..9299405 100644 --- a/resources/puppet/manifests/scipass.pp +++ b/resources/puppet/manifests/scipass.pp @@ -69,7 +69,7 @@ provider => git, user => 'vagrant', source => 'https://github.com/GlobalNOC/SciPass', - revision => 'odl' + revision => 'master' } vcsrepo { '/home/vagrant/ryu': @@ -86,4 +86,4 @@ user => 'vagrant', path => $::path, timeout => 0 -} \ No newline at end of file +} From e0b549612d54c1ad2de7ef9c4b219c3e8685b2ae Mon Sep 17 00:00:00 2001 From: Chris Small Date: Tue, 25 Nov 2014 11:13:02 -0800 Subject: [PATCH 77/81] Use python -m coverage so this works on both RH and ubuntu --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3dbb589..d0d84f4 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ clean: rm -rf dist test: - cd python; coverage run --source=./ --omit=__init__.py,*Test.py,t/SciPass.py t/SciPassTest.py; coverage report -m; coverage xml;coverage annotate; coverage html; + cd python; python -m coverage run --source=./ --omit=__init__.py,*Test.py,t/SciPass.py t/SciPassTest.py; python -m coverage report -m; python -m coverage xml;python -m coverage annotate; python -m coverage html; test_mininet: - cd python; coverage run --source=./ --omit=__init__.py,*Test.py,t/SciPass.py t/SciPassTest_mininet.py; coverage report -m; python -m coverage xml;coverage annotate; coverage html; + cd python; python -m coverage run --source=./ --omit=__init__.py,*Test.py,t/SciPass.py t/SciPassTest_mininet.py; coverage report -m; python -m coverage xml;python -m coverage annotate; python -m coverage html; dist: rm -rf dist/$(NAME)-$(VERSION) From 67e9af336a2d98b599387d1980f447fa80eaf14f Mon Sep 17 00:00:00 2001 From: Chris Small Date: Tue, 25 Nov 2014 11:27:31 -0800 Subject: [PATCH 78/81] Use sudo when installing ryu in Vagrant VM --- resources/puppet/manifests/scipass.pp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/puppet/manifests/scipass.pp b/resources/puppet/manifests/scipass.pp index 9299405..ee08880 100644 --- a/resources/puppet/manifests/scipass.pp +++ b/resources/puppet/manifests/scipass.pp @@ -81,7 +81,7 @@ } exec { 'Install Ryu': - command => 'python ./setup.py install', + command => 'sudo python ./setup.py install', cwd => '/home/vagrant/ryu', user => 'vagrant', path => $::path, From 23c7f9a912b4e399b86a7fd9860b8a1d808259f3 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Tue, 25 Nov 2014 13:35:02 -0800 Subject: [PATCH 79/81] Added tox to requirements Added python-tox to requirements so the VM can use hp-sdn-client or odl-client in the future --- resources/puppet/manifests/scipass.pp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/puppet/manifests/scipass.pp b/resources/puppet/manifests/scipass.pp index ee08880..a7188c3 100644 --- a/resources/puppet/manifests/scipass.pp +++ b/resources/puppet/manifests/scipass.pp @@ -24,7 +24,8 @@ 'msgpack-python', 'python-greenlet', 'python-pip', - 'python-dev' + 'python-dev', + 'python-tox' ] package { $deps: From aeca39ae78ef861429180f0c3aba524873d941d5 Mon Sep 17 00:00:00 2001 From: Chris Small Date: Wed, 26 Nov 2014 10:24:06 -0800 Subject: [PATCH 80/81] Fixed path for copying SciPass.xml file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index faaa194..80145f2 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ To start the Ryu version of SciPass cd scipass/python sudo mkdir /etc/SciPass - sudo cp t/etc/SciPass-mininet.xml /etc/SciPass + sudo cp t/etc/SciPass-mininet.xml /etc/SciPass/SciPass.xml ryu-manager Ryu.py From 4cf62e88e263c64b3491445a1742712b562f03bb Mon Sep 17 00:00:00 2001 From: Chris Small Date: Wed, 26 Nov 2014 10:34:00 -0800 Subject: [PATCH 81/81] Deleted test-OpenDayLight.py --- python/t/test-OpenDaylight.py | 295 ---------------------------------- 1 file changed, 295 deletions(-) delete mode 100644 python/t/test-OpenDaylight.py diff --git a/python/t/test-OpenDaylight.py b/python/t/test-OpenDaylight.py deleted file mode 100644 index 0101227..0000000 --- a/python/t/test-OpenDaylight.py +++ /dev/null @@ -1,295 +0,0 @@ -#!/usr/bin/python -""" -Tests for the OpenDaylight REST API interface - -Copyright 2013 The University of Wisconsin Board of Regents - - Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 - - 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. - - Written by: Dale W. Carder, dwcarder@wisc.edu - Network Services Group - Division of Information Technology - University of Wisconsin at Madison - -This material is based upon work supported by the National Science -Foundation under Grant No. 1247322 -""" - -import time -import unittest -sys.path.append(".") - -from OpenDaylight import OpenDaylight -from OpenDaylight import OpenDaylightFlow -from OpenDaylight import OpenDaylightNode -from OpenDaylight import OpenDaylightError -from mininet.net import Mininet -#from mininet.util import dumpNodeConnections -#from mininet.log import setLogLevel -from mininet.topo import Topo -from mininet.node import RemoteController - -# Edit these as necessary for your organization -CONTROLLER = '10.10.10.1' -USERNAME = 'admin' -PASSWORD = 'admin' -SWITCH_1 = '99:99:99:00:00:00:01:00' -# This is chosen so that it does not conflict with any other -# switches that may be associated to a controller that is not -# dedicated explicitly for testing - -class TestSequenceFunctions(unittest.TestCase): - """Tests for OpenDaylight - - At this point, tests for OpenDaylightFlow and OpenDaylightNode - are intermingled. These could be seperated out into seperate - suites. - """ - - def setUp(self): - odl = OpenDaylight() - odl.setup['hostname'] = CONTROLLER - odl.setup['username'] = USERNAME - odl.setup['password'] = PASSWORD - self.flow = OpenDaylightFlow(odl) - self.node = OpenDaylightNode(odl) - - self.switch_id_1 = SWITCH_1 - - self.odl_test_flow_1 = {u'actions': u'DROP', - u'etherType': u'0x800', - u'ingressPort': u'1', - u'installInHw': u'true', - u'name': u'odl-test-flow1', - u'node': {u'@id': self.switch_id_1, u'@type': u'OF'}, - u'priority': u'500'} - - self.odl_test_flow_2 = {u'actions': u'DROP', - u'etherType': u'0x800', - u'ingressPort': u'2', - u'installInHw': u'true', - u'name': u'odl-test-flow2', - u'node': {u'@id': self.switch_id_1, u'@type': u'OF'}, - u'priority': u'500'} - - - def test_01_delete_flows(self): - """Clean up from any previous test run, just delete these - flows if they exist. - """ - try: - self.flow.delete(self.odl_test_flow_1['node']['@id'], - self.odl_test_flow_1['name']) - except: - pass - - try: - self.flow.delete(self.odl_test_flow_2['node']['@id'], - self.odl_test_flow_2['name']) - except: - pass - - def test_10_add_flow(self): - """Add a sample flow onto the controller - """ - self.flow.add(self.odl_test_flow_1) - self.assertEqual(self.flow.request.status_code, 201) - - def test_10_add_flow2(self): - """Add a sample flow onto the controller - """ - self.flow.add(self.odl_test_flow_2) - self.assertEqual(self.flow.request.status_code, 201) - - def test_15_add_flow2(self): - """Add a duplicate flow onto the controller - """ - try: - self.flow.add(self.odl_test_flow_2) - except OpenDaylightError: - pass - except e: - self.fail('Unexpected exception thrown:', e) - else: - self.fail('Expected Exception not thrown') - - def test_20_get_flow(self): - """Retrieve the specific flow back from the controller - """ - self.flow.get(node_id=self.switch_id_1, flow_name='odl-test-flow1') - self.assertEqual(self.flow.flows, self.odl_test_flow_1) - self.assertEqual(self.flow.request.status_code, 200) - - def test_20_get_flow2(self): - """Retrieve the specific flow back from the controller - """ - self.flow.get(node_id=self.switch_id_1, flow_name='odl-test-flow1') - self.assertEqual(self.flow.flows, self.odl_test_flow_1) - self.assertEqual(self.flow.request.status_code, 200) - - - def test_30_get_all_switch_flows(self): - """Retrieve all flows from this switch back from the controller - """ - self.flow.get(node_id=self.switch_id_1) - self.assertTrue(self.odl_test_flow_1 in self.flow.flows) - self.assertTrue(self.odl_test_flow_2 in self.flow.flows) - self.assertEqual(self.flow.request.status_code, 200) - - def test_30_get_all_flows(self): - """Retrieve all flows back from the controller - """ - self.flow.get() - self.assertTrue(self.odl_test_flow_1 in self.flow.flows) - self.assertTrue(self.odl_test_flow_2 in self.flow.flows) - self.assertEqual(self.flow.request.status_code, 200) - - def test_30_get_flows_invalid_switch(self): - """Try to get a flow from a non-existant switch - """ - try: - # This dpid is specifically chosen figuring that it - # would not be in use in a production system. Plus, I - # simply just like the number 53. - self.flow.get(node_id='53:53:53:53:53:53:53:53') - except OpenDaylightError: - pass - except e: - self.fail('Unexpected exception thrown:', e) - else: - self.fail('Expected Exception not thrown') - - def test_40_get_flows_invalid_flowname(self): - """Try to get a flow that does not exist. - """ - try: - self.flow.get(node_id=self.switch_id_1, flow_name='foo-foo-foo-bar') - except OpenDaylightError: - pass - except e: - self.fail('Unexpected exception thrown:', e) - else: - self.fail('Expected Exception not thrown') - - def test_50_delete_flow(self): - """Delete flow 1. - """ - self.flow.delete(self.odl_test_flow_1['node']['@id'], - self.odl_test_flow_1['name']) - self.assertEqual(self.flow.request.status_code, 200) - - - def test_51_deleted_flow_get(self): - """Verify that the deleted flow does not exist. - """ - try: - self.flow.get(node_id=self.switch_id_1, flow_name='odl-test-flow1') - except OpenDaylightError: - pass - except e: - self.fail('Unexpected exception thrown:', e) - else: - self.fail('Expected Exception not thrown') - - def test_55_delete_flow2(self): - """Delete flow 2 - """ - self.flow.delete(self.odl_test_flow_2['node']['@id'], - self.odl_test_flow_2['name']) - self.assertEqual(self.flow.request.status_code, 200) - - - #TODO: Add invalid flow that has a bad port - #TODO: Add invalid flow that has a non-existant switch - #TODO: Add invalid flow that has an invalid switch name (non-hexadecimal), - # see https://bugs.opendaylight.org/show_bug.cgi?id=27 - - def test_60_get_all_nodes(self): - """Get all of the nodes on the controller - - TODO: verify that SWITCH_1 is contained in the response - """ - self.node.get_nodes() - self.assertEqual(self.node.request.status_code, 200) - - def test_60_get_node_connector(self): - """Retrieve a list of all the node connectors and their properties - in a given node - - TODO: verify that SWITCH_1 is contained in the response - """ - self.node.get_node_connectors(SWITCH_1) - self.assertEqual(self.node.request.status_code, 200) - - - def test_60_get_bad_node_connector(self): - """Retrieve a list of all the node connectors and their properties - in a given node for a node that does not exist - """ - try: - self.node.get_node_connectors('53:53:53:53:53:53:53:53') - except OpenDaylightError: - pass - except e: - self.fail('Unexpected exception thrown:', e) - else: - self.fail('Expected Exception not thrown') - - - def test_60_save(self): - """Save the switch configurations. - It's not clear that this can be easily tested, so we just - see if this call works or not based on the http status code. - """ - self.node.save() - self.assertEqual(self.node.request.status_code, 200) - - -class SingleSwitchTopo(Topo): - "Single switch connected to n hosts." - def __init__(self, n=2, **opts): - # Initialize topology and default options - Topo.__init__(self, **opts) - # mininet/ovswitch does not want ':'s in the dpid - switch_id = SWITCH_1.translate(None, ':') - switch = self.addSwitch('s1', dpid=switch_id) - # Python's range(N) generates 0..N-1 - for h in range(n): - host = self.addHost('h%s' % (h + 1)) - self.addLink(host, switch) - -def setup_mininet_simpleTest(): - "Create and test a simple network" - topo = SingleSwitchTopo(n=4) - #net = Mininet(topo) - net = Mininet( topo=topo, controller=lambda name: RemoteController( - name, ip=CONTROLLER ) ) - net.start() - #print "Dumping host connections" - #dumpNodeConnections(net.hosts) - - #time.sleep(300) - - #print "Testing network connectivity" - #net.pingAll() - #net.stop() - -if __name__ == '__main__': - # Tell mininet to print useful information - #setLogLevel('info') - - setup_mininet_simpleTest() - time.sleep(10) - unittest.main() - -