diff --git a/features/examples/topology.change-history.feature b/features/examples/topology.change-history.feature new file mode 100644 index 000000000..9f1f33225 --- /dev/null +++ b/features/examples/topology.change-history.feature @@ -0,0 +1,38 @@ +Feature: Topology Ruby API example + + The change-history.rb example ([trema]/src/examples/topology/change-history.rb) + is a sample controller which uses Topology Ruby API. + + @slow_process + Scenario: Run the Ruby example + Given a file named "change-history.conf" with: + """ + 1.upto( 3 ).each do | sw | + vswitch { dpid sw } + 1.upto( sw - 1 ).each do | peer | + link "%#x" % sw, "%#x" % peer + end + end + + run { + path "../../objects/topology/topology" + } + + run { + path "../../objects/examples/dumper/dumper" + } + + event :port_status => "topology", :packet_in => "filter", :state_notify => "topology" + filter :lldp => "topology", :packet_in => "dumper" + """ + When I run `../../trema run ../../src/examples/topology/change-history.rb -c change-history.conf -d` + Then wait until "topology" is up + Then *** sleep 32 *** + + Then the file "change-history.dot" should match /.*graph \[label="Gen \d+\\n\(0x3 -> 0x1\) up"\];.*/ + Then the file "change-history.dot" should match /.*graph \[label="Gen \d+\\n\(0x3 -> 0x2\) up"\];.*/ + Then the file "change-history.dot" should match /.*graph \[label="Gen \d+\\n\(0x2 -> 0x1\) up"\];.*/ + Then the file "change-history.dot" should match /.*graph \[label="Gen \d+\\n\(0x2 -> 0x3\) up"\];.*/ + Then the file "change-history.dot" should match /.*graph \[label="Gen \d+\\n\(0x1 -> 0x2\) up"\];.*/ + Then the file "change-history.dot" should match /.*graph \[label="Gen \d+\\n\(0x1 -> 0x3\) up"\];.*/ + \ No newline at end of file diff --git a/features/examples/topology.show_switch_status.feature b/features/examples/topology.show_switch_status.feature index b1ae61122..26e5be95b 100644 --- a/features/examples/topology.show_switch_status.feature +++ b/features/examples/topology.show_switch_status.feature @@ -1,6 +1,7 @@ Feature: show_switch_status example. show_switch_status is a simple usage example of topology C API. + show-switch-status.rb is a simple usage example of topology Ruby API. show_switch_status command will query for all the switch and port information that the topology daemon hold and print them to standard output. @@ -44,3 +45,39 @@ Feature: show_switch_status example. """ And the output should match / dpid : 0xe0, port : 1\(.+\), status : up, external : (true|false)/ And the output should match / dpid : 0xe0, port : 2\(.+\), status : up, external : (true|false)/ + + @slow_process + Scenario: [Ruby API] Show switch and port information obtained from topology. + Given a file named "show_switch_status.conf" with: + """ + vswitch("topology1") { datapath_id "0xe0" } + vhost ("host1") { + ip "192.168.0.1" + netmask "255.255.0.0" + mac "00:00:00:01:00:01" + } + + vhost ("host2") { + ip "192.168.0.2" + netmask "255.255.0.0" + mac "00:00:00:01:00:02" + } + + link "topology1", "host1" + link "topology1", "host2" + """ + And I run `trema run ../repeater_hub/repeater-hub.rb -c show_switch_status.conf -d` + And I run `trema run "../../../objects/topology/topology -d"` + And *** sleep 4 *** + When I run `trema run ./show-switch-status.rb` + Then the output should contain: + """ + Switch status + dpid : 0xe0, status : up + """ + And the output should contain: + """ + Port status + """ + And the output should match / dpid : 0xe0, port : 1\(.+\), status : up, external : (true|false)/ + And the output should match / dpid : 0xe0, port : 2\(.+\), status : up, external : (true|false)/ diff --git a/features/examples/topology.show_topology.feature b/features/examples/topology.show_topology.feature index 4791bbb29..77277d9b4 100644 --- a/features/examples/topology.show_topology.feature +++ b/features/examples/topology.show_topology.feature @@ -1,6 +1,7 @@ Feature: show_topology example. show_topology is a simple usage example of topology C API. + show-topology.rb is a simple usage example of topology Ruby API. show_topology command will query for all the link information that the topology daemon hold and print them in trema network DSL style. @@ -157,3 +158,73 @@ Feature: show_topology example. """ link "0x4", "0x1" """ + + @slow_process + Scenario: [Ruby API] Show discovered link topology. (Directly start topology daemon) + Given a file named "show_topology.conf" with: + """ + vswitch("topology1") { datapath_id "0x1" } + vswitch("topology2") { datapath_id "0x2" } + vswitch("topology3") { datapath_id "0x3" } + vswitch("topology4") { datapath_id "0x4" } + + link "topology1", "topology2" + link "topology1", "topology3" + link "topology1", "topology4" + link "topology2", "topology3" + link "topology2", "topology4" + link "topology3", "topology4" + """ + And I run `trema run ../repeater_hub/repeater-hub.rb -c show_topology.conf -d` + And I run `trema run "../../../objects/topology/topology -d --always_run_discovery"` + And *** sleep 4 *** + When I run `trema run ./show-topology.rb` + Then the output should contain: + """ + vswitch { + datapath_id "0x2" + } + """ + And the output should contain: + """ + vswitch { + datapath_id "0x3" + } + """ + And the output should contain: + """ + vswitch { + datapath_id "0x1" + } + """ + And the output should contain: + """ + vswitch { + datapath_id "0x4" + } + """ + And the output should contain: + """ + link "0x2", "0x1" + """ + And the output should contain: + """ + link "0x3", "0x2" + """ + And the output should contain: + """ + link "0x3", "0x1" + """ + And the output should contain: + """ + link "0x4", "0x2" + """ + And the output should contain: + """ + link "0x4", "0x3" + """ + And the output should contain: + """ + link "0x4", "0x1" + """ + diff --git a/src/examples/topology/README.md b/src/examples/topology/README.md index 94a9e6c04..6a7482558 100644 --- a/src/examples/topology/README.md +++ b/src/examples/topology/README.md @@ -6,7 +6,7 @@ This directory includes sample application using libtopology. - `show_topology` is a command line application, which retrieves link information from Topology daemon and print them in trema network DSL style. -- `show_swith_status` is a command line application, which retrieves +- `show_switch_status` is a command line application, which retrieves switch and port information from Topology daemon and print them to stdout. - `enable_discovery` is a command line application, which enables @@ -27,8 +27,8 @@ This directory includes sample application using libtopology. +----------+ packet out(LLDP) +-----------+ -How to run ----------- +How to run (show_topology/show_switch_status) +--------------------------------------------- 1. Change to trema directory and build trema, if you haven't done so already. @@ -51,7 +51,7 @@ How to run $ ./trema run ./objects/examples/topology/enable_discovery -4. Run show_topology/show_switch_status from trema run +4. (C version) Run show_topology/show_switch_status from trema run $ ./trema run objects/examples/topology/show_topology $ ./trema run objects/examples/topology/show_switch_status @@ -61,6 +61,63 @@ How to run $ env TREMA_HOME=`pwd` src/examples/topology/show_topology $ env TREMA_HOME=`pwd` src/examples/topology/show_switch_status +4. (Ruby version) Run show-topology.rb/show-switch-status.rb from trema run + + $ ./trema run src/examples/topology/show-topology.rb + $ ./trema run src/examples/topology/show-switch-status.rb + + +How to run (change-history.rb) +------------------------------ + +1. Change to trema directory and build trema, if you haven't done so already. + + $ cd $TREMA_HOME + $ ./build.rb + +2. Start topology daemon. + + $ ./trema run -c src/examples/topology/change-history.conf & + +3. Run change-history.rb from trema run + + $ ./trema run src/examples/topology/change-history.rb + +4. Controller will output will be written to change-history.dot in current directory. + + (In another terminal) + $ tail -F change-history.dot + digraph { + subgraph cluster0 { + graph [label="Gen 0\ninitial"]; + "0x1_0" [label="0x1", color="green"]; + "0x2_0" [label="0x2", color="green"]; + "0x3_0" [label="0x3", color="green"]; + "0x1_0" -> "0x2_0" [color="green"]; + "0x1_0" -> "0x3_0" [color="green"]; + "0x2_0" -> "0x3_0" [color="green"]; + "0x3_0" -> "0x2_0" [color="green"]; + "0x3_0" -> "0x1_0" [color="green"]; + "0x2_0" -> "0x1_0" [color="green"]; + +5. See how topology change is notified to dot file. + Example: stop switch with dpid 0x3 + + (In another terminal) + $ ./trema kill 0x3 + + Example: restart switch with dpid 0x3 + + $ ./trema up 0x3 + +6. Graphically see topology change history. + + $ dot -Tsvg -O change-history.dot + $ firefox change-history.dot.svg + + You may need to install graphviz package to use dot command. + + $ sudo apt-get install graphviz License & Terms --------------- diff --git a/src/examples/topology/change-history.conf b/src/examples/topology/change-history.conf new file mode 100644 index 000000000..e838c90f1 --- /dev/null +++ b/src/examples/topology/change-history.conf @@ -0,0 +1,20 @@ + +1.upto( 3 ).each do | sw | + vswitch { dpid sw } + 1.upto( sw - 1 ).each do | peer | + link "%#x" % sw, "%#x" % peer + end +end + +run { + path "./objects/topology/topology" +# options "-l debug" +} + +run { + path "./objects/examples/dumper/dumper" +} + +event :port_status => "topology", :packet_in => "filter", :state_notify => "topology" +filter :lldp => "topology", :packet_in => "dumper" + diff --git a/src/examples/topology/change-history.rb b/src/examples/topology/change-history.rb new file mode 100644 index 000000000..cd56a8c5b --- /dev/null +++ b/src/examples/topology/change-history.rb @@ -0,0 +1,161 @@ +require "trema/topology" +require "trema/topology/topology_cache" + + +# 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 + + # 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 + + # TODO Add option to specify file from command line. + @dotfile = File.open( "change-history.dot", "w" ) + + # digraph { + puts_dotfile "digraph {" + at_exit do + # } for digraph + puts_dotfile "}" + end + end + + + def cache_ready g + @generation = 0 + + # Initially color everything as updated (green) + g.switches.each do | _, sw | + sw[:color] = "green" + end + g.links.each do | _, lnk | + lnk[:color] = "green" + end + + puts_dotfile to_dot_subgraph( g, @generation, "initial" ) + end + + + def switch_status_updated sw_attr + if not instance_variable_defined?(:@generation) then + return nil + end + + @generation += 1 + sw_attr[:generation] = @generation + + g = get_cache + 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 + end + + end + + + def link_status_updated link_attr + if not instance_variable_defined?(:@generation) then + return nil + end + link = Link[link_attr] + + @generation += 1 + link[:generation] = @generation + + g = get_cache + remove_old_colors( g, @generation ) + + if link.up? then + link[:color] = "green" + update_cache_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 + g.links[ link.key ][:color] = "red" if g.links[ link.key ] + 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 + end + end + + + def shutdown_on_idle + @last_generation ||= -1 + if @last_generation == @generation then + shutdown! + else + @last_generation = @generation + end + end + + + def remove_old_colors( g, generation ) + g.switches.each do | _, sw | + sw[:generation] ||= 0 + sw.delete(:color) if sw[:generation] != generation + end + g.links.each do | _, lnk | + lnk[:generation] ||= 0 + lnk.delete(:color) if lnk[:generation] != generation + end + end + + + def to_dot_subgraph( g, generation, sublabel="" ) + s = " subgraph cluster#{generation} {\n" + s << %Q( graph [label="Gen #{generation}\\n#{sublabel}"];\n) + # // switches + g.switches.each do | _, sw | + dot_attrs = %Q(label="0x#{sw.dpid.to_s(16)}") + if sw.member?(:color) then + dot_attrs << ", " unless dot_attrs.empty? + dot_attrs << %Q(color="#{sw[:color]}") + end + s << %Q( "0x#{sw.dpid.to_s(16)}_#{generation}" [#{dot_attrs}];\n) + end + # // links + g.links.each do | _, lnk | + dot_attrs = "" + if lnk.member?(:color) then + dot_attrs << ", " unless dot_attrs.empty? + dot_attrs << %Q(color="#{lnk[:color]}") + end + s << %Q( "0x#{lnk.from_dpid.to_s(16)}_#{generation}" -> "0x#{lnk.to_dpid.to_s(16)}_#{generation}" [#{dot_attrs}];\n) + end + # } for subgraph + s << " }\n" + return s + end + + + def puts_dotfile s + begin + @dotfile.puts s + @dotfile.flush + rescue + error "Failed to write dotfile (#{$!.inspect})\n\t#{$!.backtrace.join("\n\t") }" + STDERR.puts "Failed to write dotfile." + shutdown! + end + end +end + + diff --git a/src/examples/topology/show-switch-status.rb b/src/examples/topology/show-switch-status.rb new file mode 100644 index 000000000..cd9d2b61e --- /dev/null +++ b/src/examples/topology/show-switch-status.rb @@ -0,0 +1,53 @@ +require "trema/topology" + + +class ShowSwitchStatus < Controller + include Topology + + + oneshot_timer_event :timed_out, 15 + oneshot_timer_event :on_start, 0 + def on_start + send_all_switch_status_request + end + + def all_switch_status_reply sw_status + puts "Switch status" + sw_status.each do | sw_hash | + sw = Topology::Switch[ sw_hash ] + + status_str = "unknown" + if sw.up? then + status_str = "up" + else + status_str = "down" + end + puts " dpid : 0x#{sw.dpid.to_s(16)}, status : #{status_str}" + end + + send_all_port_status_request + end + + def all_port_status_reply port_status + puts "Port status" + port_status.each do | port_hash | + port = Port[ port_hash ] + status_str = "unknown" + if port.up? then + status_str = "up" + else + status_str = "down" + end + + puts " dpid : 0x#{port.dpid.to_s(16)}, port : #{port.portno.to_s}(#{port.name}), status : #{status_str}, external : #{port.external?.to_s}" + end + + shutdown! + end + + + def timed_out + error "timed out." + shutdown! + end +end diff --git a/src/examples/topology/show-topology.rb b/src/examples/topology/show-topology.rb new file mode 100644 index 000000000..da0220aaa --- /dev/null +++ b/src/examples/topology/show-topology.rb @@ -0,0 +1,53 @@ +require "trema/topology" + + +class ShowTopology < Controller + include Topology + + + oneshot_timer_event :timed_out, 10 + oneshot_timer_event :on_start, 0 + def on_start + send_all_link_status_request + end + + + def all_link_status_reply link_status + dpids = Hash.new + links = Hash.new + + debug "topology: entries #{link_status.size}" + + link_status.each do | link_hash | + link = Link[link_hash] + if link.up? then + dpids[link.from_dpid] = nil + dpids[link.to_dpid] = nil + + dpid_pair = [link.from_dpid, link.to_dpid] + + links[ [dpid_pair.max, dpid_pair.min] ] = nil + else + debug "link down" + end + end + + dpids.keys.each do | dpid | + puts "vswitch {" + puts %Q( datapath_id "0x#{dpid.to_s(16)}") + puts "}\n\n" + end + + links.keys.each do | dpid0, dpid1 | + puts %Q(link "0x#{dpid0.to_s(16)}", "0x#{dpid1.to_s(16)}") + end + + shutdown! + end + + + def timed_out + error "timed out." + shutdown! + end +end