diff --git a/features/examples/topology.show_switch_status.feature b/features/examples/topology.show_switch_status.feature index 031d31198..e734f156e 100644 --- a/features/examples/topology.show_switch_status.feature +++ b/features/examples/topology.show_switch_status.feature @@ -39,7 +39,7 @@ Feature: show_switch_status And I run `trema run -c topology.conf -d` And wait until "topology" is up And *** sleep 16 *** - And I run `trema run ../../objects/examples/topology/show_switch_status` + When I run `trema run ../../objects/examples/topology/show_switch_status` Then the output should contain: """ Switch status @@ -49,18 +49,8 @@ Feature: show_switch_status """ Port status """ - And the output should contain: - """ - dpid : 0xe0, port : 1(trema1-0), status : up, external : true - """ - And the output should contain: - """ - dpid : 0xe0, port : 65534(vsw_0xe0), status : down, external : false - """ - And the output should contain: - """ - dpid : 0xe0, port : 2(trema0-0), status : up, external : true - """ + And the output should match /dpid : 0xe0, port : 1\(.+\), status : up, external : true/ + And the output should match /dpid : 0xe0, port : 2\(.+\), status : up, external : true/ @slow_process Scenario: One openflow switches, two servers (Ruby version) @@ -97,7 +87,7 @@ Feature: show_switch_status And I run `trema run -c topology.conf -d` And wait until "topology" is up And *** sleep 16 *** - And I run `trema run ../../src/examples/topology/show-switch-status.rb` + When I run `trema run ../../src/examples/topology/show-switch-status.rb` Then the output should contain: """ Switch status @@ -107,15 +97,5 @@ Feature: show_switch_status """ Port status """ - And the output should contain: - """ - dpid : 0xe0, port : 1(trema1-0), status : up, external : true - """ - And the output should contain: - """ - dpid : 0xe0, port : 65534(vsw_0xe0), status : down, external : false - """ - And the output should contain: - """ - dpid : 0xe0, port : 2(trema0-0), status : up, external : true - """ + And the output should match /dpid : 0xe0, port : 1\(.+\), status : up, external : true/ + And the output should match /dpid : 0xe0, port : 2\(.+\), status : up, external : true/ diff --git a/features/topology/topology.cache.feature b/features/topology/topology.cache.feature deleted file mode 100644 index 36c10452d..000000000 --- a/features/topology/topology.cache.feature +++ /dev/null @@ -1,131 +0,0 @@ -Feature: topology cache Ruby API - - As a developer using Trema - I want to develop topology aware application. - - - Scenario: cache_ready handler - Given a file named "topology.conf" with: - """ - vswitch("topology1") { datapath_id "0x1" } - - run { - path "../../objects/topology/topology" - options "--always_run_discovery" - } - - event :port_status => "topology", :packet_in => "filter", :state_notify => "topology" - filter :lldp => "topology", :packet_in => "TestTopology" - """ - And a file named "TestTopology.rb" with: - """ - require "trema/topology" - require "trema/topology/topology_cache" - - class TestController < Controller - include Topology - - def topology_ready - send_rebuild_cache_request - end - - def cache_ready c - info "cache_ready" - end - end - """ - And I run `trema run TestTopology.rb -c topology.conf -d` - And wait until "topology" is up - Then the file "../../tmp/log/TestController.log" should contain "cache_ready" - - - Scenario: get cache without rebuilding - Given a file named "topology.conf" with: - """ - vswitch("topology1") { datapath_id "0x1" } - vswitch("topology2") { datapath_id "0x2" } - - link "topology1", "topology2" - - run { - path "../../objects/topology/topology" - options "--always_run_discovery" - } - - event :port_status => "topology", :packet_in => "filter", :state_notify => "topology" - filter :lldp => "topology", :packet_in => "TestTopology" - """ - And a file named "TestTopology.rb" with: - """ - require "trema/topology" - require "trema/topology/topology_cache" - - class TestController < Controller - include Topology - - oneshot_timer_event :delayed_event, 4 - - def delayed_event - send_rebuild_cache_request false - end - - def cache_ready c - info c.to_s - end - end - """ - And I run `trema run TestTopology.rb -c topology.conf -d` - And wait until "topology" is up - And *** sleep 4 *** - Then the file "../../tmp/log/TestController.log" should contain: - """ - Cache: - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Switch: 0x1 - {status:1, up:true} - Port: 0x1:1 - { - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Links_in - <= 0x2:1 - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Links_out - => 0x2:1 - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Switch: 0x2 - {status:1, up:true} - Port: 0x2:1 - { - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Links_in - <= 0x1:1 - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Links_out - => 0x1:1 - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Port: 0x1:1 - { - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Port: 0x2:1 - { - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Link: (0x1:1)->(0x2:1) - {status:1, unstable:false, up:true} - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Link: (0x2:1)->(0x1:1) - {status:1, unstable:false, up:true} - """ - - diff --git a/features/topology/topology.map.feature b/features/topology/topology.map.feature new file mode 100644 index 000000000..6110a1b81 --- /dev/null +++ b/features/topology/topology.map.feature @@ -0,0 +1,167 @@ +Feature: topology map Ruby API + + As a developer using Trema + I want to develop topology aware application. + + Scenario: map_ready handler + Given a file named "topology.conf" with: + """ + vswitch("topology1") { datapath_id "0x1" } + + run { + path "../../objects/topology/topology" + options "--always_run_discovery" + } + + event :port_status => "topology", :packet_in => "filter", :state_notify => "topology" + filter :lldp => "topology", :packet_in => "MapReadyTestTopology" + """ + And a file named "MapReadyTestTopology.rb" with: + """ + require "trema/topology/map_api" + + class MapReadyTestTopology < Controller + include TopologyMap + + oneshot_timer_event :test_start, 0 + + def test_start + send_rebuild_map_request + end + + def map_ready c + info "map_ready" + end + end + """ + When I run `trema run MapReadyTestTopology.rb -c topology.conf -d` + And wait until "topology" is up + And *** sleep 1 *** + Then the file "../../tmp/log/MapReadyTestTopology.log" should contain "map_ready" + + Scenario: map_ready handler (block) + Given a file named "topology.conf" with: + """ + vswitch("topology1") { datapath_id "0x1" } + + run { + path "../../objects/topology/topology" + options "--always_run_discovery" + } + + event :port_status => "topology", :packet_in => "filter", :state_notify => "topology" + filter :lldp => "topology", :packet_in => "MapReadyTestTopologyB" + """ + And a file named "MapReadyTestTopologyB.rb" with: + """ + require "trema/topology/map_api" + + class MapReadyTestTopologyB < Controller + include TopologyMap + + oneshot_timer_event :test_start, 0 + + def test_start + send_rebuild_map_request { |map| + info "map_ready_block" + } + end + + def map_ready c + info "map_ready_should_not_be_called" + end + end + """ + When I run `trema run MapReadyTestTopologyB.rb -c topology.conf -d` + And wait until "topology" is up + And *** sleep 1 *** + Then the file "../../tmp/log/MapReadyTestTopologyB.log" should contain "map_ready_block" + And the file "../../tmp/log/MapReadyTestTopologyB.log" should not contain "map_ready_should_not_be_called" + + Scenario: get map without rebuilding + Given a file named "topology.conf" with: + """ + vswitch("topology1") { datapath_id "0x1" } + vswitch("topology2") { datapath_id "0x2" } + + link "topology1", "topology2" + + run { + path "../../objects/topology/topology" + options "--always_run_discovery" + } + + event :port_status => "topology", :packet_in => "filter", :state_notify => "topology" + filter :lldp => "topology", :packet_in => "RebuildTestController" + """ + And a file named "RebuildTestController.rb" with: + """ + require "trema/topology/map_api" + + class RebuildTestController < Controller + include TopologyMap + + oneshot_timer_event :delayed_event, 4 + + def delayed_event + send_rebuild_map_request false + end + + def map_ready c + info c.to_s + end + end + """ + When I run `trema run RebuildTestController.rb -c topology.conf -d` + And wait until "topology" is up + And *** sleep 4 *** + Then the file "../../tmp/log/RebuildTestController.log" should contain: + """ + Map: + """ + And the file "../../tmp/log/RebuildTestController.log" should contain: + """ + Switch: 0x1 - {up:true} + """ + And the file "../../tmp/log/RebuildTestController.log" should match / Port: 0x1:1 - \{external:false, mac:"[0-9a-f:]+", name:".*", up:true\}/ + And the file "../../tmp/log/RebuildTestController.log" should contain: + """ + Links_in + <= 0x2:1 + """ + And the file "../../tmp/log/RebuildTestController.log" should contain: + """ + Links_out + => 0x2:1 + """ + And the file "../../tmp/log/RebuildTestController.log" should contain: + """ + Switch: 0x2 - {up:true} + """ + And the file "../../tmp/log/RebuildTestController.log" should match / Port: 0x2:1 - \{external:false, mac:"[0-9a-f:]+", name:".*", up:true\}/ + And the file "../../tmp/log/RebuildTestController.log" should contain: + """ + Links_in + <= 0x1:1 + """ + And the file "../../tmp/log/RebuildTestController.log" should contain: + """ + Links_out + => 0x1:1 + """ + And the file "../../tmp/log/RebuildTestController.log" should contain: + """ + Port: 0x1:1 - { + """ + And the file "../../tmp/log/RebuildTestController.log" should contain: + """ + Port: 0x2:1 - { + """ + And the file "../../tmp/log/RebuildTestController.log" should contain: + """ + Link: (0x1:1)->(0x2:1) - {unstable:false, up:true} + """ + And the file "../../tmp/log/RebuildTestController.log" should contain: + """ + Link: (0x2:1)->(0x1:1) - {unstable:false, up:true} + """ diff --git a/features/topology/topology.ruby.feature b/features/topology/topology.ruby.feature index b1450b077..ccbfa75c1 100644 --- a/features/topology/topology.ruby.feature +++ b/features/topology/topology.ruby.feature @@ -1,10 +1,9 @@ Feature: topology Ruby API - + As a developer using Trema I want to develop topology aware application. - - Scenario: Receive topology_ready message + Scenario: Receive subscribe_topology_reply if handler defined Given a file named "topology.conf" with: """ vswitch("topology1") { datapath_id "0x1" } @@ -15,27 +14,32 @@ Feature: topology Ruby API } event :port_status => "topology", :packet_in => "filter", :state_notify => "topology" - filter :lldp => "topology", :packet_in => "TestTopology" + filter :lldp => "topology", :packet_in => "SubscribeTestTopology" """ - And a file named "TestTopology.rb" with: + And a file named "SubscribeTestTopology.rb" with: """ - require "trema/topology" - require "trema/topology/topology_cache" - - class TestController < Controller + class SubscribeTestTopology < Controller include Topology - def topology_ready - info "topology_ready" + def subscribe_topology_reply + info "subscribe_topology_reply" + info "subscribed? is true" if subscribed? + + send_unsubscribe_topology do + self.shutdown! + end + end + + def link_status_updated link_attrs end end """ - And I run `trema run TestTopology.rb -c topology.conf -d` - And wait until "topology" is up - Then the file "../../tmp/log/TestController.log" should contain "topology_ready" + When I run `trema run SubscribeTestTopology.rb -c topology.conf -d` + And wait until "topology" is up + Then the file "../../tmp/log/SubscribeTestTopology.log" should contain "subscribe_topology_reply" + And the file "../../tmp/log/SubscribeTestTopology.log" should contain "subscribed? is true" - - Scenario: Receive topology_discovery_ready message + Scenario: Will not auto subscribe if no handler defined Given a file named "topology.conf" with: """ vswitch("topology1") { datapath_id "0x1" } @@ -46,28 +50,50 @@ Feature: topology Ruby API } event :port_status => "topology", :packet_in => "filter", :state_notify => "topology" - filter :lldp => "topology", :packet_in => "TestTopology" + filter :lldp => "topology", :packet_in => "NoSubscribeTestTopology" + """ + And a file named "NoSubscribeTestTopology.rb" with: """ - And a file named "TestTopology.rb" with: + class NoSubscribeTestTopology < Controller + include Topology + + oneshot_timer_event :test_start, 0 + def test_start + info "subscribed? is false" if not subscribed? + end + end + """ + When I run `trema run NoSubscribeTestTopology.rb -c topology.conf -d` + And wait until "topology" is up + Then the file "../../tmp/log/NoSubscribeTestTopology.log" should contain "subscribed? is false" + + Scenario: enable_topology_discovery should startup discovery. + Given a file named "topology.conf" with: """ - require "trema/topology" - require "trema/topology/topology_cache" + vswitch("topology1") { datapath_id "0x1" } + + run { + path "../../objects/topology/topology" + } - class TestController < Controller + event :port_status => "topology", :packet_in => "filter", :state_notify => "topology" + filter :lldp => "topology", :packet_in => "EnableTopology" + """ + And a file named "EnableTopology.rb" with: + """ + class EnableTopology < Controller include Topology - def topology_ready - enable_topology_discovery - end - def topology_discovery_ready - info "topology_discovery_ready" + oneshot_timer_event :test_start, 0 + + def test_start + send_enable_topology_discovery end end """ - And I run `trema run TestTopology.rb -c topology.conf -d` - And wait until "topology" is up - Then the file "../../tmp/log/TestController.log" should contain "topology_discovery_ready" - + When I run `trema run EnableTopology.rb -c topology.conf -d` + And wait until "topology" is up + Then the file "../../tmp/log/topology.log" should contain "Enabling topology discovery." Scenario: Receive switch, port, link update notifications Given a file named "topology.conf" with: @@ -83,57 +109,54 @@ Feature: topology Ruby API } event :port_status => "topology", :packet_in => "filter", :state_notify => "topology" - filter :lldp => "topology", :packet_in => "TestTopology" + filter :lldp => "topology", :packet_in => "TopologyEventTestController" """ - And a file named "TestTopology.rb" with: + And a file named "TopologyEventTestController.rb" with: """ - require "trema/topology" - require "trema/topology/topology_cache" - - class TestController < Controller + class TopologyEventTestController < Controller include Topology - - def switch_status_updated sw - info Switch[ sw ].to_s + + oneshot_timer_event :time_out, 4 + def time_out + send_unsubscribe_topology do + self.shutdown! + end + end + + def switch_status_up dpid + info Switch.new( dpid ).to_s end def port_status_updated p - info Port[ p ].to_s + info Port.new( p ).to_s end def link_status_updated l - info Link[ l ].to_s + info Link.new( l ).to_s end end """ - And I run `trema run TestTopology.rb -c topology.conf -d` - And wait until "topology" is up - And *** sleep 4 *** - Then the file "../../tmp/log/TestController.log" should contain: - """ - Switch: 0x1 - {status:1, up:true} - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Switch: 0x2 - {status:1, up:true} - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Port: 0x1:1 - { - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Port: 0x2:1 - { - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Link: (0x1:1)->(0x2:1) - {status:1, unstable:false, up:true} - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Link: (0x2:1)->(0x1:1) - {status:1, unstable:false, up:true} - """ - + When I run `trema run TopologyEventTestController.rb -c topology.conf -d` + And wait until "topology" is up + And *** sleep 6 *** + Then the file "../../tmp/log/TopologyEventTestController.log" should contain: + """ + Switch: 0x1 - {up:true} + """ + And the file "../../tmp/log/TopologyEventTestController.log" should contain: + """ + Switch: 0x2 - {up:true} + """ + And the file "../../tmp/log/TopologyEventTestController.log" should match /Port: 0x1:1 - \{external:false, mac:"[0-9a-f:]+", name:".*", up:true\}/ + And the file "../../tmp/log/TopologyEventTestController.log" should match /Port: 0x2:1 - \{external:false, mac:"[0-9a-f:]+", name:".*", up:true\}/ + And the file "../../tmp/log/TopologyEventTestController.log" should contain: + """ + Link: (0x1:1)->(0x2:1) - {unstable:false, up:true} + """ + And the file "../../tmp/log/TopologyEventTestController.log" should contain: + """ + Link: (0x2:1)->(0x1:1) - {unstable:false, up:true} + """ Scenario: Receive get all switch, port, link status with block Given a file named "topology.conf" with: @@ -149,59 +172,49 @@ Feature: topology Ruby API } event :port_status => "topology", :packet_in => "filter", :state_notify => "topology" - filter :lldp => "topology", :packet_in => "TestTopology" + filter :lldp => "topology", :packet_in => "TopologyEventTestControllerB" """ - And a file named "TestTopology.rb" with: + And a file named "TopologyEventTestControllerB.rb" with: """ - require "trema/topology" - require "trema/topology/topology_cache" - - class TestController < Controller + class TopologyEventTestControllerB < Controller include Topology oneshot_timer_event :show_topology, 4 def show_topology - send_all_switch_status_request do |sw| - sw.each { |each| info Switch[ each ].to_s } + get_all_switch_status do |sw| + sw.each { |each| info Switch.new( each ).to_s } end - send_all_port_status_request do |ports| - ports.each { |each| info Port[ each ].to_s } + get_all_port_status do |ports| + ports.each { |each| info Port.new( each ).to_s } end - send_all_link_status_request do |links| - links.each { |each| info Link[ each ].to_s } + get_all_link_status do |links| + links.each { |each| info Link.new( each ).to_s } end end end """ - And I run `trema run TestTopology.rb -c topology.conf -d` - And wait until "topology" is up - And *** sleep 4 *** - Then the file "../../tmp/log/TestController.log" should contain: - """ - Switch: 0x1 - {status:1, up:true} - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Switch: 0x2 - {status:1, up:true} - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Port: 0x1:1 - { - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Port: 0x2:1 - { - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Link: (0x1:1)->(0x2:1) - {status:1, unstable:false, up:true} - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Link: (0x2:1)->(0x1:1) - {status:1, unstable:false, up:true} - """ - + When I run `trema run TopologyEventTestControllerB.rb -c topology.conf -d` + And wait until "topology" is up + And *** sleep 4 *** + Then the file "../../tmp/log/TopologyEventTestControllerB.log" should contain: + """ + Switch: 0x1 - {up:true} + """ + And the file "../../tmp/log/TopologyEventTestControllerB.log" should contain: + """ + Switch: 0x2 - {up:true} + """ + And the file "../../tmp/log/TopologyEventTestControllerB.log" should match /Port: 0x1:1 - \{external:false, mac:"[0-9a-f:]+", name:".*", up:true\}/ + And the file "../../tmp/log/TopologyEventTestControllerB.log" should match /Port: 0x2:1 - \{external:false, mac:"[0-9a-f:]+", name:".*", up:true\}/ + And the file "../../tmp/log/TopologyEventTestControllerB.log" should contain: + """ + Link: (0x1:1)->(0x2:1) - {unstable:false, up:true} + """ + And the file "../../tmp/log/TopologyEventTestControllerB.log" should contain: + """ + Link: (0x2:1)->(0x1:1) - {unstable:false, up:true} + """ Scenario: Receive get all switch, port, link status with handler Given a file named "topology.conf" with: @@ -217,62 +230,53 @@ Feature: topology Ruby API } event :port_status => "topology", :packet_in => "filter", :state_notify => "topology" - filter :lldp => "topology", :packet_in => "TestTopology" + filter :lldp => "topology", :packet_in => "GetAllTestController" """ - And a file named "TestTopology.rb" with: + And a file named "GetAllTestController.rb" with: """ - require "trema/topology" - require "trema/topology/topology_cache" - - class TestController < Controller + class GetAllTestController < Controller include Topology oneshot_timer_event :show_topology, 4 def show_topology - send_all_switch_status_request - send_all_port_status_request - send_all_link_status_request + get_all_switch_status + get_all_port_status + get_all_link_status end - def all_switch_status_reply sw - sw.each { |each| info Switch[ each ].to_s } + def all_switch_status sw + sw.each { |each| info Switch.new( each ).to_s } end - def all_port_status_reply ports - ports.each { |each| info Port[ each ].to_s } + def all_port_status ports + ports.each { |each| info Port.new( each ).to_s } end - def all_link_status_reply links - links.each { |each| info Link[ each ].to_s } + def all_link_status links + links.each { |each| info Link.new( each ).to_s } end end """ - And I run `trema run TestTopology.rb -c topology.conf -d` - And wait until "topology" is up - And *** sleep 4 *** - Then the file "../../tmp/log/TestController.log" should contain: - """ - Switch: 0x1 - {status:1, up:true} - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Switch: 0x2 - {status:1, up:true} - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Port: 0x1:1 - { - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Port: 0x2:1 - { - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Link: (0x1:1)->(0x2:1) - {status:1, unstable:false, up:true} - """ - And the file "../../tmp/log/TestController.log" should contain: - """ - Link: (0x2:1)->(0x1:1) - {status:1, unstable:false, up:true} - """ + When I run `trema run GetAllTestController.rb -c topology.conf -d` + And wait until "topology" is up + And *** sleep 4 *** + Then the file "../../tmp/log/GetAllTestController.log" should contain: + """ + Switch: 0x1 - {up:true} + """ + And the file "../../tmp/log/GetAllTestController.log" should contain: + """ + Switch: 0x2 - {up:true} + """ + And the file "../../tmp/log/GetAllTestController.log" should match /Port: 0x1:1 - \{external:false, mac:"[0-9a-f:]+", name:".*", up:true\}/ + And the file "../../tmp/log/GetAllTestController.log" should match /Port: 0x2:1 - \{external:false, mac:"[0-9a-f:]+", name:".*", up:true\}/ + And the file "../../tmp/log/GetAllTestController.log" should contain: + """ + Link: (0x1:1)->(0x2:1) - {unstable:false, up:true} + """ + And the file "../../tmp/log/GetAllTestController.log" should contain: + """ + Link: (0x2:1)->(0x1:1) - {unstable:false, up:true} + """ diff --git a/ruby/trema/topology.c b/ruby/trema/topology.c index 845b2ebcd..5d10b35c7 100644 --- a/ruby/trema/topology.c +++ b/ruby/trema/topology.c @@ -28,6 +28,14 @@ extern VALUE mTrema; VALUE mTopology; +typedef struct topology_callback { + VALUE self; + VALUE block; +} topology_callback; + + +static VALUE topology_subscribe_topology( VALUE self ); +static VALUE topology_enable_topology_discovery( VALUE self ); /* * init_libtopology(service_name) * Initialize topology client. @@ -40,10 +48,31 @@ VALUE mTopology; static VALUE topology_init_libtopology( VALUE self, VALUE service_name ) { init_libtopology( StringValuePtr( service_name ) ); + rb_iv_set( self, "@is_libtopology_initialized", Qtrue ); return self; } +static void +maybe_init_libtopology( VALUE self ) { + ID is_init = rb_intern( "@is_libtopology_initialized" ); + if ( rb_ivar_defined( self, is_init ) == Qfalse || + rb_ivar_get( self, is_init ) == Qfalse ) { + info( "libtopology was not initialized. Default initializing." ); + init_libtopology( "topology" ); + rb_iv_set( self, "@is_libtopology_initialized", Qtrue ); + + if ( rb_funcall( self, rb_intern( "topology_handler_implemented?" ), 0 ) == Qtrue ) { + topology_subscribe_topology( self ); + } + + if ( rb_respond_to( ( VALUE ) self, rb_intern( "link_status_updated" ) ) == Qtrue ) { + topology_enable_topology_discovery( self ); + } + } +} + + /* * finalize_libtopology(service_name) * Finalize topology client. @@ -53,16 +82,18 @@ topology_init_libtopology( VALUE self, VALUE service_name ) { static VALUE topology_finalize_libtopology( VALUE self ) { finalize_libtopology(); + rb_iv_set( self, "@is_libtopology_initialized", Qfalse ); return self; } + static VALUE switch_status_to_hash( const topology_switch_status* sw_status ) { VALUE sw = rb_hash_new(); rb_hash_aset( sw, ID2SYM( rb_intern( "dpid" ) ), ULL2NUM( sw_status->dpid ) ); - rb_hash_aset( sw, ID2SYM( rb_intern( "status" ) ), INT2FIX( (int)sw_status->status ) ); +// rb_hash_aset( sw, ID2SYM( rb_intern( "status" ) ), INT2FIX( (int)sw_status->status ) ); - if( sw_status->status == TD_SWITCH_UP ) { + if ( sw_status->status == TD_SWITCH_UP ) { rb_hash_aset( sw, ID2SYM( rb_intern( "up" ) ), Qtrue ); } else { rb_hash_aset( sw, ID2SYM( rb_intern( "up" ) ), Qfalse ); @@ -70,20 +101,18 @@ switch_status_to_hash( const topology_switch_status* sw_status ) { return sw; } + static void handle_switch_status_updated( void* self, const topology_switch_status* sw_status ) { - rb_iv_set( ( VALUE ) self, "@cache_up_to_date", Qfalse); + rb_iv_set( ( VALUE ) self, "@map_up_to_date", Qfalse); - if ( rb_respond_to( ( VALUE ) self, rb_intern( "switch_status_updated" ) ) == Qtrue ) { - VALUE sw = switch_status_to_hash( sw_status ); - rb_funcall( ( VALUE ) self, rb_intern( "switch_status_updated" ), 1, sw ); - } if ( rb_respond_to( ( VALUE ) self, rb_intern( "_switch_status_updated" ) ) == Qtrue ) { VALUE sw = switch_status_to_hash( sw_status ); rb_funcall( ( VALUE ) self, rb_intern( "_switch_status_updated" ), 1, sw ); } } + static VALUE port_status_to_hash( const topology_port_status* port_status ) { VALUE port = rb_hash_new(); @@ -93,10 +122,16 @@ port_status_to_hash( const topology_port_status* port_status ) { rb_hash_aset( port, ID2SYM( rb_intern( "name" ) ), rb_str_new2( port_status->name ) ); char macaddr[] = "FF:FF:FF:FF:FF:FF"; const uint8_t* mac = port_status->mac; - snprintf ( macaddr, sizeof(macaddr), "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] ); + snprintf( macaddr, sizeof(macaddr), "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] ); rb_hash_aset( port, ID2SYM( rb_intern( "mac" ) ), rb_str_new2( macaddr ) ); - rb_hash_aset( port, ID2SYM( rb_intern( "external" ) ), INT2FIX( (int)port_status->external ) ); - rb_hash_aset( port, ID2SYM( rb_intern( "status" ) ), INT2FIX( (int)port_status->status ) ); +// rb_hash_aset( port, ID2SYM( rb_intern( "external" ) ), INT2FIX( (int)port_status->external ) ); +// rb_hash_aset( port, ID2SYM( rb_intern( "status" ) ), INT2FIX( (int)port_status->status ) ); + + if ( port_status->external == TD_PORT_EXTERNAL ) { + rb_hash_aset( port, ID2SYM( rb_intern( "external" ) ), Qtrue ); + } else { + rb_hash_aset( port, ID2SYM( rb_intern( "external" ) ), Qfalse ); + } if ( port_status->status == TD_PORT_UP ) { rb_hash_aset( port, ID2SYM( rb_intern( "up" ) ), Qtrue ); @@ -106,20 +141,18 @@ port_status_to_hash( const topology_port_status* port_status ) { return port; } + static void handle_port_status_updated( void* self, const topology_port_status* port_status ) { - rb_iv_set( ( VALUE ) self, "@cache_up_to_date", Qfalse); + rb_iv_set( ( VALUE ) self, "@map_up_to_date", Qfalse); - if ( rb_respond_to( ( VALUE ) self, rb_intern( "port_status_updated" ) ) == Qtrue ) { - VALUE port = port_status_to_hash( port_status ); - rb_funcall( ( VALUE ) self, rb_intern( "port_status_updated" ), 1, port ); - } if ( rb_respond_to( ( VALUE ) self, rb_intern( "_port_status_updated" ) ) == Qtrue ) { VALUE port = port_status_to_hash( port_status ); rb_funcall( ( VALUE ) self, rb_intern( "_port_status_updated" ), 1, port ); } } + static VALUE link_status_to_hash( const topology_link_status* link_status ) { VALUE link = rb_hash_new(); @@ -127,14 +160,14 @@ link_status_to_hash( const topology_link_status* link_status ) { rb_hash_aset( link, ID2SYM( rb_intern( "from_portno" ) ), INT2FIX( (int)link_status->from_portno ) ); rb_hash_aset( link, ID2SYM( rb_intern( "to_dpid" ) ), ULL2NUM( link_status->to_dpid ) ); rb_hash_aset( link, ID2SYM( rb_intern( "to_portno" ) ), INT2FIX( (int)link_status->to_portno ) ); - rb_hash_aset( link, ID2SYM( rb_intern( "status" ) ), INT2FIX( (int)link_status->status ) ); +// rb_hash_aset( link, ID2SYM( rb_intern( "status" ) ), INT2FIX( (int)link_status->status ) ); - if( link_status->status != TD_LINK_DOWN ) { + if ( link_status->status != TD_LINK_DOWN ) { rb_hash_aset( link, ID2SYM( rb_intern( "up" ) ), Qtrue ); } else { rb_hash_aset( link, ID2SYM( rb_intern( "up" ) ), Qfalse ); } - if( link_status->status == TD_LINK_UNSTABLE ) { + if ( link_status->status == TD_LINK_UNSTABLE ) { rb_hash_aset( link, ID2SYM( rb_intern( "unstable" ) ), Qtrue ); } else { rb_hash_aset( link, ID2SYM( rb_intern( "unstable" ) ), Qfalse ); @@ -142,14 +175,11 @@ link_status_to_hash( const topology_link_status* link_status ) { return link; } + static void handle_link_status_updated( void* self, const topology_link_status* link_status ) { - rb_iv_set( ( VALUE ) self, "@cache_up_to_date", Qfalse); + rb_iv_set( ( VALUE ) self, "@map_up_to_date", Qfalse); - if ( rb_respond_to( ( VALUE ) self, rb_intern( "link_status_updated" ) ) == Qtrue ) { - VALUE link = link_status_to_hash( link_status ); - rb_funcall( ( VALUE ) self, rb_intern( "link_status_updated" ), 1, link ); - } if ( rb_respond_to( ( VALUE ) self, rb_intern( "_link_status_updated" ) ) == Qtrue ) { VALUE link = link_status_to_hash( link_status ); rb_funcall( ( VALUE ) self, rb_intern( "_link_status_updated" ), 1, link ); @@ -162,117 +192,148 @@ handle_subscribed_reply( void* self, topology_response *res ) { switch ( res->status ) { case TD_RESPONSE_OK: case TD_RESPONSE_ALREADY_SUBSCRIBED: - if( res->status == TD_RESPONSE_ALREADY_SUBSCRIBED ){ + if ( res->status == TD_RESPONSE_ALREADY_SUBSCRIBED ) { warn( "Already subscribed to topology service." ); } - rb_iv_set( (VALUE)self, "@is_topology_ready", Qtrue ); + rb_iv_set( (VALUE)self, "@is_subscribed", Qtrue ); - if ( rb_respond_to( ( VALUE ) self, rb_intern( "topology_ready" ) ) == Qtrue ) { - rb_funcall( ( VALUE ) self, rb_intern( "topology_ready" ), 0 ); + if ( rb_respond_to( ( VALUE ) self, rb_intern( "subscribe_topology_reply" ) ) == Qtrue ) { + rb_funcall( ( VALUE ) self, rb_intern( "subscribe_topology_reply" ), 0 ); } break; default: - warn( "%s: Abnormal subscription reply: %#x", __func__, (unsigned int)res->status ); + warn( "%s: Abnormal subscribed reply: %#x", __func__, (unsigned int)res->status ); } } static void -handle_unsubscribed_reply( void* self, topology_response *res ) { - if( res->status == TD_RESPONSE_NO_SUCH_SUBSCRIBER ) { +handle_unsubscribed_reply( void* tcb, topology_response *res ) { + topology_callback* cb = tcb; + VALUE self = cb->self; + VALUE block = cb->block; + + if ( res->status == TD_RESPONSE_NO_SUCH_SUBSCRIBER ) { warn( "Already unsubscribed from topology Service." ); - }else{ - warn( "%s: Abnormal unsubscription reply: %#x", __func__, (unsigned int)res->status ); + } else if ( res->status != TD_RESPONSE_OK ) { + warn( "%s: Abnormal unsubscribed reply: %#x", __func__, (unsigned int)res->status ); } - rb_iv_set( (VALUE)self, "@is_topology_ready", Qfalse ); + rb_iv_set( self, "@is_subscribed", Qfalse ); + + if ( block != Qnil ){ + rb_funcall( block, rb_intern( "call" ), 0 ); + } else if ( rb_respond_to( ( VALUE ) self, rb_intern( "unsubscribe_topology_reply" ) ) == Qtrue ) { + rb_funcall( ( VALUE ) self, rb_intern( "unsubscribe_topology_reply" ), 0 ); + } + xfree( cb ); } /** - * subscribe_topology() - * Subscribe to topology. - * - * This method is intended to be called inside Controller#start. - * + * @!group Topology update event subscription control + * Manually subscribe to topology to get topology update events. + * @note This will be automatically called by #start if a Topology update event handler is defined. + * @return [Boolean] true if request sent successfully. */ static VALUE topology_subscribe_topology( VALUE self ) { + maybe_init_libtopology( self ); add_callback_switch_status_updated( handle_switch_status_updated, ( void * ) self ); add_callback_port_status_updated( handle_port_status_updated, ( void * ) self ); add_callback_link_status_updated( handle_link_status_updated, ( void * ) self ); - subscribe_topology( handle_subscribed_reply, ( void * ) self ); - return self; + bool succ = subscribe_topology( handle_subscribed_reply, ( void * ) self ); + return ( succ )? Qtrue : Qfalse; } + /** - * unsubscribe_topology() - * Unsubscribe from topology. + * @!group Topology update event subscription control + * Unsubscribe from topology. * - * This method is intended to be called inside Controller#shutdown!. + * @return [Boolean] true if request sent successfully. * */ static VALUE topology_unsubscribe_topology( VALUE self ) { - unsubscribe_topology( handle_unsubscribed_reply, ( void * ) self ); + maybe_init_libtopology( self ); + + topology_callback* cb = xcalloc( 1, sizeof(topology_callback) ); + cb->self = self; + if ( rb_block_given_p() == Qtrue ) { + cb->block = rb_block_proc(); + } else { + cb->block = Qnil; + } + bool succ = unsubscribe_topology( handle_unsubscribed_reply, ( void * ) cb ); add_callback_switch_status_updated( NULL, NULL ); add_callback_port_status_updated( NULL, NULL ); add_callback_link_status_updated( NULL, NULL ); - return self; + return ( succ )? Qtrue : Qfalse; } + static void handle_enable_topology_discovery_reply( void* self, const topology_response *res ) { - if( res->status != TD_RESPONSE_OK ){ + if ( res->status != TD_RESPONSE_OK ) { warn( "%s: Abnormal reply: %#x", __func__, (unsigned int)res->status ); return; } - if ( rb_respond_to( ( VALUE ) self, rb_intern( "topology_discovery_ready" ) ) == Qtrue ) { - rb_funcall( ( VALUE ) self, rb_intern( "topology_discovery_ready" ), 0 ); + if ( rb_respond_to( ( VALUE ) self, rb_intern( "enable_topology_discovery_reply" ) ) == Qtrue ) { + rb_funcall( ( VALUE ) self, rb_intern( "enable_topology_discovery_reply" ), 0 ); } } + /** - * @group Discovery control + * @!group Discovery control * * Enable topology discovery. * + * @return [Boolean] true if request sent successfully. + * */ static VALUE topology_enable_topology_discovery( VALUE self ) { - enable_topology_discovery( handle_enable_topology_discovery_reply, (void*) self ); - return self; + maybe_init_libtopology( self ); + + bool succ = enable_topology_discovery( handle_enable_topology_discovery_reply, (void*) self ); + return ( succ )? Qtrue : Qfalse; } + static void handle_disable_topology_discovery_reply( void* self, const topology_response *res ) { UNUSED( self ); - if( res->status != TD_RESPONSE_OK ){ + if ( res->status != TD_RESPONSE_OK ) { warn( "%s: Abnormal reply: %#x", __func__, (unsigned int)res->status ); return; } + if ( rb_respond_to( ( VALUE ) self, rb_intern( "disable_topology_discovery_reply" ) ) == Qtrue ) { + rb_funcall( ( VALUE ) self, rb_intern( "disable_topology_discovery_reply" ), 0 ); + } } + /** - * @group Discovery control + * @!group Discovery control * * Disable topology discovery. * + * @return [Boolean] true if request sent successfully. + * */ static VALUE topology_disable_topology_discovery( VALUE self ) { - disable_topology_discovery( handle_disable_topology_discovery_reply, (void*) self ); - return self; -} + maybe_init_libtopology( self ); + bool succ = disable_topology_discovery( handle_disable_topology_discovery_reply, (void*) self ); + return ( succ )? Qtrue : Qfalse; +} -typedef struct topology_callback { - VALUE self; - VALUE block; -} topology_callback; static void handle_get_all_link_status_callback( void *tcb, size_t number, const topology_link_status *link_status ) { @@ -280,27 +341,27 @@ handle_get_all_link_status_callback( void *tcb, size_t number, const topology_li VALUE self = cb->self; VALUE block = cb->block; - if ( block != Qnil ){ + if ( block != Qnil ) { VALUE links = rb_ary_new2( (long)number ); for( size_t i = 0 ; i < number ; ++i ){ VALUE link = link_status_to_hash( &link_status[i] ); rb_ary_push( links, link ); } rb_funcall( block, rb_intern( "call" ), 1, links ); - } else if ( rb_respond_to( ( VALUE ) self, rb_intern( "all_link_status_reply" ) ) == Qtrue ) { + } else if ( rb_respond_to( ( VALUE ) self, rb_intern( "all_link_status" ) ) == Qtrue ) { VALUE links = rb_ary_new2( (long)number ); for( size_t i = 0 ; i < number ; ++i ){ VALUE link = link_status_to_hash( &link_status[i] ); rb_ary_push( links, link ); } - rb_funcall( ( VALUE ) self, rb_intern( "all_link_status_reply" ), 1, links ); + rb_funcall( ( VALUE ) self, rb_intern( "all_link_status" ), 1, links ); } xfree( cb ); } /** - * @group Get all status requests + * @!group Get all status request/reply * * Request Topology service to send all link status it holds. * Results will be returned as callback to Block given, @@ -308,20 +369,24 @@ handle_get_all_link_status_callback( void *tcb, size_t number, const topology_li * * @yieldparam switches [Array] Array of Hash including current status. * + * @return [Boolean] true if request sent successfully. + * * @see #link_status_updated Each Hash instance included in the array is equivalent to link_status_updated argument Hash. - * @see #all_link_status_reply + * @see #all_link_status */ static VALUE topology_send_all_link_status( VALUE self ) { + maybe_init_libtopology( self ); + topology_callback* cb = xcalloc( 1, sizeof(topology_callback) ); cb->self = self; - if( rb_block_given_p() == Qtrue ) { + if ( rb_block_given_p() == Qtrue ) { cb->block = rb_block_proc(); } else { cb->block = Qnil; } - get_all_link_status( handle_get_all_link_status_callback, (void*) cb ); - return self; + bool succ = get_all_link_status( handle_get_all_link_status_callback, (void*) cb ); + return ( succ )? Qtrue : Qfalse; } @@ -331,27 +396,27 @@ handle_get_all_port_status_callback( void *tcb, size_t number, const topology_po VALUE self = cb->self; VALUE block = cb->block; - if ( block != Qnil ){ + if ( block != Qnil ) { VALUE ports = rb_ary_new2( (long)number ); - for( size_t i = 0 ; i < number ; ++i ){ + for( size_t i = 0 ; i < number ; ++i ) { VALUE port = port_status_to_hash( &port_status[i] ); rb_ary_push( ports, port ); } rb_funcall( block, rb_intern( "call" ), 1, ports ); - } else if ( rb_respond_to( ( VALUE ) self, rb_intern( "all_port_status_reply" ) ) == Qtrue ) { + } else if ( rb_respond_to( ( VALUE ) self, rb_intern( "all_port_status" ) ) == Qtrue ) { VALUE ports = rb_ary_new2( (long)number ); for( size_t i = 0 ; i < number ; ++i ){ VALUE port = port_status_to_hash( &port_status[i] ); rb_ary_push( ports, port ); } - rb_funcall( ( VALUE ) self, rb_intern( "all_port_status_reply" ), 1, ports ); + rb_funcall( ( VALUE ) self, rb_intern( "all_port_status" ), 1, ports ); } xfree( cb ); } /** - * @group Get all status requests + * @!group Get all status request/reply * * Request Topology service to send all port status it holds. * Results will be returned as callback to Block given, @@ -359,19 +424,24 @@ handle_get_all_port_status_callback( void *tcb, size_t number, const topology_po * * @yieldparam ports [Array] Array of Hash including current status. * + * @return [Boolean] true if request sent successfully. + * * @see #port_status_updated Each Hash instance included in the array is equivalent to port_status_updated argument Hash. - * @see #all_port_status_reply */ + * @see #all_port_status + */ static VALUE topology_send_all_port_status( VALUE self ) { + maybe_init_libtopology( self ); + topology_callback* cb = xcalloc( 1, sizeof(topology_callback) ); cb->self = self; - if( rb_block_given_p() == Qtrue ) { + if ( rb_block_given_p() == Qtrue ) { cb->block = rb_block_proc(); } else { cb->block = Qnil; } - get_all_port_status( handle_get_all_port_status_callback, (void*) cb ); - return self; + bool succ = get_all_port_status( handle_get_all_port_status_callback, (void*) cb ); + return ( succ )? Qtrue : Qfalse; } @@ -381,27 +451,27 @@ handle_get_all_switch_status_callback( void *tcb, size_t number, const topology_ VALUE self = cb->self; VALUE block = cb->block; - if ( block != Qnil ){ + if ( block != Qnil ) { VALUE switches = rb_ary_new2( (long)number ); for( size_t i = 0 ; i < number ; ++i ){ VALUE sw = switch_status_to_hash( &sw_status[i] ); rb_ary_push( switches, sw ); } rb_funcall( block, rb_intern( "call" ), 1, switches ); - } else if ( rb_respond_to( ( VALUE ) self, rb_intern( "all_switch_status_reply" ) ) == Qtrue ) { + } else if ( rb_respond_to( ( VALUE ) self, rb_intern( "all_switch_status" ) ) == Qtrue ) { VALUE switches = rb_ary_new2( (long)number ); for( size_t i = 0 ; i < number ; ++i ){ VALUE sw = switch_status_to_hash( &sw_status[i] ); rb_ary_push( switches, sw ); } - rb_funcall( ( VALUE ) self, rb_intern( "all_switch_status_reply" ), 1, switches ); + rb_funcall( ( VALUE ) self, rb_intern( "all_switch_status" ), 1, switches ); } xfree( cb ); } /** - * @group Get all status requests + * @!group Get all status request/reply * * @!method send_all_switch_status_request { |switches| ... } | nil * Request Topology service to send all switch status it holds. @@ -410,56 +480,69 @@ handle_get_all_switch_status_callback( void *tcb, size_t number, const topology_ * * @yieldparam switches [Array] Array of Hash including current status. * + * @return [Boolean] true if request sent successfully. + * * @see #switch_status_updated Each Hash instance included in the array is equivalent to switch_status_updated argument Hash. - * @see #all_switch_status_reply + * @see #all_switch_status */ static VALUE topology_send_all_switch_status( VALUE self ) { + maybe_init_libtopology( self ); + topology_callback* cb = xcalloc( 1, sizeof(topology_callback) ); cb->self = self; - if( rb_block_given_p() == Qtrue ) { + if ( rb_block_given_p() == Qtrue ) { cb->block = rb_block_proc(); } else { cb->block = Qnil; } - get_all_switch_status( handle_get_all_switch_status_callback, (void*) cb ); - return self; + bool succ = get_all_switch_status( handle_get_all_switch_status_callback, (void*) cb ); + return ( succ )? Qtrue : Qfalse; } void Init_topology( void ) { mTopology = rb_define_module_under( mTrema, "Topology" ); - // @!group enum topology_switch_status_type - rb_define_const( mTopology, "TD_SWITCH_DOWN", TD_SWITCH_DOWN ); - rb_define_const( mTopology, "TD_SWITCH_UP", TD_SWITCH_UP ); - - // @!group enum topology_port_status_type - rb_define_const( mTopology, "TD_PORT_DOWN", TD_PORT_DOWN ); - rb_define_const( mTopology, "TD_PORT_UP", TD_PORT_UP ); - // @!group enum topology_port_external_type - rb_define_const( mTopology, "TD_PORT_INACTIVE", TD_PORT_INACTIVE ); - rb_define_const( mTopology, "TD_PORT_EXTERNAL", TD_PORT_EXTERNAL ); - - // @!group enum topology_link_status_type - rb_define_const( mTopology, "TD_LINK_DOWN", TD_LINK_DOWN ); - rb_define_const( mTopology, "TD_LINK_UP", TD_LINK_UP ); - rb_define_const( mTopology, "TD_LINK_UNSTABLE", TD_LINK_UNSTABLE ); - - // @!endgroup + /* @!group enum topology_switch_status_type */ + /* Same as C API enum topology_switch_status_type */ + rb_define_const( mTopology, "TD_SWITCH_DOWN", INT2NUM( TD_SWITCH_DOWN ) ); + /* Same as C API enum topology_switch_status_type */ + rb_define_const( mTopology, "TD_SWITCH_UP", INT2NUM( TD_SWITCH_UP ) ); + + /* @!group enum topology_port_status_type */ + /* Same as C API enum topology_port_status_type */ + rb_define_const( mTopology, "TD_PORT_DOWN", INT2NUM( TD_PORT_DOWN ) ); + /* Same as C API enum topology_port_status_type */ + rb_define_const( mTopology, "TD_PORT_UP", INT2NUM( TD_PORT_UP ) ); + + /* @!group enum topology_port_external_type */ + /* Same as C API enum topology_port_external_type */ + rb_define_const( mTopology, "TD_PORT_INACTIVE", INT2NUM( TD_PORT_INACTIVE ) ); + /* Same as C API enum topology_port_external_type */ + rb_define_const( mTopology, "TD_PORT_EXTERNAL", INT2NUM( TD_PORT_EXTERNAL ) ); + + /* @!group enum topology_link_status_type */ + /* Same as C API enum topology_link_status_type */ + rb_define_const( mTopology, "TD_LINK_DOWN", INT2NUM( TD_LINK_DOWN ) ); + /* Same as C API enum topology_link_status_type */ + rb_define_const( mTopology, "TD_LINK_UP", INT2NUM( TD_LINK_UP ) ); + /* Same as C API enum topology_link_status_type */ + rb_define_const( mTopology, "TD_LINK_UNSTABLE", INT2NUM( TD_LINK_UNSTABLE ) ); + /* @!endgroup */ rb_define_protected_method( mTopology, "init_libtopology", topology_init_libtopology, 1 ); rb_define_protected_method( mTopology, "finalize_libtopology", topology_finalize_libtopology, 0 ); - rb_define_protected_method( mTopology, "subscribe_topology", topology_subscribe_topology, 0 ); - rb_define_protected_method( mTopology, "unsubscribe_topology", topology_unsubscribe_topology, 0 ); + rb_define_method( mTopology, "send_subscribe_topology", topology_subscribe_topology, 0 ); + rb_define_method( mTopology, "send_unsubscribe_topology", topology_unsubscribe_topology, 0 ); - rb_define_method( mTopology, "enable_topology_discovery", topology_enable_topology_discovery, 0 ); - rb_define_method( mTopology, "disable_topology_discovery", topology_disable_topology_discovery, 0 ); + rb_define_method( mTopology, "send_enable_topology_discovery", topology_enable_topology_discovery, 0 ); + rb_define_method( mTopology, "send_disable_topology_discovery", topology_disable_topology_discovery, 0 ); - rb_define_method( mTopology, "send_all_link_status_request", topology_send_all_link_status, 0 ); - rb_define_method( mTopology, "send_all_port_status_request", topology_send_all_port_status, 0 ); - rb_define_method( mTopology, "send_all_switch_status_request", topology_send_all_switch_status, 0 ); + rb_define_method( mTopology, "get_all_link_status", topology_send_all_link_status, 0 ); + rb_define_method( mTopology, "get_all_port_status", topology_send_all_port_status, 0 ); + rb_define_method( mTopology, "get_all_switch_status", topology_send_all_switch_status, 0 ); rb_require( "trema/topology" ); } diff --git a/ruby/trema/topology.rb b/ruby/trema/topology.rb index dc59615f6..7304ac34b 100644 --- a/ruby/trema/topology.rb +++ b/ruby/trema/topology.rb @@ -15,12 +15,12 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -require 'trema/topology/port' require 'trema/topology/link' +require 'trema/topology/port' require 'trema/topology/switch' module Trema - + # A module to add topology information notification capability to Controller module Topology # @@ -30,53 +30,107 @@ def self.handler name # Do nothing. end - # @group Basic Topology events and methods + # @!group Topology update event subscription control # - # @!method topology_ready? - # @return true if Topology service is ready to use. + # @return true if subscribed to topology update notification. # - def topology_ready? - @is_topology_ready + def subscribed? + return @is_subscribed end - - + + # - # @!method topology_ready( ) + # @!method subscribe_topology_reply( ) # - # @abstract topology_ready event handler. Override this to implement a custom handler. - # Start using Topology service related methods after this event. + # @abstract send_subscribe_topology reply event handler. Override this to implement a custom handler. # - handler :topology_ready - + # Will be called as a reply to send_subscribe_topology, if defined. + # @return [void] # - # @!method topology_discovery_ready( ) + handler :subscribe_topology_reply + + + # + # @!method unsubscribe_topology_reply( ) # - # @abstract topology_discovery_ready event handler. Override this to implement a custom handler. + # @abstract send_unsubscribe_topology reply event handler. Override this to implement a custom handler. # - # Start using Topology Discovery service related methods after this event. + # Will be called as a reply to send_unsubscribe_topology, if defined. + # @return [void] # - handler :topology_discovery_ready + handler :unsubscribe_topology_reply - # @group Topology update event handlers + # @!group Discovery control + + # + # @!method enable_topology_discovery_reply( ) + # + # @abstract send_enable_topology_discovery reply event handler. Override this to implement a custom handler. # + # Will be called as a reply to send_enable_topology_discovery, if defined. + # @return [void] + # + handler :enable_topology_discovery_reply + + + # + # @!method disable_topology_discovery_reply( ) + # + # @abstract send_disable_topology_discovery reply event handler. Override this to implement a custom handler. + # + # Will be called as a reply to send_disable_topology_discovery, if defined. + # @return [void] + # + handler :disable_topology_discovery_reply + + + # @!group Topology update event handlers + + # + # # @!method switch_status_updated( sw_stat ) + # Switch status update event handler. + # @abstract + # @note In general user should override #switch_status_up and #switch_status_down, + # unless user have common operation for both events. # - # @abstract switch_status_updated event handler. Override this to implement a custom handler. + # @note sw_stat contains info about switch attribute only. It will not contain info about it's ports. # # @param [Hash] sw_stat # Hash containing info about updated switch. # @option sw_stat [Integer] :dpid dpid of the switch - # @option sw_stat [Integer] :status status of the switch. Refer to enum topology_switch_status_type in topology_service_interface.h # @option sw_stat [Boolean] :up true if status is TD_SWITCH_UP + # @return [void] # handler :switch_status_updated + + + # + # @overload switch_status_up( dpid ) + # Switch status up event handler. + # @abstract + # @param [Integer] dpid datapath_id of the switch. + # @return [void] + # + handler :switch_status_up + + + # + # @overload switch_status_down( dpid ) + # Switch status down event handler. + # @abstract + # @param [Integer] dpid datapath_id of the switch. + # @return [void] + # + handler :switch_status_down # # @!method port_status_updated( port_stat ) # # @abstract port_status_updated event handler. Override this to implement a custom handler. + # Port status update event handler. # # @param [Hash] port_stat # Hash containing info about updated port. @@ -85,8 +139,8 @@ def topology_ready? # @option port_stat [String] :name name of the port # @option port_stat [String] :mac mac address # @option port_stat [Integer] :external external flag of the port. Refer to enum topology_port_external_type in topology_service_interface.h - # @option port_stat [Integer] :status status of the port. Refer to enum topology_port_status_type in topology_service_interface.h # @option port_stat [Boolean] :up true if status is TD_PORT_UP + # @return [void] # handler :port_status_updated @@ -94,6 +148,7 @@ def topology_ready? # @!method link_status_updated( link_stat ) # # @abstract link_status_updated event handler. Override this to implement a custom handler. + # Link status update event handler. # # @param [Hash] link_stat # Hash containing info about updated link. @@ -101,67 +156,70 @@ def topology_ready? # @option link_stat [Integer] :from_portno port number of the switch which the link departs # @option link_stat [Integer] :to_dpid dpid of the switch which the link arraives # @option link_stat [Integer] :to_portno port number of the switch which the link arrives - # @option link_stat [Integer] :status status of the link. Refer to enum topology_link_status_type in topology_service_interface.h # @option link_stat [Boolean] :up true if status is *NOT* TD_LINK_DOWN, false otherwise. # @option link_stat [Boolean] :unstable true if status is TD_LINK_UNSTABLE, false otherwise. + # @return [void] # handler :link_status_updated - - # @group Get all status event handlers + + + # @!group Get all status request/reply # - # @!method all_switch_status_reply( sw_stats ) - # Event handler used for send_all_switch_status_request reply event, - # if handler block was omitted on send_all_port_status_request call. - # @abstract get_all_switch_status callback handler. Override this to implement a custom handler. + # @!method all_switch_status( sw_stats ) + # get_all_switch_status reply event handler, + # when handler block was omitted on get_all_switch_status call. + # @abstract # # @param [Array] sw_stats # Array of Hash containing info about updated switch. # @see #switch_status_updated Each Hash instance included in the array is equivalent to #switch_status_updated argument Hash. + # @return [void] # - handler :all_switch_status_reply + handler :all_switch_status # - # @!method all_port_status_reply( port_stats ) - # Event handler used for send_all_port_status_request reply event, - # if handler block was omitted on send_all_port_status_request call. - # @abstract get_all_port_status callback handler. Override this to implement a custom handler. + # @!method all_port_status( port_stats ) + # get_all_port_status reply event handler, + # when handler block was omitted on get_all_port_status call. + # @abstract # # @param [Array] port_stats # Array of Hash containing info about updated port. # @see #port_status_updated Each Hash instance included in the array is equivalent to port_status_updated argument Hash. + # @return [void] # - handler :all_port_status_reply + handler :all_port_status # - # @!method all_link_status_reply( link_stat ) - # Event handler used for send_all_link_status_request reply event, - # if handler block was omitted on send_all_link_status_request call. - # @abstract get_all_link_status call handler. Override this to implement a custom handler. + # @!method all_link_status( link_stat ) + # get_all_link_status reply event handler, + # when handler block was omitted on get_all_link_status call. + # @abstract # # @param [Array] link_stat # Array of Hash containing info about updated link. # @see #link_status_updated Each Hash instance included in the array is equivalent to link_status_updated argument Hash. + # @return [void] # - handler :all_link_status_reply + handler :all_link_status - # @endgroup + # @!endgroup # # @!method start - # Initialize and subscribe to topology interface. - # Place to implement initialization before start_trema() call. + # Initialize topology as controller start. + # This method will automatically subscribe to topology if topology handler was defined. # # This method will be implicitly called inside Controller#run! between init_trema() and start_trema() calls if not overridden by user. - # @note Be sure to initialize and subscribe to topology if overriding this method. + # @note Be sure to initialize and subscribe topology if overriding this method. # # @example - # class MyController < Controller + # class YourController < Controller # include Topology # def start # init_libtopology "topology" - # subscribe_topology - # # Simply calling super() instead of above may be sufficient + # send_subscribe_topology if topology_handler_implemented? # # # your application's pre-start_trema() call initialization here. # end @@ -170,18 +228,49 @@ def topology_ready? def start # specify the name of topology service name init_libtopology "topology" - subscribe_topology + send_subscribe_topology if topology_handler_implemented? + send_enable_topology_discovery if respond_to?( :link_status_updated ) end - + + # # @!method shutdown! - # Shutdown controller. - # Unsubscribe and finalize topology before stopping trema. + # Finalize topology before stopping trema. # def shutdown! - unsubscribe_topology finalize_libtopology super() end + + ####################### + protected + ####################### + + # + # @return true if there exist a definition for topology update event handler + # + def topology_handler_implemented? + return respond_to?( :switch_status_up ) | respond_to?( :switch_status_down ) | + respond_to?( :switch_status_updated ) | + respond_to?( :port_status_updated ) | + respond_to?( :link_status_updated ) + end + + def _switch_status_updated sw + info "debug _switch_status_updated " + switch_status_up sw[:dpid] if respond_to?( :switch_status_up ) and sw[:up] + switch_status_updated sw if respond_to?( :switch_status_updated ) + switch_status_down sw[:dpid] if respond_to?( :switch_status_down ) and not sw[:up] + end + + + def _port_status_updated port + port_status_updated port if respond_to?( :port_status_updated ) + end + + + def _link_status_updated link + link_status_updated link if respond_to?( :link_status_updated ) + end end end diff --git a/ruby/trema/topology/guarded-property-accessor.rb b/ruby/trema/topology/guarded-property-accessor.rb new file mode 100644 index 000000000..68229bcb2 --- /dev/null +++ b/ruby/trema/topology/guarded-property-accessor.rb @@ -0,0 +1,61 @@ +# +# Copyright (C) 2008-2013 NEC Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + + +module Trema + module Topology + module GuardedPropertyAccesor + + # Accessor for the property value of this node + def []( key ) + return @property[key] + end + + + # Overwrite a property value of this node + # @raise [ArgumentError] if key was a mandatory key of a node + def []=( key, value ) + raise ArgumentError, "Overwriting #{key} is not allowed" if self.class.is_mandatory_key?(key) + return @property[key] = value + end + + + # Delete a property of this node. + # mandatory key will be treated as if it didn't exist. + def delete( key ) + if block_given? then + yield key if not @property.has_key?(key) or self.class.is_mandatory_key?(key) + end + return nil if self.class.is_mandatory_key?(key) + return deleted = @property.delete(key) + end + + + # Update properties of this node using specified Hash or node + # mandatory keys will be ignored + def update( other ) + other = other.properties if other.is_a?(self.class) + other = other.to_hash if not other.is_a?(Hash) + + other.each_pair do |key,value| + @property[key] = value if not self.class.is_mandatory_key?(key) + end + end + alias :merge! :update + end + end +end \ No newline at end of file diff --git a/ruby/trema/topology/link.rb b/ruby/trema/topology/link.rb index 9afd23ce6..d228e3dd5 100644 --- a/ruby/trema/topology/link.rb +++ b/ruby/trema/topology/link.rb @@ -18,41 +18,55 @@ module Trema module Topology + # Link's Hash key 4-tuple's array index + FROM_DPID = 0 + # Link's Hash key 4-tuple's array index + FROM_PORTNO = 1 + # Link's Hash key 4-tuple's array index + TO_DPID = 2 + # Link's Hash key 4-tuple's array index + TO_PORTNO = 3 + # A class to represent a link in a Topology - class Link < Hash + class Link + + # @!attribute [r] from_dpid # @return [Integer] datapath ID of the switch which this link departs from def from_dpid - return self[:from_dpid] + return @property[:from_dpid] end + # @!attribute [r] from_portno # @return [Integer] port number which this link departs from def from_portno - return self[:from_portno] + return @property[:from_portno] end + # @!attribute [r] to_dpid # @return [Integer] datapath ID of the switch which this link arrive to def to_dpid - return self[:to_dpid] + return @property[:to_dpid] end + # @!attribute [r] to_portno # @return [Integer] port number which this link departs arrive to def to_portno - return self[:to_portno] + return @property[:to_portno] end # @return [Boolean] returns true if link is up def up? - return self[:up] + return @property[:up] end # @return [Boolean] returns true if link is unstable def unstable? - return self[:unstable] + return @property[:unstable] end @@ -62,12 +76,6 @@ def key end - # @return [String] Link key as a String - def key_str - return "L#{ from_dpid.to_s(16) }-#{ from_portno.to_s }-#{ to_dpid.to_s(16) }-#{ to_portno.to_s }" - end - - # Link constructor. # @param [Hash] link Hash containing Link properties. Must at least contain keys listed in Options. # @option link [Integer] :from_dpid Switch dpid which this link departs from @@ -76,21 +84,18 @@ def key_str # @option link [Integer] :to_portno port number of switch which this link peer to # @return [Link] # @example - # link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] - def Link.[]( link ) - raise ArgumentError, "Key element for Link missing in Hash" if not Link.has_keys?(link) - - link[:from_dpid].freeze - link[:from_portno].freeze - link[:to_dpid].freeze - link[:to_portno].freeze - super( link ) + # link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) + def initialize( link ) + raise ArgumentError, "Mandatory Key element for Link missing in Hash" if not Link.has_mandatory_keys?( link ) + # TODO type check mandatory key? + # TODO copy Hash? + @property = link end - # @param key Hash key element + # @param [Symbol] key Hash key element # @return [Boolean] true if key is key element for Link - def Link.is_key?( key ) + def Link.is_mandatory_key?( key ) case key when :from_dpid, :from_portno, :to_dpid, :to_portno return true @@ -101,31 +106,26 @@ def Link.is_key?( key ) # Test if Hash has required key as a Link instance - # @param hash Hash to test + # @param [Hash] hash Hash to test # @return [Boolean] true if hash has all required keys. - def Link.has_keys?( hash ) + def Link.has_mandatory_keys?( hash ) return !(hash.values_at(:from_dpid, :from_portno, :to_dpid, :to_portno).include? nil) end - # @private - def initialize( *arg ) - raise ArgumentError, "Empty Link cannot be created. Use Link[ {...} ] form." - end - - + # @return [String] human readable string representation. def to_s "Link: (0x#{ from_dpid.to_s(16) }:#{ from_portno.to_s })->(0x#{ to_dpid.to_s(16) }:#{ to_portno.to_s }) - {#{property_to_s}}" end ############################################################################ - private + protected ############################################################################ def property_to_s - kvp_ary = self.select { |key,_| not Link.is_key?(key) } + kvp_ary = @property.select { |key,_| not Link.is_mandatory_key?( key ) } kvp_ary = kvp_ary.sort_by { |key,_| key.to_s } s = kvp_ary.map { |key, val| "#{key.to_s}:#{val.inspect}" }.join(", ") return s diff --git a/ruby/trema/topology/cache.rb b/ruby/trema/topology/map.rb similarity index 59% rename from ruby/trema/topology/cache.rb rename to ruby/trema/topology/map.rb index b966c6d6d..ea731d659 100644 --- a/ruby/trema/topology/cache.rb +++ b/ruby/trema/topology/map.rb @@ -18,71 +18,66 @@ module Trema module Topology - # Topology Cache structure - class Cache - # Hash of Switches: dpid => Switch + # Topology Map structure + class Map + # @return [{Integer=>Switch}] Hash of Switches: dpid => Switch # @note Do not directly add/remove elements in this Hash. attr_reader :switches - # Hash of Links: [from.dpid, from.port_no, to.dpid, to.port_no] => Links + # @return [{[Integer,Integer,Integer,Integer]=>Link}] Hash of Links: [from.dpid, from.port_no, to.dpid, to.port_no] => Links # @note Do not directly add/remove elements in this Hash. attr_reader :links - # Create empty Cache + # Create empty Map def initialize @switches = Hash.new - @links = Hash.new; + @links = Hash.new end # @!group Switch manipulation methods - # Add a switch to topology cache. + # Add a switch to topology map. # @return [Switch] Switch instance added. def add_switch sw raise TypeError, "Trema::Topology::Switch expected" if not sw.is_a?(Switch) - @switches[ sw.dpid ] = sw; + @switches[ sw.dpid ] = sw end - # Delete a switch from Topology cache. + # Delete a switch from Topology map. # @note Links from/to the switch will also be removed + # @param [Switch,Hash] sw Switch instance or a Hash with Switch info def del_switch sw - del_switch_by_dpid sw.dpid + del_switch_by_dpid sw[:dpid] end - # Delete a switch from Topology cache using dpid + # Delete a switch from Topology map using dpid # @see #del_switch def del_switch_by_dpid dpid remove_links = @links.select { |key,_| (key[FROM_DPID] == dpid || key[TO_DPID] == dpid) } - remove_links.each { |kv_pair| self.del_link_by_key_tuple( kv_pair[0] ) } + remove_links.each { |kv_pair| self.del_link( kv_pair.first ) } @switches.delete dpid end - # Lookup a switch from Topology cache using dpid - # @param [Integer] dpid dpid of the switch to look for. - # @return [Switch,nil] Switch instance found, or nil if not found. - def lookup_switch_by_dpid dpid - @switches[dpid] - end - - - # Get a switch from Topology cache using dpid. + # Get a switch from Topology map using dpid. # Switch instance will be created if not found. # @param [Integer] dpid dpid of the switch to look for. # @return [Switch] Switch instance for specified dpid def get_switch_for_dpid dpid sw = lookup_switch_by_dpid dpid - sw ||= add_switch Switch[ { :dpid => dpid } ] + sw ||= add_switch Switch.new( { :dpid => dpid } ) return sw end # @!group Link manipulation methods - # Add a link to Topology cache. + # Add a link to Topology map. # @note Corresponding Switch object's links_out, links_in will also be updated. def add_link link + raise TypeError, "Trema::Topology::Link expected" if not link.is_a?(Link) + key = link.key key.each { |each| each.freeze } key.freeze @@ -90,55 +85,65 @@ def add_link link sw_from = get_switch_for_dpid link.from_dpid sw_to = get_switch_for_dpid link.to_dpid - sw_from.add_outbound_link link - sw_to.add_inbound_link link + sw_from.add_link link + sw_to.add_link link @links[ key ] = link end - # Delete a link from Topology cache. + # Delete a link from Topology map. # @note Corresponding Switch object's links_out, links_in will also be updated. + # @param [Link,Hash] link Link instance or a Hash with link info. def del_link link - del_link_by_key_tuple [link.from_dpid, link.from_portno, link.to_dpid, link.to_portno ] + del_link_by_key [ link[:from_dpid], link[:from_portno], link[:to_dpid], link[:to_portno] ] end - # Delete a link from Topology cache. - def del_link_by_key_elements from_dpid, from_portno, to_dpid, to_portno - del_link_by_key_tuple [from_dpid, from_portno, to_dpid, to_portno ] - end - - - # Delete a link from Topology cache. + # Delete a link from Topology map. # @param [Array(Integer,Integer,Integer,Integer)] key # 4 element array. [from.dpid, from.port_no, to.dpid, to.port_no] - def del_link_by_key_tuple key + def del_link_by_key key sw_from = @switches[ key[FROM_DPID] ]; sw_to = @switches[ key[TO_DPID] ]; - sw_from.del_link_by_key( key ) if sw_from - sw_to.del_link_by_key( key ) if sw_to + sw_from.delete_link( key ) if sw_from + sw_to.delete_link( key ) if sw_to @links.delete( key ) end + # + # @!group Lookup map contents + # - # Lookup a link from Topology cache. + # Lookup a switch from Topology map using dpid + # @param [Integer] dpid dpid of the switch to look for. + # @return [Switch] Switch instance found, or nil if not found. + def lookup_switch_by_dpid dpid + @switches[dpid] + end + + + # Lookup a link from Topology map. # @param [Hash] link look up a link instance using key elements listed in Options - # @option (see Link.[]) + # @option (see Link#initialize) + # @return [Link] Link instance found, or nil if not found. def lookup_link_by_hash link key = [ link[:from_dpid], link[:from_portno], link[:to_dpid], link[:to_portno] ]; return @links[ key ] end - # @!group Update by Hash methods + # + # @!group Update by Hash methods + # # Update Switch instance. Switch instance will be created if it does not exist. # Switch instance will be removed if the state is not up - # @param [Hash] sw switch instance info hash - # @option (see Switch.[]) - def update_switch_by_hash sw - raise ArgumentError, "Key element for Switch missing in Hash" if not Switch.has_keys?( sw ) + # @param [Switch,Hash] sw Switch instance or a Hash with switch info + # @option (see Switch#initialize) + def update_switch sw + sw = sw.property if sw.is_a?(Switch) + raise ArgumentError, "Mandatory key element for Switch missing in Hash" if not Switch.has_mandatory_keys?( sw ) dpid = sw[:dpid] if sw[:up] then @@ -146,7 +151,7 @@ def update_switch_by_hash sw if s != nil then s.update( sw ) else - add_switch Switch[ sw ] + add_switch Switch.new( sw ) end else del_switch_by_dpid dpid @@ -156,43 +161,49 @@ def update_switch_by_hash sw # Update Link instance. Link instance will be created if it does not exist. # Link instance will be removed if the state is not up - # @param [Hash] link link instance info hash - # @option (see Link.[]) - def update_link_by_hash link - raise ArgumentError, "Key element for Link missing in Hash" if not Link.has_keys?( link ) + # @param [Link,Hash] link Link instance or a Hash with link info + # @option (see Link#initialize) + def update_link link + link = link.property if link.is_a?(Link) + raise ArgumentError, "Mandatory key element for Link missing in Hash" if not Link.has_mandatory_keys?( link ) if link[:up] then l = lookup_link_by_hash( link ) if l != nil then l.update( link ) else - add_link Link[ link ] + add_link Link.new( link ) end else - del_link_by_key_elements(link[:from_dpid],link[:from_portno],link[:to_dpid],link[:to_portno]) + del_link link end end # Update Port instance. Port instance will be created if it does not exist. # Port instance will be removed if the state is not up - # @param [Hash] port port instance info hash - # @option (see Port.[]) - def update_port_by_hash port - raise ArgumentError, "Key element for Port missing in Hash" if not Port.has_keys?( port ) + # @param [Port,Hash] port Port instance or a Hash with port info + # @option (see Port#initialize) + def update_port port + port = port.property if port.is_a?(Port) + raise ArgumentError, "Mandatory key element for Port missing in Hash" if not Port.has_mandatory_keys?( port ) + dpid = port[:dpid] s = lookup_switch_by_dpid( dpid ) if port[:up] then - s ||= add_switch Switch[ { :dpid => dpid } ] + s ||= add_switch Switch.new( { :dpid => dpid } ) end - s.update_port_by_hash( port ) if s != nil + s.update_port( port ) if s != nil end - - # @endgroup - - + + # + # @!endgroup + # + + # Dump map info as a String + # @return [String] content of this map. def to_s - s = "Cache:\n" + s = "Map:\n" s << "(Empty)\n" if @switches.empty? and @links.empty? @switches.each_pair do |_, sw| s << sw.to_s @@ -202,6 +213,7 @@ def to_s end return s end + end end end diff --git a/ruby/trema/topology/map_api.rb b/ruby/trema/topology/map_api.rb new file mode 100644 index 000000000..66f7efae9 --- /dev/null +++ b/ruby/trema/topology/map_api.rb @@ -0,0 +1,339 @@ +# +# Copyright (C) 2008-2013 NEC Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +require 'trema/topology' +require 'trema/topology/map' +require 'trema/topology/guarded-property-accessor' + + +module Trema + + module Topology + class Switch + include Topology::GuardedPropertyAccesor + + # @!group Additional methods for Map maintenance + + # @return [Hash] Raw access to properties of a Switch. User must not modify mandatory key elements in Hash. + attr_reader :property + + + # @return [String] Switch key as a String + def key_str + return "S#{ dpid.to_s(16) }" + end + + + # @!attribute [w] up + # @param [Boolean] new_state_up + # @return [Boolean] returns new state + def up= new_state_up + if new_state_up then + @property[:up] = true + else + @property[:up] = false + end + end + + + # Update port on this switch + # @overload update_port port + # @param [Port] port Port instance to update + # @overload update_port port_hash + # @param [Hash] port_hash a Hash with port info about instance to update + # @option (see Port#initialize) + def update_port port + port = port.property if port.is_a?(Port) + raise ArgumentError, "Mandatory key element for Port missing in Hash" if not Port.has_mandatory_keys?( port ) + + portno = port[:portno] + if port[:up] then + if @ports.include?( portno ) then + @ports[ portno ].update port + else + add_port Port.new( port ) + end + else + @ports.delete( portno ) + end + end + end + + class Port + include Topology::GuardedPropertyAccesor + + # @!group Additional methods for Map maintenance + + # @return [Hash] Raw access to properties of a Port. User must not modify mandatory key elements in Hash. + attr_reader :property + + + # @return [String] Port key as a String + def key_str + return "P#{ dpid.to_s(16) }-#{ portno.to_s }" + end + end + + class Link + include Topology::GuardedPropertyAccesor + + # @!group Additional methods for Map maintenance + + # @return [Hash] Raw access to properties of a Link. User must not modify mandatory key elements in Hash. + attr_reader :property + + + # @return [String] Link key as a String + def key_str + return "L#{ from_dpid.to_s(16) }-#{ from_portno.to_s }-#{ to_dpid.to_s(16) }-#{ to_portno.to_s }" + end + end + end + + # Module to add Topology Map feature + module TopologyMap + include Topology + + # + # @private Just a placeholder for YARD. + # + def self.handler name + # Do nothing. + end + + # @!group Basic Map events and methods + # + # @example + # class TestController < Controller + # include Topology + # + # oneshot_timer_event :on_start, 0 + # def on_start + # send_enable_topology_discovery + # send_rebuild_map_request + # end + # + # def map_ready map + # info "Topology Map ready!" + # p map + # + # # You can do whatever with map after this point. + # # Topology::Map instance can also be obtained later using #get_map method. + # + # # example: + # # build spanning trees for each vertices + # end + # + # def link_status_updated link + # # Directly read Hash containing link info. + # p link[:from_dpid] + # # Or, create Topology::Link instance. + # linkObj = Link.new( links ) + # p linkObj.from_dpid + # + # # Do what ever before Topology Map update + # # Note: Link instance will be removed after map update, + # # if the notified link state was not up. + # + # # example: + # if not linkObj.up? then + # # Link down event: + # # 1. drop flow path, which contains this link. + # # 2. resolve alternate path with updated topology. + # # 3. set newly calculated flow path. + # else + # # Link up event: + # # 1. PathResolver rebuild spanning trees. + # # 2. replace existing path by newly calculated path using using FlowManager::libPath? + # end + # + # # (Optional) Manually update Map + # # Note: Map will be automatically updated after exit from this handler + # # even if this manual update + # update_map_by_link_hash link + # + # if map_ready? + # # Check to see if we're at a point after map_ready event. + # + # # Do whatever with updated map. + # puts get_map + # end + # end + # end + # + # Start rebuilding topology map. + # `map_ready` or specified block will be called, when map rebuild is complete. + # @param [Boolean] clear_map Clear map before update. + # @yieldparam map [Map] Current map + # @return [Boolean] true if request sent successfully + def send_rebuild_map_request clear_map=true, &block + @need_map_ready_notify = true + @map_up_to_date = false + @map = Map.new if clear_map + @all_link_received = false + @all_switch_received = false + @all_port_received = false + + succ = true + succ &= get_all_switch_status do |switches| + switches.each { |sw| update_map_by_switch_hash(sw) } + @all_switch_received = true + notify_map_ready( &block ) if @need_map_ready_notify and map_ready? + end + + succ &= get_all_port_status do |ports| + ports.each { |port| update_map_by_port_hash(port) } + @all_port_received = true + notify_map_ready( &block ) if @need_map_ready_notify and map_ready? + end + + succ &= get_all_link_status do |links| + links.each { |link| update_map_by_link_hash(link) } + @all_link_received = true + notify_map_ready( &block ) if @need_map_ready_notify and map_ready? + end + return succ + end + + + # + # @!method map_ready( map ) + # Event handler for map_ready event. + # @abstract map_ready event handler. Override this to implement a custom handler. + # @param [Map] map + # @return [void] + # Reference to current topology map. + # @see #send_rebuild_map_request See #send_rebuild_map_request for usage example. + handler :map_ready + + + # + # Returns a reference to latest map available. + # @note Returned instance may be outdated, when invoked inside Topology update event handlers + # before manually update of the map. + # @return [Map] Returns a reference to available map. + # + # @see #get_map + # + def get_last_map + return @map + end + + # + # Returns a reference to current map when available. + # @note This method will return nil when a updated map is not ready. + # @return [Map] Returns a reference to latest map or nil, when current map is not available. + # + # @example + # def link_status_updated lnk + # get_last_map # returns a outdated network map. + # get_map # returns nil + # update_map_by_link_hash lnk + # get_last_map # returns the current map + # get_map # returns the current map + # end + # + def get_map + return @map if map_up_to_date? + return nil + end + + + # Check if map is ready to use. + def map_ready? + @all_link_received and @all_switch_received and @all_port_received + end + + + # Check if current map is in latest state. + def map_up_to_date? + map_ready? and @map_up_to_date + end + + + # Call inside switch_status_updated handler to manually update map to latest state + # @note Map will be automatically updated even if this method call was omitted. + # @param [Hash] sw Specify switch hash given to switch_status_updated. Additional properties may be added to reflect internal map. + # @return [void] + # @see #send_rebuild_map_request See #send_rebuild_map_request for usage example. + def update_map_by_switch_hash sw + @map ||= Topology::Map.new + @map.update_switch( sw ) + @map_up_to_date = true + end + + + # Call inside link_status_updated handler to manually update map to latest state + # @note Map will be automatically updated even if this method call was omitted. + # @param [Hash] link Specify link hash given to link_status_updated. Additional properties may be added to reflect internal map. + # @return [void] + # @see #send_rebuild_map_request See #send_rebuild_map_request for usage example. + def update_map_by_link_hash link + @map ||= Topology::Map.new + @map.update_link( link ) + @map_up_to_date = true + end + + + # Call inside port_status_updated handler to manually update map to latest state + # @note Map will be automatically updated even if this method call was omitted. + # @param [Hash] port Specify link hash given to port_status_updated. Additional properties may be added to reflect internal map. + # @return [void] + # @see #send_rebuild_map_request See #send_rebuild_map_request for usage example. + def update_map_by_port_hash port + @map ||= Topology::Map.new + @map.update_port( port ) + @map_up_to_date = true + end + + ###################### + protected + ###################### + + + alias_method :super_switch_status_updated, :_switch_status_updated + def _switch_status_updated sw + super_switch_status_updated( sw ) + update_map_by_switch_hash( sw ) + end + + + alias_method :super_port_status_updated, :_port_status_updated + def _port_status_updated port + super_port_status_updated( port ) + update_map_by_port_hash( port ) + end + + + alias_method :super_link_status_updated, :_link_status_updated + def _link_status_updated link + super_link_status_updated( link ) + update_map_by_link_hash( link ) + end + + + def notify_map_ready &block + if block then + block.call( @map ) + elsif self.respond_to? :map_ready then + map_ready @map + end + @need_map_ready_notify = false + end + end +end + diff --git a/ruby/trema/topology/port.rb b/ruby/trema/topology/port.rb index 0ff60d73f..997365907 100644 --- a/ruby/trema/topology/port.rb +++ b/ruby/trema/topology/port.rb @@ -19,40 +19,46 @@ module Trema module Topology # A class to represent a port in a Topology - class Port < Hash + class Port + + # @!attribute [r] dpid # @return [Integer] datapath ID of the switch which this port belong to. def dpid - return self[:dpid] + return @property[:dpid] end + # @!attribute [r] portno # @return [Integer] port number def portno - return self[:portno] + return @property[:portno] end + # @!attribute [r] up # @return [Boolean] returns true if port is up def up? - return self[:up] + return @property[:up] end # @return [Boolean] returns true if port is external def external? - return self[:external] == Topology::TD_PORT_EXTERNAL + return @property[:external] end + # @!attribute [r] name # @return [String] returns port's name def name - return self[:name] + return @property[:name] end + # @!attribute [r] mac # @return [String] returns port's mac address def mac - return self[:mac] + return @property[:mac] end @@ -62,31 +68,24 @@ def key end - # @return [String] Port key as a String - def key_str - return "P#{ dpid.to_s(16) }-#{ portno.to_s }" - end - - # Port constructor # @param [Hash] port Hash containing Port properties. Must at least contain keys listed in Options. # @option port [Integer] :dpid Switch dpid which this port belongs # @option port [Integer] :portno port number # @return [Port] # @example - # port = Port[ {:dpid => 1234, :portno => 42} ] - def Port.[]( port ) - raise ArgumentError, "Key element for Port missing in Hash" if not Port.has_keys?( port ) - - port[ :dpid ].freeze - port[ :portno ].freeze - super( port ) + # port = Port.new( {:dpid => 1234, :portno => 42} ) + def initialize( port ) + raise ArgumentError, "Mandatory key element for Port missing in Hash" if not Port.has_mandatory_keys?( port ) + # TODO type check mandatory key? + # TODO copy Hash? + @property = port end - # @param key Hash key element + # @param [Symbol] key Hash key element # @return [Boolean] true if key is key element for Port - def Port.is_key?( key ) + def Port.is_mandatory_key?( key ) case key when :dpid, :portno return true @@ -99,29 +98,24 @@ def Port.is_key?( key ) # Test if Hash has required key as a Port instance # @param hash Hash to test # @return [Boolean] true if hash has all required keys. - def Port.has_keys?( hash ) + def Port.has_mandatory_keys?( hash ) return !(hash.values_at(:dpid, :portno).include? nil) end - # @private - def initialize( *arg ) - raise ArgumentError, "Empty Port cannot be created. Use Port[ {...} ] form." - end - - + # @return [String] human readable string representation. def to_s "Port: 0x#{ dpid.to_s(16) }:#{ portno.to_s } - {#{property_to_s}}" end ############################################################################ - private + protected ############################################################################ def property_to_s - kvp_ary = self.select { |key,_| not Port.is_key?(key) } + kvp_ary = @property.select { |key,_| not Port.is_mandatory_key?( key ) } kvp_ary = kvp_ary.sort_by { |key,_| key.to_s } s = kvp_ary.map { |key, val| "#{key.to_s}:#{val.inspect}" }.join(", ") return s diff --git a/ruby/trema/topology/switch.rb b/ruby/trema/topology/switch.rb index d9f63e7b4..c6eb82288 100644 --- a/ruby/trema/topology/switch.rb +++ b/ruby/trema/topology/switch.rb @@ -19,28 +19,33 @@ module Trema module Topology # A class to represent a switch in a Topology - class Switch < Hash - # Hash of Ports: port_no => Port - # @note Manipulation of ports has no impact on topology. - # e.g. Removing element from ports will NOT delete a links on that port. + class Switch + + # @return [{Integer=>Port}] Hash of Ports: port_no => Port attr_reader :ports - # Hash of inbound,outbound Link: [from.dpid, from.port_no, to.dpid, to.port_no] => Links - # @note Do not directly add/remove elements in this Hash. - # These hash will be updated through ToplogyCache methods. + # @return [{[Integer,Integer,Integer,Integer]=>Link}] Hash of inbound/outbound Link: [from.dpid, from.port_no, to.dpid, to.port_no] => Links attr_reader :links_in, :links_out + # @!attribute [r] links + # @return [{[Integer,Integer,Integer,Integer]=>Link}] Hash of inbound+outbound Link: [from.dpid, from.port_no, to.dpid, to.port_no] => Links + def links + return links_in.merge(links_out) + end + + + # @!attribute [r] dpid # @return [Integer] Switch datapath ID for this Switch instance def dpid - return self[:dpid] + return @property[:dpid] end # @return [Boolean] returns true if switch is up def up? - return self[:up] + return @property[:up] end @@ -50,57 +55,7 @@ def key end - # @return [String] Switch key as a String - def key_str - return "S#{ dpid.to_s(16) }" - end - - - # Switch constructor. - # @param [Hash] sw Hash containing Switch properties. Must at least contain keys listed in Options. - # @option sw [Integer] :dpid Switch dpid - # @return Switch - # @example - # sw = Switch[ {:dpid => 0x1234} ] - def Switch.[]( sw ) - raise ArgumentError, "Key element for Switch missing in Hash" if not Switch.has_keys?( sw ) - - sw[:dpid].freeze - new_instance = super( sw ) - new_instance.initialize_members - return new_instance - end - - - # @param key Hash key element - # @return [Boolean] true if key is key element for Switch - def Switch.is_key?( key ) - return ( key == :dpid ) - end - - - # Test if Hash has required key as a Switch instance - # @param hash Hash to test - # @return [Boolean] true if hash has all required keys. - def Switch.has_keys?( hash ) - return hash.has_key?( :dpid ) - end - - - # @private - def initialize( *arg ) - raise ArgumentError, "Empty Switch cannot be created. Use Switch[ {...} ] form." - end - - - def initialize_members - @ports = Hash.new - @links_in = Hash.new - @links_out = Hash.new - return self - end - - + # Add a port on this Switch. # @param [Port] port Port instance to add to switch def add_port port raise TypeError, "Trema::Topology::Port expected" if not port.is_a?(Port) @@ -109,76 +64,103 @@ def add_port port end - # @param [Integer] portno Create a Port instance and add to switch - def add_port_by_portno portno - @ports[ portno ] = Port[ { :dpid => self.dpid, :portno => portno } ] - end - - - # @param [Port] port Port instance to delete from - def del_port port - @ports.delete( port.portno ) - end - - - # @param [Integer] portno port number to delete - def del_port_by_portno portno - @ports.delete( portno ); - end - - - # Update port on this switch by Hash - # @param [Hash] port Hash containing info about updated port. - # @see Port.[] - def update_port_by_hash port - raise ArgumentError, "Key element for Port missing in Hash" if not Port.has_keys?( port ) - - portno = port[:portno] - if port[:up] then - if @ports.include?( portno ) then - @ports[ portno ].update port - else - add_port Port[ port ] - end + # Delete a port on this Switch. + # It will be silently ignored if the port did not belong to this switch + # @overload delete_port port + # @param [Port] port Port instance to delete + # @overload delete_port port_hash + # @param [Hash] port_hash a Hash with port info about instance to delete + # @overload delete_port portno + # @param [Integer] portno portno of instance to delete + def delete_port port + if port.is_a?(Port) + @ports.delete( port.portno ) + elsif port.is_a(Hash) + @ports.delete( port[:portno] ) + elsif port.is_a(Integer) + @ports.delete( port ) else - @ports.delete( portno ) + raise TypeError, "Either Port, Port info(Hash), portno(Integer) expected" end end - # @param [Link] link inbound link to add. - def add_inbound_link link - raise ArgumentError, "Specified link is not a link to this switch" if link.to_dpid != self.dpid - @links_in[ link.key ] = link - end - - - # @param [Link] link outbound link to add. - def add_outbound_link link - raise ArgumentError, "Specified link is not a link from this switch" if link.from_dpid != self.dpid - @links_out[ link.key ] = link + # Add a link to this Switch. + # It will be silently ignored if the link was not bound to this switch + # @param [Link] link link to add. + def add_link link + @links_in[ link.key ] = link if link.to_dpid == self.dpid + @links_out[ link.key ] = link if link.from_dpid == self.dpid + end + + + # Delete a link on this Switch. + # It will be silently ignored if the link was not bound to this switch + # @overload delete_link link + # @param [Port] port Link instance to delete + # @overload delete_link link_hash + # @param [Hash] port_hash a Hash with link info about instance to delete + # @overload delete_link key + # @param [[Integer, Integer, Integer, Integer]] key Link key 4-tuple of the link to delete + def delete_link link + if link.is_a?(Link) + key = link.key + @links_in.delete( key ) if link.to_dpid == self.dpid + @links_out.delete( key ) if link.from_dpid == self.dpid + elsif link.is_a?(Hash) + key = [ link[:from_dpid], link[:from_portno], link[:to_dpid], link[:to_portno] ] + @links_in.delete( key ) if link[:to_dpid] == self.dpid + @links_out.delete( key ) if link[:from_dpid] == self.dpid + elsif link.is_a?(Array) and link.size == 4 + key = [ link[FROM_DPID], link[FROM_PORTNO], link[TO_DPID], link[TO_PORTNO] ] + @links_in.delete( key ) if link[TO_DPID] == self.dpid + @links_out.delete( key ) if link[FROM_DPID] == self.dpid + else + raise TypeError, "Either Link, Link info(Hash), Link key tuple([Integer,Integer,Integer,Integer]) expected" + end end - # @param [Link] link inbound link to delete. - def del_inbound_link link - @links_in.delete link.key + # Switch constructor. + # @overload initialize sw_hash + # @param [Hash] sw_hash a Hash containing Switch properties. Must at least contain keys listed in Options. + # @option sw_hash [Integer] :dpid Switch dpid + # @overload initialize dpid + # @param [Integer] dpid datapath_id + # @return Switch + # @example + # sw = Switch.new( {:dpid => 0x1234} ) + # sw = Switch.new( 0x1234 ) + def initialize( sw ) + sw = { :dpid => sw, :up => true } if sw.is_a?(Integer) + raise ArgumentError, "Mandatory key element for Switch missing in Hash" if not Switch.has_mandatory_keys?( sw ) + sw.merge!( { :up => true } ) if not sw.member? :up + + # TODO type check mandatory key? + # TODO copy Hash? + @property = sw + @ports = Hash.new + @links_in = Hash.new + @links_out = Hash.new end - # @param [Link] link outbound link to delete - def del_outbound_link link - @links_out.delete link.key + # @param [Symbol] key Hash key element + # @return [Boolean] true if key is key element for Switch + def Switch.is_mandatory_key?( key ) + return ( key == :dpid ) end - # @param [Array(Integer, Integer, Integer, Integer)] key Link key 4-tuple of the link to delete - def del_link_by_key key - @links_in.delete key - @links_out.delete key + # Test if Hash has required key as a Switch instance + # @param hash Hash to test + # @return [Boolean] true if hash has all required keys. + def Switch.has_mandatory_keys?( hash ) + return hash.has_key?( :dpid ) end + # @return [String] human readable string representation. def to_s s = "Switch: 0x#{ dpid.to_s(16) } - {#{property_to_s}}\n" @ports.each_pair do |_,port| @@ -197,12 +179,12 @@ def to_s ############################################################################ - private + protected ############################################################################ def property_to_s - kvp_ary = self.select { |key,_| not Switch.is_key?(key) } + kvp_ary = @property.select { |key,_| not Switch.is_mandatory_key?( key ) } kvp_ary = kvp_ary.sort_by { |key,_| key.to_s } s = kvp_ary.map { |key, val| "#{key.to_s}:#{val.inspect}" }.join(", ") return s @@ -210,3 +192,4 @@ def property_to_s end end end + diff --git a/ruby/trema/topology/topology_cache.rb b/ruby/trema/topology/topology_cache.rb deleted file mode 100644 index e933b062b..000000000 --- a/ruby/trema/topology/topology_cache.rb +++ /dev/null @@ -1,228 +0,0 @@ -# -# Copyright (C) 2008-2013 NEC Corporation -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2, as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# - - -require 'trema/topology/port' -require 'trema/topology/link' -require 'trema/topology/switch' -require 'trema/topology/cache' - - -module Trema - module Topology - # Link's Hash key 4-tuple's array index - FROM_DPID = 0 - # Link's Hash key 4-tuple's array index - FROM_PORTNO = 1 - # Link's Hash key 4-tuple's array index - TO_DPID = 2 - # Link's Hash key 4-tuple's array index - TO_PORTNO = 3 - end - - - module Topology - # - # @private Just a placeholder for YARD. - # - def self.handler name - # Do nothing. - end - - # @!group Basic Cache events and methods - # - # @example - # class TestController < Controller - # include Topology - # - # oneshot_timer_event :on_start, 0 - # def on_start - # enable_topology_discovery - # send_rebuild_cache_request - # end - # - # def cache_ready cache - # info "Topology Cache ready!" - # p cache - # - # # You can do whatever with cache after this point. - # # Topology::Cache instance can also be obtained later using #get_cache method. - # - # # example: - # # build spanning trees for each vertices - # end - # - # def link_status_updated link - # # Directly read Hash containing link info. - # p link[:from_dpid] - # # Or, create Topology::Link instance. - # linkObj = Link[ links ] - # p linkObj.from_dpid - # - # # Do what ever before Topology Cache update - # # Note: Link instance will be removed after cache update, - # # if the state was not up. - # - # # example: - # if not linkObj.up? then - # # Link down event: - # # 1. drop flow path, which contains this link. - # # 2. resolve alternate path with updated topology. - # # 3. set newly calculated flow path. - # else - # # Link up event: - # # 1. PathResolver rebuild spanning trees. - # # 2. replace existing path by newly calculated path using using FlowManager::libPath? - # end - # - # # (Optional) Manually update Cache - # # Note: Cache will be automatically updated after exit from this handler - # # even if this manual update - # update_cache_by_link_hash link - # - # if cache_ready? - # # Check to see if we're at a point after cache_ready event. - # - # # Do whatever with updated cache. - # puts get_cache - # end - # end - # end - # - # Start rebuilding topology cache. - # `cache_ready` will be called, when cache rebuild is complete - # @param [Boolean] clear_cache Clear cache before update. - def send_rebuild_cache_request clear_cache=true - @need_cache_ready_notify = true - @cache_up_to_date = false - @cache = Topology::Cache.new if clear_cache - @all_link_received = false - @all_switch_received = false - @all_port_received = false - - send_all_switch_status_request do |switches| - switches.each { |sw| update_cache_by_switch_hash(sw) } - @all_switch_received = true - notify_cache_ready() if @need_cache_ready_notify and cache_ready? - end - - send_all_port_status_request do |ports| - ports.each { |port| update_cache_by_port_hash(port) } - @all_port_received = true - notify_cache_ready() if @need_cache_ready_notify and cache_ready? - end - - send_all_link_status_request do |links| - links.each { |link| update_cache_by_link_hash(link) } - @all_link_received = true - notify_cache_ready() if @need_cache_ready_notify and cache_ready? - end - end - - - # - # @!method cache_ready( cache ) - # Event handler for cache_ready event. - # @abstract cache_ready event handler. Override this to implement a custom handler. - # @param [Cache] cache - # Reference to current topology cache. - # @see #send_rebuild_cache_request See #send_rebuild_cache_request for usage example. - handler :cache_ready - - - # - # @return [Cache] Returns a reference to current cache - # - def get_cache - @cache - end - - - # Check if cache is ready to use. - def cache_ready? - @all_link_received and @all_switch_received and @all_port_received - end - - - # Check if current cache is in latest state. - def cache_up_to_date? - cache_ready? and @cache_up_to_date - end - - - # Call inside switch_status_updated handler to manually update cache to latest state - # @note Cache will be automatically updated even if this method call was omitted. - # @param [Hash] sw Specify switch hash given to switch_status_updated. Additional properties may be added to reflect internal cache. - # @see #send_rebuild_cache_request See #send_rebuild_cache_request for usage example. - def update_cache_by_switch_hash sw - _switch_status_updated sw - end - - - # Call inside link_status_updated handler to manually update cache to latest state - # @note Cache will be automatically updated even if this method call was omitted. - # @param [Hash] link Specify link hash given to link_status_updated. Additional properties may be added to reflect internal cache. - # @see #send_rebuild_cache_request See #send_rebuild_cache_request for usage example. - def update_cache_by_link_hash link - _link_status_updated link - end - - - # Call inside port_status_updated handler to manually update cache to latest state - # @note Cache will be automatically updated even if this method call was omitted. - # @param [Hash] port Specify link hash given to port_status_updated. Additional properties may be added to reflect internal cache. - # @see #send_rebuild_cache_request See #send_rebuild_cache_request for usage example. - def update_cache_by_port_hash port - _port_status_updated port - end - - ###################### - protected - ###################### - - - def _switch_status_updated sw - @cache ||= Topology::Cache.new - @cache.update_switch_by_hash( sw ) - @cache_up_to_date = true - end - - - def _port_status_updated port - @cache ||= Topology::Cache.new - @cache.update_port_by_hash( port ) - @cache_up_to_date = true - end - - - def _link_status_updated link - @cache ||= Topology::Cache.new - @cache.update_link_by_hash( link ) - @cache_up_to_date = true - end - - - # @private - def notify_cache_ready - if self.respond_to? :cache_ready then - cache_ready @cache - end - @need_cache_ready_notify = false - end - end -end - diff --git a/spec/trema/topology-cache_spec.rb b/spec/trema/topology-cache_spec.rb deleted file mode 100644 index 677fe1c6c..000000000 --- a/spec/trema/topology-cache_spec.rb +++ /dev/null @@ -1,291 +0,0 @@ -# -# Copyright (C) 2008-2013 NEC Corporation -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2, as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# - - -require File.join( File.dirname( __FILE__ ), "..", "spec_helper" ) -require "trema" -require "trema/topology" -require "trema/topology/topology_cache" - - -include Trema::Topology - -describe Trema::Topology, :nosudo => true do - - describe Cache do - it "should initialize it's members to empty" do - c = Cache.new - expect( c.switches.empty? ).to be_true - expect( c.links.empty? ).to be_true - end - - it "should have switch manipulation methods" do - c = Cache.new - c.add_switch Switch[ {:dpid => 0x1234 } ] - expect( c.switches.empty? ).to be_false - - expect( c.lookup_switch_by_dpid( 0x1234 ) ).not_to be_nil - - c.add_switch Switch[ {:dpid => 0x5678 } ] - expect( c.switches[ 0x5678 ] ).not_to be_nil - - c.del_switch Switch[ {:dpid => 0x5678 } ] - c.del_switch_by_dpid 0x1234 - expect( c.switches.empty? ).to be_true - end - - it "should have link manipulation methods" do - c = Cache.new - c.add_switch Switch[ {:dpid => 0x1234 } ] - c.add_switch Switch[ {:dpid => 0x5678 } ] - - c.add_link Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] - expect( c.links.empty? ).to be_false - - expect( c.lookup_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) ).not_to be_nil - - c.del_link Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] - expect( c.lookup_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) ).to be_nil - - c.add_link Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] - c.del_link_by_key_elements 0x1234, 42, 0x5678, 72 - expect( c.lookup_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) ).to be_nil - - c.add_link Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] - c.del_link_by_key_tuple [0x1234, 42, 0x5678, 72] - expect( c.lookup_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) ).to be_nil - - expect( c.links.empty? ).to be_true - end - - describe "Switch events" do - it "should add Switch on switch up event" do - c = Cache.new - c.update_switch_by_hash( {:dpid => 0x1234, :status => 1, :up => true } ) - expect( c.switches ).to include(0x1234) - - expect( c.switches[ 0x1234 ][:status] ).to be == 1 - expect( c.switches[ 0x1234 ][:up] ).to be_true - end - - it "should delete Switch on switch up event" do - c = Cache.new - c.add_switch Switch[ {:dpid => 0x1234, :status => 1, :up => true } ] - c.add_switch Switch[ {:dpid => 0x5678, :status => 1, :up => true } ] - - c.update_switch_by_hash( {:dpid => 0x1234, :status => 0, :up => false } ) - expect( c.switches ).not_to include(0x1234) - expect( c.switches ).to include(0x5678) - end - - it "should update Switch on existing switch's up event" do - c = Cache.new - c.add_switch Switch[ {:dpid => 0x1234, :status => 1, :up => true, :extra => "Old Value", :no_change => true } ] - c.update_switch_by_hash( {:dpid => 0x1234, :status => 1, :up => true, :extra => "New Value" } ) - expect( c.switches ).to include(0x1234) - expect( c.switches[ 0x1234 ][:status] ).to be == 1 - expect( c.switches[ 0x1234 ][:up] ).to be_true - expect( c.switches[ 0x1234 ][:extra] ).to match("New Value") - expect( c.switches[ 0x1234 ][:no_change] ).to be_true - end - end - - describe "Link event" do - before do - @c = Cache.new - @c.add_switch Switch[ {:dpid => 0x1234, :status => 1, :up => true } ] - @c.add_switch Switch[ {:dpid => 0x5678, :status => 1, :up => true } ] - end - - it "should add Link on link up event" do - @c.update_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :status => 0, :up => true, :unstable => false } ) - expect( @c.links[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil - expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:status] ).to be == 0 - expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:up] ).to be_true - expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:unstable] ).to be_false - - expect( @c.switches[0x1234].links_out[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil - expect( @c.switches[0x5678].links_in[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil - end - - it "should remove Link on link down event" do - @c.add_link Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] - expect( @c.links.empty? ).to be_false - - @c.update_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :status => 1, :up => false, :unstable => false } ) - expect( @c.links[ [0x1234, 42, 0x5678, 72] ] ).to be_nil - end - - it "should update Link on existing link event" do - @c.update_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :status => 1, :up => true, :unstable => false } ) - - @c.update_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => true, :unstable => true } ) - expect( @c.links[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil - expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:status] ).to be == 1 - expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:up] ).to be_true - expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:unstable] ).to be_true - - expect( @c.switches[0x1234].links_out[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil - expect( @c.switches[0x5678].links_in[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil - end - end - - describe "Link event recoverable error" do - before do - @c = Cache.new - end - it "should add Switch and Link on link up event, when switch did not exist" do - @c.update_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :status => 0, :up => true, :unstable => false } ) - expect( @c.links[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil - expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:status] ).to be == 0 - expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:up] ).to be_true - expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:unstable] ).to be_false - - expect( @c.switches[0x1234].links_out[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil - expect( @c.switches[0x5678].links_in[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil - end - end - - describe "Port event" do - before do - @c = Cache.new - @c.add_switch Switch[ {:dpid => 0x1234, :status => 1, :up => true } ] - @c.add_switch Switch[ {:dpid => 0x5678, :status => 1, :up => true } ] - end - - it "should add Port on port up event" do - @c.update_port_by_hash( { :dpid => 0x1234, :portno => 42, :name => "Port Name", :mac => "FF:FF:FF:FF:FF:FF", :external => 0, :status => 0, :up => true } ) - expect( @c.switches[0x1234].ports[42][:name] ).to match("Port Name") - end - - it "should delete Port on port down event" do - @c.update_port_by_hash( { :dpid => 0x1234, :portno => 42, :name => "Port Name", :mac => "FF:FF:FF:FF:FF:FF", :external => 0, :status => 1, :up => false } ) - expect( @c.switches[0x1234].ports ).not_to include(42) - end - - it "should update Port on existing port up event" do - @c.update_port_by_hash( { :dpid => 0x1234, :portno => 42, :name => "Port Name", :mac => "FF:FF:FF:FF:FF:FF", :external => 0, :status => 0, :up => true } ) - @c.update_port_by_hash( { :dpid => 0x1234, :portno => 42, :name => "New Port Name", :external => 0, :status => 0, :up => true } ) - expect( @c.switches[0x1234].ports[42][:name] ).to match("Port Name") - expect( @c.switches[0x1234].ports[42][:mac] ).to match("FF:FF:FF:FF:FF:FF") - end - end - - describe "Port event recoverable error" do - before do - @c = Cache.new - end - it "should add Switch and Port on port up event, when switch did not exist" do - @c.update_port_by_hash( { :dpid => 0x1234, :portno => 42, :name => "Port Name", :mac => "FF:FF:FF:FF:FF:FF", :external => 0, :status => 0, :up => true } ) - expect( @c.switches[0x1234].ports[42][:name] ).to match("Port Name") - end - end - - it "should be serializable to human readable form by to_s" do - c = Cache.new - - s = Switch[ { :dpid => 0x1234, :up => true, :magic => 42 } ] - s.add_port Port[ { :dpid => 0x1234, :portno => 42, :up => true, :not_used => 1 } ] - c.add_switch s - c.add_link Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :not_used => 1, :up => true } ] - c.add_link Link[ {:from_dpid => 0xABCD, :from_portno => 102, :to_dpid => 0x1234, :to_portno => 42, :not_used => 1, :up => true } ] - expect( c.to_s ).to be == <<-EOS -Cache: -Switch: 0x1234 - {magic:42, up:true} - Port: 0x1234:42 - {not_used:1, up:true} - Links_in - <= 0xabcd:102 - Links_out - => 0x5678:72 -Switch: 0xabcd - {} - Links_out - => 0x1234:42 -Switch: 0x5678 - {} - Links_in - <= 0x1234:42 -Link: (0xabcd:102)->(0x1234:42) - {not_used:1, up:true} -Link: (0x1234:42)->(0x5678:72) - {not_used:1, up:true} - EOS - end - end - - describe "Cache management functions" do - before do - class DummyController - include Topology - end - @c = DummyController.new - end - - it "should respond to get_cache, cache_ready?, cache_up_to_date?" do - expect( @c ).to respond_to( :get_cache, :cache_ready?, :cache_up_to_date? ) - end - - it "should issue all status request on rebuild request" do - - expect( @c.cache_up_to_date? ).to be_false - - @c.should_receive(:send_all_switch_status_request).and_yield( [ {:dpid => 0x1234, :up => true } ] ) - @c.should_receive(:send_all_port_status_request).and_yield( [ {:dpid => 0x1234, :portno => 42, :up => true } ] ) - @c.should_receive(:send_all_link_status_request).and_yield( [ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => true } ] ) - - @c.should_receive(:cache_ready).once - - @c.send_rebuild_cache_request - - expect( @c.cache_up_to_date? ).to be_true - end - - it "should update Cache on Switch update event" do - @c.update_cache_by_switch_hash( {:dpid => 0x1234, :up => true } ) - cache = @c.get_cache - expect( cache.lookup_switch_by_dpid( 0x1234 ) ).not_to be_nil - - @c.update_cache_by_switch_hash( {:dpid => 0x1234, :up => false } ) - cache = @c.get_cache - expect( cache.lookup_switch_by_dpid( 0x1234 ) ).to be_nil - end - - it "should update Cache on Port update event" do - @c.update_cache_by_port_hash( {:dpid => 0x1234, :portno => 42, :up => true } ) - cache = @c.get_cache - expect( cache.lookup_switch_by_dpid( 0x1234 ).ports[42] ).not_to be_nil - - @c.update_cache_by_port_hash( {:dpid => 0x1234, :portno => 42, :up => false } ) - cache = @c.get_cache - expect( cache.lookup_switch_by_dpid( 0x1234 ).ports[42] ).to be_nil - end - - it "should update Cache on Link update event" do - @c.update_cache_by_link_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => true } ) - cache = @c.get_cache - expect( cache.lookup_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) ).not_to be_nil - - @c.update_cache_by_link_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => false } ) - cache = @c.get_cache - expect( cache.lookup_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) ).to be_nil - end - - - end -end - -### Local variables: -### mode: Ruby -### coding: utf-8-unix -### indent-tabs-mode: nil -### End: diff --git a/spec/trema/topology-link_spec.rb b/spec/trema/topology-link_spec.rb index f1eac9590..856418acc 100644 --- a/spec/trema/topology-link_spec.rb +++ b/spec/trema/topology-link_spec.rb @@ -18,8 +18,6 @@ require File.join( File.dirname( __FILE__ ), "..", "spec_helper" ) require "trema" -require "trema/topology" -require "trema/topology/topology_cache" include Trema::Topology @@ -27,9 +25,9 @@ describe Trema::Topology, :nosudo => true do describe Link do - it "should be initialized like Hash" do + it "should be initialized with Hash" do expect { - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) }.not_to raise_error end @@ -41,82 +39,78 @@ it "should raise error if key :from_dpid missing" do expect { - link = Link[ {:from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] + link = Link.new( {:from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) }.to raise_error(ArgumentError) end it "should raise error if key :from_portno missing" do expect { - link = Link[ {:from_dpid => 0x1234, :to_dpid => 0x5678, :to_portno => 72 } ] + link = Link.new( {:from_dpid => 0x1234, :to_dpid => 0x5678, :to_portno => 72 } ) }.to raise_error(ArgumentError) end it "should raise error if key :to_dpid missing" do expect { - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_portno => 72 } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_portno => 72 } ) }.to raise_error(ArgumentError) end it "should raise error if key :to_portno missing" do expect { - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678 } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678 } ) }.to raise_error(ArgumentError) end it "should have from_dpid accessor" do - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) expect( link.from_dpid ).to be == 0x1234 end it "should have from_dpid accessor" do - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) expect( link.from_portno ).to be == 42 end + it "should have to_dpid accessor" do - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) expect( link.to_dpid ).to be == 0x5678 end it "should have to_dpid accessor" do - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) expect( link.to_portno ).to be == 72 end it "has method up?" do - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => true } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => true } ) expect( link.up? ).to be_true - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => false } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => false } ) expect( link.up? ).to be_false - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) expect( link.up? ).to be_false end it "has method unstable?" do - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :unstable => true } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :unstable => true } ) expect( link.unstable? ).to be_true - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :unstable => false } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :unstable => false } ) expect( link.unstable? ).to be_false - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) expect( link.unstable? ).to be_false end it "should have method key" do - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) expect( link.key ).to be == [0x1234,42,0x5678,72] end - it "should have method key_str" do - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] - expect( link.key_str ).to match("1234-42-5678-72") - end - it "should be serializable to human readable form by to_s" do - link = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => true, :not_used => 1 } ] + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => true, :not_used => 1 } ) expect( link.to_s ).to be == "Link: (0x1234:42)->(0x5678:72) - {not_used:1, up:true}" end end diff --git a/spec/trema/topology-map_spec.rb b/spec/trema/topology-map_spec.rb new file mode 100644 index 000000000..9119f8386 --- /dev/null +++ b/spec/trema/topology-map_spec.rb @@ -0,0 +1,459 @@ +# +# Copyright (C) 2008-2013 NEC Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + + +require File.join( File.dirname( __FILE__ ), "..", "spec_helper" ) +require "trema" +require "trema/topology/map_api" + + +include Trema::TopologyMap + +describe Trema::TopologyMap, :nosudo => true do + + describe "Monkey patched Switch" do + it "should have method key_str" do + s = Switch.new( { :dpid => 0x1234, :magic => 42 } ) + expect( s.key_str ).to match("1234") + end + + it "up state should be overwritable" do + s = Switch.new( { :dpid => 0x1234, :up => true } ) + s.up = false + expect( s.up? ).to be_false + + s = Switch.new( { :dpid => 0x1234, :up => false } ) + s.up = true + expect( s.up? ).to be_true + end + + describe "[],[]=" do + before do + @s = Switch.new( {:dpid => 0x1234, :extra => true } ) + end + + it "should have access to it's property" do + expect( @s[:extra] ).to be_true + end + + it "should be writable to non mandatory key" do + @s[:extra] = false + expect( @s[:extra] ).to be_false + end + + it "should raise exception if attempt to write to non mandatory key" do + expect { + @s[:dpid] = 0x5678 + }.to raise_error(ArgumentError) + end + end + + describe "delete" do + before do + @s = Switch.new( {:dpid => 0x1234, :extra => true } ) + end + + it "should remove property value" do + @s.delete(:extra) + expect( @s.property.has_key?(:extra) ).to be_false + end + + it "should ignore mandatory key" do + @s.delete(:dpid) + expect( @s[:dpid] ).to eq(0x1234) + end + end + + describe "update" do + before do + @s = Switch.new( {:dpid => 0x1234, :extra => true } ) + end + it "should update property by given hash" do + @s.update( {:extra => false, :new => 42}) + expect( @s[:extra] ).to be_false + expect( @s[:new] ).to eq(42) + end + + it "should ignore update to mandatory key" do + @s.update( {:extra => false, :dpid => 42}) + expect( @s[:extra] ).to be_false + expect( @s[:dpid] ).to eq(0x1234) + end + end + end + + describe "Monkey Patched Port" do + it "should have method key_str" do + port = Port.new( {:dpid => 0x1234, :portno => 42 } ) + expect( port.key_str ).to match("1234-42") + end + end + + describe "Monkey Patched Link" do + it "should have method key_str" do + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) + expect( link.key_str ).to match("1234-42-5678-72") + end + + describe "[],[]=" do + it "should have access to it's property" do + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :extra => true } ) + expect( link[:extra] ).to be_true + end + + it "should be writable to non mandatory key" do + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :extra => true } ) + link[:extra] = false + expect( link[:extra] ).to be_false + end + + it "should raise exception if attempt to write to non mandatory key" do + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) + expect { + link[:from_portno] = 55 + }.to raise_error(ArgumentError) + end + end + + describe "delete" do + it "should remove property value" do + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :extra => true } ) + link.delete(:extra) + expect( link.property.has_key?(:extra) ).to be_false + end + + it "should ignore mandatory key" do + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :extra => true } ) + link.delete(:from_dpid) + expect( link[:from_dpid] ).to eq(0x1234) + end + end + + describe "update" do + it "should update property by given hash" do + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :extra => true } ) + link.update( {:extra => false, :new => 42}) + expect( link[:extra] ).to be_false + expect( link[:new] ).to eq(42) + end + + it "should ignore update to mandatory key" do + link = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :extra => true } ) + link.update( {:extra => false, :from_dpid => 42}) + expect( link[:extra] ).to be_false + expect( link[:from_dpid] ).to eq(0x1234) + end + end + end + + describe Map do + it "should initialize it's members to empty" do + c = Map.new + expect( c.switches.empty? ).to be_true + expect( c.links.empty? ).to be_true + end + + it "should have switch manipulation methods" do + c = Map.new + c.add_switch Switch.new( {:dpid => 0x1234 } ) + expect( c.switches.empty? ).to be_false + + expect( c.lookup_switch_by_dpid( 0x1234 ) ).not_to be_nil + + c.add_switch Switch.new( {:dpid => 0x5678 } ) + expect( c.switches[ 0x5678 ] ).not_to be_nil + + c.del_switch Switch.new( {:dpid => 0x5678 } ) + c.del_switch_by_dpid 0x1234 + expect( c.switches.empty? ).to be_true + end + + describe "link manipulation methods" do + before do + @c = Map.new + @c.add_switch Switch.new( {:dpid => 0x1234 } ) + @c.add_switch Switch.new( {:dpid => 0x5678 } ) + end + + it "should have add_link" do + @c.add_link Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) + expect( @c.links.empty? ).to be_false + end + + it "should have lookup_link_by_hash" do + @c.add_link Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) + expect( @c.lookup_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) ).not_to be_nil + end + + it "should have del_link(Link)" do + @c.add_link Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) + @c.del_link Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) + expect( @c.lookup_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) ).to be_nil + end + + it "should have del_link(Hash)" do + @c.add_link Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) + @c.del_link( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) + expect( @c.lookup_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) ).to be_nil + end + + it "should have del_link_by_key" do + @c.add_link Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) + @c.del_link_by_key [0x1234, 42, 0x5678, 72] + expect( @c.lookup_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) ).to be_nil + end + end + + describe "Switch events" do + it "should add Switch on switch up event" do + c = Map.new + c.update_switch( {:dpid => 0x1234, :up => true } ) + expect( c.switches ).to include(0x1234) + + expect( c.switches[ 0x1234 ].up? ).to be_true + expect( c.switches[ 0x1234 ][:up] ).to be_true + end + + it "should delete Switch on switch up event" do + c = Map.new + c.add_switch Switch.new( {:dpid => 0x1234, :up => true } ) + c.add_switch Switch.new( {:dpid => 0x5678, :up => true } ) + + c.update_switch( {:dpid => 0x1234, :up => false } ) + expect( c.switches ).not_to include(0x1234) + expect( c.switches ).to include(0x5678) + end + + it "should update Switch on existing switch's up event" do + c = Map.new + c.add_switch Switch.new( {:dpid => 0x1234, :up => true, :extra => "Old Value", :no_change => true } ) + c.update_switch( {:dpid => 0x1234, :up => true, :extra => "New Value" } ) + expect( c.switches ).to include(0x1234) + expect( c.switches[ 0x1234 ].up? ).to be_true + expect( c.switches[ 0x1234 ][:up] ).to be_true + expect( c.switches[ 0x1234 ][:extra] ).to match("New Value") + expect( c.switches[ 0x1234 ][:no_change] ).to be_true + end + end + + describe "Link event" do + before do + @c = Map.new + @c.add_switch Switch.new( {:dpid => 0x1234, :up => true } ) + @c.add_switch Switch.new( {:dpid => 0x5678, :up => true } ) + end + + it "should add Link on link up event" do + @c.update_link( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => true, :unstable => false } ) + expect( @c.links[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil + expect( @c.links[ [0x1234, 42, 0x5678, 72] ].up? ).to be_true + expect( @c.links[ [0x1234, 42, 0x5678, 72] ].unstable? ).to be_false + expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:up] ).to be_true + expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:unstable] ).to be_false + + expect( @c.switches[0x1234].links_out[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil + expect( @c.switches[0x5678].links_in[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil + end + + it "should remove Link on link down event" do + @c.add_link Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) + expect( @c.links.empty? ).to be_false + + @c.update_link( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => false, :unstable => false } ) + expect( @c.links[ [0x1234, 42, 0x5678, 72] ] ).to be_nil + end + + it "should update Link on existing link event" do + @c.update_link( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => true, :unstable => false } ) + + @c.update_link( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => true, :unstable => true } ) + expect( @c.links[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil + expect( @c.links[ [0x1234, 42, 0x5678, 72] ].up? ).to be_true + expect( @c.links[ [0x1234, 42, 0x5678, 72] ].unstable? ).to be_true + expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:up] ).to be_true + expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:unstable] ).to be_true + + expect( @c.switches[0x1234].links_out[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil + expect( @c.switches[0x5678].links_in[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil + end + end + + describe "Link event recoverable error" do + before do + @c = Map.new + end + it "should add Switch and Link on link up event, when switch did not exist" do + @c.update_link( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => true, :unstable => false } ) + expect( @c.links[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil + expect( @c.links[ [0x1234, 42, 0x5678, 72] ].up? ).to be_true + expect( @c.links[ [0x1234, 42, 0x5678, 72] ].unstable? ).to be_false + expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:up] ).to be_true + expect( @c.links[ [0x1234, 42, 0x5678, 72] ][:unstable] ).to be_false + + expect( @c.switches[0x1234].links_out[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil + expect( @c.switches[0x5678].links_in[ [0x1234, 42, 0x5678, 72] ] ).not_to be_nil + end + end + + describe "Port event" do + before do + @c = Map.new + @c.add_switch Switch.new( {:dpid => 0x1234, :up => true } ) + @c.add_switch Switch.new( {:dpid => 0x5678, :up => true } ) + end + + it "should add Port on port up event" do + @c.update_port( { :dpid => 0x1234, :portno => 42, :name => "Port Name", :mac => "FF:FF:FF:FF:FF:FF", :external => false, :up => true } ) + expect( @c.switches[0x1234].ports[42].up? ).to be_true + expect( @c.switches[0x1234].ports[42].external? ).to be_false + expect( @c.switches[0x1234].ports[42].name ).to eq("Port Name") + expect( @c.switches[0x1234].ports[42].mac ).to eq("FF:FF:FF:FF:FF:FF") + end + + it "should delete Port on port down event" do + @c.update_port( { :dpid => 0x1234, :portno => 42, :name => "Port Name", :mac => "FF:FF:FF:FF:FF:FF", :external => false, :up => false } ) + expect( @c.switches[0x1234].ports ).not_to include(42) + end + + it "should update Port on existing port up event" do + @c.update_port( { :dpid => 0x1234, :portno => 42, :name => "Port Name", :mac => "FF:FF:FF:FF:FF:FF", :external => false, :up => true } ) + @c.update_port( { :dpid => 0x1234, :portno => 42, :name => "New Port Name", :external => false, :up => true } ) + expect( @c.switches[0x1234].ports[42].up? ).to be_true + expect( @c.switches[0x1234].ports[42].external? ).to be_false + expect( @c.switches[0x1234].ports[42].name ).to eq("New Port Name") + expect( @c.switches[0x1234].ports[42].mac ).to eq("FF:FF:FF:FF:FF:FF") + end + end + + describe "Port event recoverable error" do + before do + @c = Map.new + end + it "should add Switch and Port on port up event, when switch did not exist" do + @c.update_port( { :dpid => 0x1234, :portno => 42, :name => "Port Name", :mac => "FF:FF:FF:FF:FF:FF", :external => false, :up => true } ) + expect( @c.switches[0x1234].ports[42].up? ).to be_true + expect( @c.switches[0x1234].ports[42].external? ).to be_false + expect( @c.switches[0x1234].ports[42].name ).to eq("Port Name") + expect( @c.switches[0x1234].ports[42].mac ).to eq("FF:FF:FF:FF:FF:FF") + end + end + + it "should be serializable to human readable form by to_s" do + c = Map.new + + s = Switch.new( { :dpid => 0x1234, :up => true, :magic => 42 } ) + s.add_port Port.new( { :dpid => 0x1234, :portno => 42, :up => true, :not_used => 1 } ) + c.add_switch s + c.add_link Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :not_used => 1, :up => true } ) + c.add_link Link.new( {:from_dpid => 0xABCD, :from_portno => 102, :to_dpid => 0x1234, :to_portno => 42, :not_used => 1, :up => true } ) + expect( c.to_s ).to be == <<-EOS +Map: +Switch: 0x1234 - {magic:42, up:true} + Port: 0x1234:42 - {not_used:1, up:true} + Links_in + <= 0xabcd:102 + Links_out + => 0x5678:72 +Switch: 0xabcd - {up:true} + Links_out + => 0x1234:42 +Switch: 0x5678 - {up:true} + Links_in + <= 0x1234:42 +Link: (0xabcd:102)->(0x1234:42) - {not_used:1, up:true} +Link: (0x1234:42)->(0x5678:72) - {not_used:1, up:true} + EOS + end + end + + describe "Map management functions" do + before do + class DummyController + include Topology + end + @c = DummyController.new + end + + it "should respond to get_last_map" do + expect( @c ).to respond_to( :get_last_map ) + end + + it "should respond to get_map" do + expect( @c ).to respond_to( :get_map ) + end + + it "should respond to map_ready?" do + expect( @c ).to respond_to( :map_ready? ) + end + + it "should respond to map_up_to_date?" do + expect( @c ).to respond_to( :map_up_to_date? ) + end + + it "should issue all status request on rebuild request" do + + expect( @c.map_up_to_date? ).to be_false + + @c.should_receive(:get_all_switch_status).and_yield( [ {:dpid => 0x1234, :up => true } ] ) + @c.should_receive(:get_all_port_status).and_yield( [ {:dpid => 0x1234, :portno => 42, :up => true } ] ) + @c.should_receive(:get_all_link_status).and_yield( [ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => true } ] ) + + @c.should_receive(:map_ready).once + + @c.send_rebuild_map_request + + expect( @c.map_up_to_date? ).to be_true + end + + it "should update Map on Switch update event" do + @c.update_map_by_switch_hash( {:dpid => 0x1234, :up => true } ) + map = @c.get_last_map + expect( map.lookup_switch_by_dpid( 0x1234 ) ).not_to be_nil + + @c.update_map_by_switch_hash( {:dpid => 0x1234, :up => false } ) + map = @c.get_last_map + expect( map.lookup_switch_by_dpid( 0x1234 ) ).to be_nil + end + + it "should update Map on Port update event" do + @c.update_map_by_port_hash( {:dpid => 0x1234, :portno => 42, :up => true } ) + map = @c.get_last_map + expect( map.lookup_switch_by_dpid( 0x1234 ).ports[42] ).not_to be_nil + + @c.update_map_by_port_hash( {:dpid => 0x1234, :portno => 42, :up => false } ) + map = @c.get_last_map + expect( map.lookup_switch_by_dpid( 0x1234 ).ports[42] ).to be_nil + end + + it "should update Map on Link update event" do + @c.update_map_by_link_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => true } ) + map = @c.get_last_map + expect( map.lookup_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) ).not_to be_nil + + @c.update_map_by_link_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :up => false } ) + map = @c.get_last_map + expect( map.lookup_link_by_hash( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) ).to be_nil + end + + end +end + +### Local variables: +### mode: Ruby +### coding: utf-8-unix +### indent-tabs-mode: nil +### End: diff --git a/spec/trema/topology-port_spec.rb b/spec/trema/topology-port_spec.rb index 182681620..79d823a87 100644 --- a/spec/trema/topology-port_spec.rb +++ b/spec/trema/topology-port_spec.rb @@ -18,8 +18,6 @@ require File.join( File.dirname( __FILE__ ), "..", "spec_helper" ) require "trema" -require "trema/topology" -require "trema/topology/topology_cache" include Trema::Topology @@ -27,9 +25,9 @@ describe Trema::Topology, :nosudo => true do describe Port do - it "should be initialized like Hash" do + it "should be initialized with Hash" do expect { - port = Port[ {:dpid => 0x1234, :portno => 42 } ] + port = Port.new( {:dpid => 0x1234, :portno => 42 } ) }.not_to raise_error end @@ -41,70 +39,65 @@ it "should raise error if key :dpid missing" do expect { - port = Port[ {:data => 0x1234, :portno => 42 } ] + port = Port.new( {:data => 0x1234, :portno => 42 } ) }.to raise_error(ArgumentError) end it "should raise error if key :portno missing" do expect { - port = Port[ {:dpid => 0x1234, :number => 42 } ] + port = Port.new( {:dpid => 0x1234, :number => 42 } ) }.to raise_error(ArgumentError) end it "should have dpid accessor" do - port = Port[ {:dpid => 0x1234, :portno => 42 } ] + port = Port.new( {:dpid => 0x1234, :portno => 42 } ) expect( port.dpid ).to be == 0x1234 end it "should have portno accessor" do - port = Port[ {:dpid => 0x1234, :portno => 42 } ] + port = Port.new( {:dpid => 0x1234, :portno => 42 } ) expect( port.portno ).to be == 42 end it "has method up?" do - port = Port[ {:dpid => 0x1234, :portno => 42, :up => true } ] + port = Port.new( {:dpid => 0x1234, :portno => 42, :up => true } ) expect( port.up? ).to be_true - port = Port[ {:dpid => 0x1234, :portno => 42, :up => false } ] + port = Port.new( {:dpid => 0x1234, :portno => 42, :up => false } ) expect( port.up? ).to be_false - port = Port[ {:dpid => 0x1234, :portno => 42 } ] + port = Port.new( {:dpid => 0x1234, :portno => 42 } ) expect( port.up? ).to be_false end it "has method external?" do - port = Port[ {:dpid => 0x1234, :portno => 42, :external => TD_PORT_EXTERNAL } ] + port = Port.new( {:dpid => 0x1234, :portno => 42, :external => true } ) expect( port.external? ).to be_true - port = Port[ {:dpid => 0x1234, :portno => 42, :external => TD_PORT_INACTIVE } ] + port = Port.new( {:dpid => 0x1234, :portno => 42, :external => false } ) expect( port.external? ).to be_false - port = Port[ {:dpid => 0x1234, :portno => 42 } ] + port = Port.new( {:dpid => 0x1234, :portno => 42 } ) expect( port.external? ).to be_false end it "has method name" do - port = Port[ {:dpid => 0x1234, :portno => 42, :name => "Port name" } ] + port = Port.new( {:dpid => 0x1234, :portno => 42, :name => "Port name" } ) expect( port.name ).to eq( "Port name" ) end it "has method mac" do - port = Port[ {:dpid => 0x1234, :portno => 42, :mac => "08:00:27:00:34:B7" } ] + port = Port.new( {:dpid => 0x1234, :portno => 42, :mac => "08:00:27:00:34:B7" } ) expect( port.mac ).to eq( "08:00:27:00:34:B7" ) end it "should have method key" do - port = Port[ {:dpid => 0x1234, :portno => 42 } ] + port = Port.new( {:dpid => 0x1234, :portno => 42 } ) expect( port.key ).to be == [0x1234,42] end - it "should have method key_str" do - port = Port[ {:dpid => 0x1234, :portno => 42 } ] - expect( port.key_str ).to match("1234-42") - end - it "should be serializable to human readable form by to_s" do - port = Port[ {:dpid => 0x1234, :portno => 42, :up => true, :not_used => 1 } ] + port = Port.new( {:dpid => 0x1234, :portno => 42, :up => true, :not_used => 1 } ) expect( port.to_s ).to be == "Port: 0x1234:42 - {not_used:1, up:true}" end end diff --git a/spec/trema/topology-switch_spec.rb b/spec/trema/topology-switch_spec.rb index a0974de51..08c4e4d55 100644 --- a/spec/trema/topology-switch_spec.rb +++ b/spec/trema/topology-switch_spec.rb @@ -18,8 +18,6 @@ require File.join( File.dirname( __FILE__ ), "..", "spec_helper" ) require "trema" -require "trema/topology" -require "trema/topology/topology_cache" include Trema::Topology @@ -27,106 +25,103 @@ describe Trema::Topology, :nosudo => true do describe Switch do - it "should be initialized like Hash" do - expect { - s = Switch[ {:dpid => 0x1234 } ] - }.not_to raise_error - end - - it "should raise error if empty instance is being created" do - expect { - s = Switch.new - }.to raise_error() - end - - it "should raise error if key :dpid missing" do - expect { - s = Switch[ { :portno => 42 } ] - }.to raise_error(ArgumentError) + describe "initialize" do + it "should default to up state" do + expect { + s = Switch.new( {:dpid => 0x1234 } ) + expect( s.up? ).to be_true + }.not_to raise_error + end + + it "should raise error if empty instance is being created" do + expect { + s = Switch.new + }.to raise_error() + end + + it "should raise error if key :dpid missing" do + expect { + s = Switch.new( { :up => false } ) + }.to raise_error(ArgumentError) + end + + it "should be down if Hash has :up => false" do + s = Switch.new( { :dpid => 0x1234, :up => false } ) + expect( s.up? ).to be_false + end end it "should have dpid accessor" do - s = Switch[ { :dpid => 0x1234, :magic => 42 } ] + s = Switch.new( { :dpid => 0x1234, :magic => 42 } ) expect( s.dpid ).to be == 0x1234 end - it "has method up?" do - s = Switch[ { :dpid => 0x1234, :magic => 42, :up => true } ] + it "should have method up?" do + s = Switch.new( { :dpid => 0x1234, :up => true } ) expect( s.up? ).to be_true - s = Switch[ { :dpid => 0x1234, :magic => 42, :up => false } ] + s = Switch.new( { :dpid => 0x1234, :up => false } ) expect( s.up? ).to be_false - s = Switch[ { :dpid => 0x1234, :magic => 42 } ] - expect( s.up? ).to be_false + s = Switch.new( { :dpid => 0x1234 } ) + expect( s.up? ).to be_true end it "should have method key" do - s = Switch[ { :dpid => 0x1234, :magic => 42 } ] + s = Switch.new( { :dpid => 0x1234, :magic => 42 } ) expect( s.key ).to be == 0x1234 end - it "should have method key_str" do - s = Switch[ { :dpid => 0x1234, :magic => 42 } ] - expect( s.key_str ).to match("1234") - end - - it "should have port manipulation methods" do - s = Switch[ { :dpid => 0x1234, :magic => 42 } ] + s = Switch.new( { :dpid => 0x1234, :magic => 42 } ) - s.add_port Port[ { :dpid => 0x1234, :portno => 1 } ] + s.add_port Port.new( { :dpid => 0x1234, :portno => 1 } ) expect( s.ports[1] ).not_to be_nil - s.add_port_by_portno 2 - expect( s.ports[2] ).not_to be_nil - - s.update_port_by_hash( { :dpid => 0x1234, :portno => 3, :up => true } ) + s.update_port( { :dpid => 0x1234, :portno => 3, :up => true } ) expect( s.ports[3] ).not_to be_nil - s.del_port Port[ { :dpid => 0x1234, :portno => 2 } ] + s.delete_port Port.new( { :dpid => 0x1234, :portno => 2 } ) expect( s.ports[2] ).to be_nil - s.del_port_by_portno 3 - expect( s.ports[3] ).to be_nil - - s.update_port_by_hash( { :dpid => 0x1234, :portno => 1, :up => false } ) + s.update_port( { :dpid => 0x1234, :portno => 1, :up => false } ) expect( s.ports[1] ).to be_nil end it "should have link manipulation methods" do - s1 = Switch[ { :dpid => 0x1234, :magic => 42 } ] - s2 = Switch[ { :dpid => 0x5678, :magic => 42 } ] + s1 = Switch.new( { :dpid => 0x1234, :magic => 42 } ) + s2 = Switch.new( { :dpid => 0x5678, :magic => 42 } ) - l1 = Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ] - l2 = Link[ {:from_dpid => 0x5678, :from_portno => 72, :to_dpid => 0x1234, :to_portno => 42 } ] + l1 = Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) + l2 = Link.new( {:from_dpid => 0x5678, :from_portno => 72, :to_dpid => 0x1234, :to_portno => 42 } ) - s1.add_outbound_link l1 + s1.add_link l1 expect( s1.links_out.empty? ).not_to be_true - s2.add_inbound_link l1 + s2.add_link l1 expect( s2.links_in.empty? ).not_to be_true - s1.add_inbound_link l2 + s1.add_link l2 expect( s1.links_in.empty? ).not_to be_true - s2.add_outbound_link l2 + s2.add_link l2 expect( s2.links_out.empty? ).not_to be_true - s1.del_inbound_link l2 - s1.del_outbound_link l1 + s1.delete_link l2 expect( s1.links_in.empty? ).to be_true + #s1.delete_link l1 + s1.delete_link( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72 } ) expect( s1.links_out.empty? ).to be_true - s2.del_link_by_key l1.key - s2.del_link_by_key l2.key + s2.delete_link l1.key expect( s2.links_in.empty? ).to be_true + s2.delete_link l2.key expect( s2.links_out.empty? ).to be_true end it "should be serializable to human readable form by to_s" do - s = Switch[ { :dpid => 0x1234, :up => true, :magic => 42 } ] - s.add_port Port[ { :dpid => 0x1234, :portno => 42, :up => true, :not_used => 1 } ] - s.add_outbound_link Link[ {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :not_used => 1 } ] - s.add_inbound_link Link[ {:from_dpid => 0xABCD, :from_portno => 102, :to_dpid => 0x1234, :to_portno => 42, :not_used => 1 } ] + s = Switch.new( { :dpid => 0x1234, :up => true, :magic => 42 } ) + s.add_port Port.new( { :dpid => 0x1234, :portno => 42, :up => true, :not_used => 1 } ) + s.add_link Link.new( {:from_dpid => 0x1234, :from_portno => 42, :to_dpid => 0x5678, :to_portno => 72, :not_used => 1 } ) + s.add_link Link.new( {:from_dpid => 0xABCD, :from_portno => 102, :to_dpid => 0x1234, :to_portno => 42, :not_used => 1 } ) expect( s.to_s ).to be == <<-EOS Switch: 0x1234 - {magic:42, up:true} Port: 0x1234:42 - {not_used:1, up:true} diff --git a/spec/trema/topology_spec.rb b/spec/trema/topology_spec.rb deleted file mode 100644 index 3a9ead7e0..000000000 --- a/spec/trema/topology_spec.rb +++ /dev/null @@ -1,72 +0,0 @@ -# -# Copyright (C) 2008-2013 NEC Corporation -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2, as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# - -require File.join( File.dirname( __FILE__ ), "..", "spec_helper" ) -require "trema" -require "trema/topology" -require "trema/topology/topology_cache" - - -include Trema::Topology - -describe Trema::Topology, :nosudo => true do - - describe "Constants from enum topology_switch_status_type" do - it "has TD_SWITCH_DOWN" do - expect(Topology::TD_SWITCH_DOWN).to eq(0) - end - it "has TD_SWITCH_UP" do - expect(Topology::TD_SWITCH_UP).to eq(1) - end - end - - describe "Constants from enum topology_port_status_type" do - it "has TD_PORT_DOWN" do - expect(Topology::TD_PORT_DOWN).to eq(0) - end - it "has TD_PORT_UP" do - expect(Topology::TD_PORT_UP).to eq(1) - end - end - - describe "Constants from enum topology_port_external_type" do - it "has TD_PORT_INACTIVE" do - expect(Topology::TD_PORT_INACTIVE).to eq(0) - end - it "has TD_PORT_EXTERNAL" do - expect(Topology::TD_PORT_EXTERNAL).to eq(1) - end - end - - describe "Constants from enum topology_link_status_type" do - it "has TD_LINK_DOWN" do - expect(Topology::TD_LINK_DOWN).to eq(0) - end - it "has TD_LINK_UP" do - expect(Topology::TD_LINK_UP).to eq(1) - end - it "has TD_LINK_UNSTABLE" do - expect(Topology::TD_LINK_UNSTABLE).to eq(2) - end - end -end - -### Local variables: -### mode: Ruby -### coding: utf-8-unix -### indent-tabs-mode: nil -### End: diff --git a/src/examples/topology/change-history.rb b/src/examples/topology/change-history.rb index cd56a8c5b..a1eea5d98 100644 --- a/src/examples/topology/change-history.rb +++ b/src/examples/topology/change-history.rb @@ -1,20 +1,36 @@ -require "trema/topology" -require "trema/topology/topology_cache" +# +# Copyright (C) 2008-2013 NEC Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +require "trema/topology/map_api" # Controller which keeps printing Switch and Link change event as graphviz dot file. # It will shutdown itself if there was no Switch or Link event for 60 seconds. # class ChangeHistory < Controller - include Topology + include TopologyMap # TODO Add option to specify idle check interval. periodic_timer_event :shutdown_on_idle, 60 oneshot_timer_event :on_start, 0 def on_start - enable_topology_discovery - send_rebuild_cache_request + send_enable_topology_discovery + send_rebuild_map_request # TODO Add option to specify file from command line. @dotfile = File.open( "change-history.dot", "w" ) @@ -28,7 +44,7 @@ def on_start end - def cache_ready g + def map_ready g @generation = 0 # Initially color everything as updated (green) @@ -43,30 +59,41 @@ def cache_ready g end - def switch_status_updated sw_attr + def switch_status_up dpid if not instance_variable_defined?(:@generation) then return nil end @generation += 1 - sw_attr[:generation] = @generation - - g = get_cache + g = get_last_map remove_old_colors( g, @generation ) - if sw_attr[:up] then - sw_attr[:color] = "green" - update_cache_by_switch_hash sw_attr - sublabel = "0x#{sw_attr[:dpid].to_s(16)} up" - puts_dotfile to_dot_subgraph( g, @generation, sublabel ) - else - g.switches[ sw_attr[:dpid] ][:color] = "red" if g.switches[ sw_attr[:dpid] ] - sublabel = "0x#{sw_attr[:dpid].to_s(16)} down" - puts_dotfile to_dot_subgraph( g, @generation, sublabel ) - # update to new state. (switch instance will be removed) - update_cache_by_switch_hash sw_attr + sw = Switch.new( dpid ) + sw[:generation] = @generation + sw[:color] = "green" + update_map_by_switch_hash sw + sublabel = "0x#{dpid.to_s(16)} up" + puts_dotfile to_dot_subgraph( g, @generation, sublabel ) + end + + + def switch_status_down dpid + if not instance_variable_defined?(:@generation) then + return nil end + @generation += 1 + g = get_last_map + remove_old_colors( g, @generation ) + + sw = Switch.new( dpid ) + sw.up = false + sw[:generation] = @generation + g.switches[ dpid ][:color] = "red" if g.switches[ dpid ] + sublabel = "0x#{dpid.to_s(16)} down" + puts_dotfile to_dot_subgraph( g, @generation, sublabel ) + # update to new state. (switch instance will be removed) + update_map_by_switch_hash sw end @@ -74,17 +101,17 @@ def link_status_updated link_attr if not instance_variable_defined?(:@generation) then return nil end - link = Link[link_attr] + link = Link.new( link_attr ) @generation += 1 link[:generation] = @generation - g = get_cache + g = get_last_map remove_old_colors( g, @generation ) if link.up? then link[:color] = "green" - update_cache_by_link_hash link + update_map_by_link_hash link sublabel = "(0x#{link.from_dpid.to_s(16)} -> 0x#{link.to_dpid.to_s(16)}) up" puts_dotfile to_dot_subgraph( g, @generation, sublabel ) else @@ -92,7 +119,7 @@ def link_status_updated link_attr sublabel = "(0x#{link.from_dpid.to_s(16)} -> 0x#{link.to_dpid.to_s(16)}) down" puts_dotfile to_dot_subgraph( g, @generation, sublabel ) # update to new state. (link instance will be removed) - update_cache_by_link_hash link + update_map_by_link_hash link end end @@ -125,7 +152,7 @@ def to_dot_subgraph( g, generation, sublabel="" ) # // switches g.switches.each do | _, sw | dot_attrs = %Q(label="0x#{sw.dpid.to_s(16)}") - if sw.member?(:color) then + if sw.property.has_key?(:color) then dot_attrs << ", " unless dot_attrs.empty? dot_attrs << %Q(color="#{sw[:color]}") end @@ -134,7 +161,7 @@ def to_dot_subgraph( g, generation, sublabel="" ) # // links g.links.each do | _, lnk | dot_attrs = "" - if lnk.member?(:color) then + if lnk.property.has_key?(:color) then dot_attrs << ", " unless dot_attrs.empty? dot_attrs << %Q(color="#{lnk[:color]}") end @@ -158,4 +185,3 @@ def puts_dotfile s end end - diff --git a/src/examples/topology/show-switch-status.rb b/src/examples/topology/show-switch-status.rb index cd9d2b61e..2ec01dd84 100644 --- a/src/examples/topology/show-switch-status.rb +++ b/src/examples/topology/show-switch-status.rb @@ -1,4 +1,19 @@ -require "trema/topology" +# +# Copyright (C) 2008-2013 NEC Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# class ShowSwitchStatus < Controller @@ -8,13 +23,13 @@ class ShowSwitchStatus < Controller oneshot_timer_event :timed_out, 15 oneshot_timer_event :on_start, 0 def on_start - send_all_switch_status_request + get_all_switch_status end - def all_switch_status_reply sw_status + def all_switch_status sw_status puts "Switch status" sw_status.each do | sw_hash | - sw = Topology::Switch[ sw_hash ] + sw = Topology::Switch.new( sw_hash ) status_str = "unknown" if sw.up? then @@ -25,13 +40,13 @@ def all_switch_status_reply sw_status puts " dpid : 0x#{sw.dpid.to_s(16)}, status : #{status_str}" end - send_all_port_status_request + get_all_port_status end - def all_port_status_reply port_status + def all_port_status port_status puts "Port status" port_status.each do | port_hash | - port = Port[ port_hash ] + port = Port.new( port_hash ) status_str = "unknown" if port.up? then status_str = "up" diff --git a/src/examples/topology/show-topology.rb b/src/examples/topology/show-topology.rb index da0220aaa..fd88b6551 100644 --- a/src/examples/topology/show-topology.rb +++ b/src/examples/topology/show-topology.rb @@ -1,32 +1,48 @@ -require "trema/topology" +# +# Copyright (C) 2008-2013 NEC Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# class ShowTopology < Controller include Topology + EXISTS = nil oneshot_timer_event :timed_out, 10 oneshot_timer_event :on_start, 0 def on_start - send_all_link_status_request + get_all_link_status end - def all_link_status_reply link_status + def all_link_status link_status dpids = Hash.new links = Hash.new debug "topology: entries #{link_status.size}" link_status.each do | link_hash | - link = Link[link_hash] + link = Link.new( link_hash ) if link.up? then - dpids[link.from_dpid] = nil - dpids[link.to_dpid] = nil + dpids[link.from_dpid] = EXISTS + dpids[link.to_dpid] = EXISTS dpid_pair = [link.from_dpid, link.to_dpid] - links[ [dpid_pair.max, dpid_pair.min] ] = nil + links[ [dpid_pair.max, dpid_pair.min] ] = EXISTS else debug "link down" end