phut 0.7.7 → 0.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rubocop.yml +14 -1
  4. data/.travis.yml +2 -6
  5. data/Gemfile +30 -2
  6. data/Gemfile.lock +156 -0
  7. data/README.md +7 -2
  8. data/Rakefile +4 -3
  9. data/bin/phut +35 -83
  10. data/bin/vhost +28 -26
  11. data/features/{dsl.feature → dsl/error.feature} +8 -6
  12. data/features/{dsl_link.feature → dsl/link.feature} +11 -14
  13. data/features/dsl/netns.feature +115 -0
  14. data/features/dsl/vhost.feature +37 -0
  15. data/features/{dsl_vswitch.feature → dsl/vswitch.feature} +12 -12
  16. data/features/phut_run.feature +15 -0
  17. data/features/shell/vswitch#destroy.feature +10 -0
  18. data/features/shell/vswitch#ports.feature +36 -0
  19. data/features/shell/vswitch.all.feature +26 -0
  20. data/features/shell/vswitch.create.feature +30 -0
  21. data/features/shell/vswitch.destroy.feature +19 -0
  22. data/features/shell/vswitch.destroy_all.feature +18 -0
  23. data/features/step_definitions/link_steps.rb +5 -0
  24. data/features/step_definitions/netns_steps.rb +31 -0
  25. data/features/step_definitions/phut_steps.rb +5 -34
  26. data/features/step_definitions/vhost_steps.rb +5 -0
  27. data/features/step_definitions/vswitch_steps.rb +17 -0
  28. data/features/support/env.rb +3 -3
  29. data/features/support/hooks.rb +23 -15
  30. data/lib/phut.rb +3 -0
  31. data/lib/phut/finder.rb +19 -0
  32. data/lib/phut/link.rb +84 -0
  33. data/lib/phut/netns.rb +111 -22
  34. data/lib/phut/open_vswitch.rb +98 -96
  35. data/lib/phut/parser.rb +39 -8
  36. data/lib/phut/raw_socket.rb +4 -0
  37. data/lib/phut/route.rb +34 -0
  38. data/lib/phut/setting.rb +21 -4
  39. data/lib/phut/shell_runner.rb +13 -2
  40. data/lib/phut/syntax.rb +31 -14
  41. data/lib/phut/syntax/directive.rb +9 -1
  42. data/lib/phut/syntax/netns_directive.rb +13 -2
  43. data/lib/phut/syntax/vhost_directive.rb +2 -0
  44. data/lib/phut/syntax/vswitch_directive.rb +3 -1
  45. data/lib/phut/version.rb +3 -1
  46. data/lib/phut/veth.rb +68 -0
  47. data/lib/phut/vhost.rb +99 -58
  48. data/lib/phut/vhost_daemon.rb +53 -11
  49. data/lib/phut/vsctl.rb +125 -0
  50. data/lib/phut/vswitch.rb +10 -0
  51. data/phut.gemspec +9 -31
  52. data/tasks/cucumber.rake +5 -1
  53. data/tasks/flay.rake +2 -0
  54. data/tasks/flog.rake +3 -1
  55. data/tasks/gem.rake +2 -0
  56. data/tasks/minitest.rake +7 -0
  57. data/tasks/reek.rake +2 -0
  58. data/tasks/rubocop.rake +2 -0
  59. data/tasks/yard.rake +2 -0
  60. data/test/phut/link_test.rb +85 -0
  61. data/test/phut/netns_test.rb +58 -0
  62. data/test/phut/open_vswitch_test.rb +125 -0
  63. data/test/phut/veth_test.rb +48 -0
  64. data/test/phut/vhost_test.rb +56 -0
  65. metadata +41 -287
  66. data/.rspec +0 -3
  67. data/Guardfile +0 -29
  68. data/features/dsl_vhost.feature +0 -37
  69. data/features/phut_kill.feature +0 -27
  70. data/features/shell.feature +0 -39
  71. data/lib/phut/configuration.rb +0 -92
  72. data/lib/phut/null_logger.rb +0 -14
  73. data/lib/phut/virtual_link.rb +0 -109
  74. data/spec/phut/parser_spec.rb +0 -66
  75. data/spec/phut_spec.rb +0 -45
  76. data/spec/spec_helper.rb +0 -14
  77. data/tasks/LICENSE +0 -675
  78. data/tasks/relish.rake +0 -8
  79. data/tasks/rspec.rake +0 -8
@@ -1,36 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'phut/link'
1
4
  require 'phut/netns'
2
5
  require 'phut/syntax/netns_directive'
3
6
  require 'phut/syntax/vhost_directive'
4
7
  require 'phut/syntax/vswitch_directive'
5
8
  require 'phut/vhost'
6
- require 'phut/virtual_link'
7
9
 
8
10
  module Phut
9
11
  # DSL syntax definitions.
10
12
  class Syntax
11
- def initialize(config, logger)
12
- @config = config
13
- @logger = logger
13
+ def initialize(netns)
14
+ @netns = netns
14
15
  end
15
16
 
17
+ # rubocop:disable MethodLength
18
+ # rubocop:disable LineLength
16
19
  def vswitch(alias_name = nil, &block)
17
20
  attrs = VswitchDirective.new(alias_name, &block)
18
- OpenVswitch.create(attrs[:dpid], attrs[:port], attrs[:name], @logger)
21
+ openflow_version = case Pio::OpenFlow.version
22
+ when :OpenFlow10
23
+ 1.0
24
+ when :OpenFlow13
25
+ 1.3
26
+ else
27
+ raise "Unknown OpenFlow version: #{Pio::OpenFlow.version}"
28
+ end
29
+ Vswitch.create(dpid: attrs[:dpid],
30
+ name: attrs[:name],
31
+ tcp_port: attrs[:port],
32
+ openflow_version: openflow_version)
19
33
  end
34
+ # rubocop:enable MethodLength
35
+ # rubocop:enable LineLength
20
36
 
21
- def vhost(alias_name = nil, &block)
22
- attrs = VhostDirective.new(alias_name, &block)
23
- Vhost.create(attrs[:ip], attrs[:mac], attrs[:promisc], attrs[:name],
24
- @logger)
37
+ def vhost(name = nil, &block)
38
+ attrs = VhostDirective.new(name, &block)
39
+ Vhost.create(name: attrs[:name],
40
+ ip_address: attrs[:ip],
41
+ mac_address: attrs[:mac],
42
+ promisc: attrs[:promisc])
25
43
  end
26
44
 
27
- def netns(name, &block)
28
- attrs = NetnsDirective.new(name, &block)
29
- Netns.create(attrs, attrs[:name], @logger)
45
+ def link(name_a, name_b)
46
+ Link.create(name_a, name_b)
30
47
  end
31
48
 
32
- def link(name_a, name_b)
33
- VirtualLink.create(name_a, name_b, @logger)
49
+ def netns(name, &block)
50
+ @netns << NetnsDirective.new(name, &block)
34
51
  end
35
52
  end
36
53
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Phut
2
4
  class Syntax
3
5
  # Common DSL directive
@@ -9,8 +11,14 @@ module Phut
9
11
  end
10
12
 
11
13
  def [](key)
12
- @attributes.fetch(key)
14
+ @attributes[key]
15
+ end
16
+
17
+ # rubocop:disable MethodMissing
18
+ def method_missing(name)
19
+ @attributes.fetch name.to_sym
13
20
  end
21
+ # rubocop:enable MethodMissing
14
22
  end
15
23
  end
16
24
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'phut/syntax/directive'
2
4
 
3
5
  module Phut
@@ -7,8 +9,9 @@ module Phut
7
9
  attribute :netmask
8
10
 
9
11
  def initialize(alias_name, &block)
10
- @attributes = { name: alias_name }
11
- instance_eval(&block)
12
+ @attributes = { name: alias_name,
13
+ netmask: '255.255.255.255' }
14
+ instance_eval(&block) if block
12
15
  end
13
16
 
14
17
  def ip(value)
@@ -20,6 +23,14 @@ module Phut
20
23
  @attributes[:net] = options.fetch(:net)
21
24
  @attributes[:gateway] = options.fetch(:gateway)
22
25
  end
26
+
27
+ def mac(value)
28
+ @attributes[:mac_address] = value
29
+ end
30
+
31
+ def vlan(value)
32
+ @attributes[:vlan] = value
33
+ end
23
34
  end
24
35
  end
25
36
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'phut/syntax/directive'
2
4
 
3
5
  module Phut
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'phut/syntax/directive'
2
4
 
3
5
  module Phut
@@ -20,7 +22,7 @@ module Phut
20
22
  @attributes[:dpid] = dpid
21
23
  @attributes[:name] ||= format('%#x', @attributes[:dpid])
22
24
  end
23
- alias_method :datapath_id, :dpid
25
+ alias datapath_id dpid
24
26
  end
25
27
  end
26
28
  end
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Base module.
2
4
  module Phut
3
- VERSION = '0.7.7'
5
+ VERSION = '0.7.8'
4
6
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require 'ipaddr'
5
+ require 'phut/shell_runner'
6
+
7
+ module Phut
8
+ # Virtual eth device
9
+ class Veth
10
+ PREFIX = 'L'
11
+
12
+ extend ShellRunner
13
+
14
+ # rubocop:disable Metrics/AbcSize
15
+ def self.all
16
+ link_devices = sh('ip link show').split("\n").map do |each|
17
+ case each
18
+ when /^\d+: #{PREFIX}(\d+)_(\h{8})[@:]/
19
+ ip_addr = IPAddr.new($LAST_MATCH_INFO[2].hex, Socket::AF_INET)
20
+ new(name: ip_addr, link_id: $LAST_MATCH_INFO[1].to_i)
21
+ when /^\d+: #{PREFIX}(\d+)_([^:]*?)[@:]/
22
+ new(name: $LAST_MATCH_INFO[2], link_id: $LAST_MATCH_INFO[1].to_i)
23
+ end
24
+ end
25
+ (Netns.all.map(&:device) + link_devices).compact
26
+ end
27
+ # rubocop:enable Metrics/AbcSize
28
+
29
+ attr_reader :link_id
30
+
31
+ def initialize(name:, link_id:)
32
+ @name = valid_ipaddress?(name) ? IPAddr.new(name, Socket::AF_INET) : name
33
+ @link_id = link_id
34
+ end
35
+
36
+ def name
37
+ @name.to_s
38
+ end
39
+
40
+ def device
41
+ if @name.is_a?(IPAddr)
42
+ hex = format('%x', @name.to_i)
43
+ "#{PREFIX}#{@link_id}_#{hex}"
44
+ else
45
+ "#{PREFIX}#{@link_id}_#{@name}"
46
+ end
47
+ end
48
+ alias to_s device
49
+ alias to_str device
50
+
51
+ def ==(other)
52
+ name == other.name && link_id == other.link_id
53
+ end
54
+
55
+ def <=>(other)
56
+ device <=> other.device
57
+ end
58
+
59
+ private
60
+
61
+ def valid_ipaddress?(string)
62
+ IPAddr.new(string, Socket::AF_INET)
63
+ true
64
+ rescue
65
+ false
66
+ end
67
+ end
68
+ end
@@ -1,102 +1,143 @@
1
- require 'active_support/core_ext/class/attribute_accessors'
2
- require 'phut/null_logger'
1
+ # frozen_string_literal: true
2
+
3
+ require 'phut/finder'
3
4
  require 'phut/setting'
4
5
  require 'phut/shell_runner'
5
- require 'pio/mac'
6
+ require 'phut/vhost_daemon'
6
7
 
7
8
  module Phut
8
- # An interface class to vhost emulation utility program.
9
+ # Virtual host for NetTester
10
+ # rubocop:disable ClassLength
9
11
  class Vhost
10
- cattr_accessor(:all, instance_reader: false) { [] }
11
-
12
- def self.create(ip_address, mac_address, promisc, name = nil,
13
- logger = NullLogger.new)
14
- new(ip_address, mac_address, promisc, name, logger).tap do |vhost|
15
- conflict = find_by(name: vhost.name)
16
- fail "The name #{vhost.name} conflicts with #{conflict}." if conflict
17
- all << vhost
12
+ extend Finder
13
+
14
+ attr_reader :ip_address
15
+ attr_reader :mac_address
16
+
17
+ def self.all
18
+ Dir.glob(File.join(Phut.socket_dir, 'vhost.*.ctl')).map do |each|
19
+ vhost = DRbObject.new_with_uri("drbunix:#{each}")
20
+ new(name: vhost.name,
21
+ ip_address: vhost.ip_address,
22
+ mac_address: vhost.mac_address,
23
+ device: vhost.device)
18
24
  end
19
25
  end
20
26
 
21
- # This method smells of :reek:NestedIterators but ignores them
22
- def self.find_by(queries)
23
- queries.inject(all) do |memo, (attr, value)|
24
- memo.find_all { |vhost| vhost.__send__(attr) == value }
25
- end.first
27
+ def self.create(*args)
28
+ new(*args).tap(&:start)
26
29
  end
27
30
 
28
- def self.each(&block)
29
- all.each(&block)
31
+ def self.destroy_all
32
+ ::Dir.glob(File.join(Phut.socket_dir, 'vhost.*.ctl')).each do |each|
33
+ /vhost\.(\S+)\.ctl/=~ each
34
+ VhostDaemon.process(Regexp.last_match(1), Phut.socket_dir).kill
35
+ end
30
36
  end
31
37
 
32
- include ShellRunner
33
-
34
- attr_reader :ip_address
35
- attr_reader :mac_address
36
- attr_accessor :network_device
38
+ def self.connect_link
39
+ all.each do |each|
40
+ Link.all.each do |link|
41
+ device = link.device(each.name)
42
+ each.device = device if device
43
+ end
44
+ end
45
+ end
37
46
 
38
- def initialize(ip_address, mac_address, promisc, name, logger)
39
- @ip_address = ip_address
40
- @promisc = promisc
47
+ # rubocop:disable ParameterLists
48
+ def initialize(name:, ip_address:, mac_address:,
49
+ device: nil, promisc: false, arp_entries: nil)
41
50
  @name = name
51
+ @ip_address = ip_address
42
52
  @mac_address = mac_address
43
- @logger = logger
53
+ @device = device
54
+ @promisc = promisc
55
+ @arp_entries = arp_entries
44
56
  end
57
+ # rubocop:enable ParameterLists
58
+
59
+ include ShellRunner
45
60
 
46
61
  def name
47
62
  @name || @ip_address
48
63
  end
49
64
 
50
- def to_s
51
- "vhost (name = #{name}, IP address = #{@ip_address})"
52
- end
53
-
54
- def run(all_hosts = Vhost.all)
55
- @all_hosts ||= all_hosts
65
+ # rubocop:disable LineLength
66
+ def start
56
67
  if ENV['rvm_path']
57
68
  sh "rvmsudo vhost run #{run_options}"
58
69
  else
59
- vhost = File.join(__dir__, '..', '..', 'bin', 'vhost')
60
- sh "bundle exec sudo #{vhost} run #{run_options}"
70
+ vhost = File.expand_path('../../bin/vhost', __dir__)
71
+ sh "bundle exec sudo env PATH=#{ENV['PATH']} #{vhost} run #{run_options}"
61
72
  end
73
+ sleep 1
74
+ self.device = @device if @device
75
+ end
76
+ alias run start
77
+ # rubocop:enable LineLength
78
+
79
+ def running?
80
+ VhostDaemon.process(name, Phut.socket_dir).running?
62
81
  end
63
82
 
64
83
  def stop
65
- return unless running?
66
- stop!
84
+ VhostDaemon.process(name, Phut.socket_dir).stop
67
85
  end
68
86
 
69
- def stop!
70
- fail "vhost (name = #{name}) is not running!" unless running?
71
- sh "vhost stop -n #{name} -S #{Phut.socket_dir}"
87
+ def kill
88
+ sh "bundle exec vhost stop -n #{name} -S #{Phut.socket_dir}"
89
+ sleep 1
72
90
  end
73
91
 
74
- def running?
75
- FileTest.exists?(pid_file)
92
+ def device
93
+ VhostDaemon.process(name, Phut.socket_dir).device
94
+ end
95
+
96
+ def device=(device_name)
97
+ VhostDaemon.process(name, Phut.socket_dir).device = device_name
98
+ end
99
+
100
+ def send_packet(destination)
101
+ VhostDaemon.process(name, Phut.socket_dir).send_packets(destination, 1)
102
+ end
103
+
104
+ def packets_sent_to(dest)
105
+ VhostDaemon.process(name, Phut.socket_dir).stats[:tx].select do |each|
106
+ (each[:destination_mac].to_s == dest.mac_address) &&
107
+ (each[:destination_ip_address].to_s == dest.ip_address)
108
+ end
109
+ end
110
+
111
+ def packets_received_from(source)
112
+ VhostDaemon.process(name, Phut.socket_dir).stats[:rx].select do |each|
113
+ (each[:source_mac].to_s == source.mac_address) &&
114
+ (each[:source_ip_address].to_s == source.ip_address)
115
+ end
116
+ end
117
+
118
+ def set_default_arp_table
119
+ arp_table = Vhost.all.each_with_object({}) do |each, hash|
120
+ hash[each.ip_address] = each.mac_address
121
+ end
122
+ VhostDaemon.process(name, Phut.socket_dir).arp_table = arp_table
123
+ end
124
+
125
+ def to_s
126
+ "vhost (name = #{name}, IP address = #{@ip_address})"
76
127
  end
77
128
 
78
129
  private
79
130
 
80
131
  def run_options
81
132
  ["-n #{name}",
82
- "-I #{@network_device}",
83
- "-i #{@ip_address}",
84
- "-m #{@mac_address}",
85
- "-a #{arp_entries}",
133
+ "-i #{ip_address}",
134
+ "-m #{mac_address}",
135
+ @arp_entries ? "-a #{@arp_entries}" : nil,
86
136
  @promisc ? '--promisc' : nil,
87
- "-P #{Phut.pid_dir}",
88
137
  "-L #{Phut.log_dir}",
138
+ "-P #{Phut.pid_dir}",
89
139
  "-S #{Phut.socket_dir}"].compact.join(' ')
90
140
  end
91
-
92
- def arp_entries
93
- @all_hosts.map do |each|
94
- "#{each.ip_address}/#{each.mac_address}"
95
- end.join(',')
96
- end
97
-
98
- def pid_file
99
- "#{Phut.pid_dir}/vhost.#{name}.pid"
100
- end
101
141
  end
142
+ # rubocop:enable ClassLength
102
143
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'drb'
2
4
  require 'logger'
3
5
  require 'phut/raw_socket'
@@ -16,22 +18,55 @@ module Phut
16
18
  end
17
19
 
18
20
  def initialize(options)
19
- @options = options
21
+ @options = options.dup
22
+ @options[:device] = @options.fetch(:interface)
23
+ @options[:log_dir] = File.expand_path(@options[:log_dir])
24
+ @options[:pid_dir] = File.expand_path(@options[:pid_dir])
25
+ @options[:socket_dir] = File.expand_path(@options[:socket_dir])
20
26
  reset_stats
21
27
  end
22
28
 
29
+ def name
30
+ @options.fetch :name
31
+ end
32
+
33
+ def ip_address
34
+ @options.fetch :ip_address
35
+ end
36
+
37
+ def mac_address
38
+ @options.fetch :mac_address
39
+ end
40
+
41
+ def device
42
+ @options[:device]
43
+ end
44
+
45
+ def device=(name)
46
+ @options[:device] = name
47
+ end
48
+
23
49
  def run
24
50
  start_logging
25
51
  create_pid_file
26
52
  start_daemon
53
+ @stop = false
27
54
  rescue
28
55
  shutdown
29
56
  end
30
57
 
58
+ def running?
59
+ FileTest.exists?(pid_file)
60
+ end
61
+
31
62
  def stop
32
63
  @stop = true
33
64
  end
34
65
 
66
+ def kill
67
+ @kill = true
68
+ end
69
+
35
70
  def send_packets(dest, npackets)
36
71
  return unless lookup_arp_table(dest.ip_address)
37
72
  udp = create_udp_packet(dest)
@@ -50,19 +85,23 @@ module Phut
50
85
  @packets_received = []
51
86
  end
52
87
 
88
+ def arp_table=(arp_table)
89
+ @options[:arp_table] = arp_table
90
+ end
91
+
53
92
  private
54
93
 
55
94
  def start_logging
56
95
  @logger = Logger.new(File.open(log_file, 'a'))
57
96
  @logger.info("#{@options.fetch(:name)} started " \
58
- "(interface = #{@options.fetch(:interface)}," \
97
+ "(interface = #{@options.fetch(:device)}," \
59
98
  " IP address = #{@options.fetch(:ip_address)}," \
60
99
  " MAC address = #{@options.fetch(:mac_address)}," \
61
100
  " arp_entries = #{@options.fetch(:arp_entries)})")
62
101
  end
63
102
 
64
103
  def raw_socket
65
- @raw_socket ||= RawSocket.new(@options.fetch(:interface))
104
+ @raw_socket ||= RawSocket.new(@options.fetch(:device))
66
105
  end
67
106
 
68
107
  def lookup_arp_table(ip_address)
@@ -89,11 +128,17 @@ module Phut
89
128
  end
90
129
 
91
130
  # rubocop:disable MethodLength
131
+ # rubocop:disable AbcSize
92
132
  def read_loop
93
133
  loop do
134
+ unless @options.fetch(:device)
135
+ sleep 0.1
136
+ next
137
+ end
94
138
  begin
95
139
  raw_data, = raw_socket.recvfrom(8192)
96
140
  udp = Pio::Udp.read(raw_data)
141
+ next if @stop
97
142
  unless @options[:promisc]
98
143
  next if udp.destination_ip_address != @options.fetch(:ip_address)
99
144
  end
@@ -106,6 +151,7 @@ module Phut
106
151
  end
107
152
  end
108
153
  # rubocop:enable MethodLength
154
+ # rubocop:enable AbcSize
109
155
 
110
156
  def start_daemon
111
157
  Process.daemon
@@ -118,7 +164,7 @@ module Phut
118
164
  unix_domain_socket =
119
165
  self.class.unix_domain_socket(@options.fetch(:name),
120
166
  @options.fetch(:socket_dir))
121
- DRb.start_service(unix_domain_socket, self, UNIXFileMode: 0666)
167
+ DRb.start_service(unix_domain_socket, self, UNIXFileMode: 0o666)
122
168
  Thread.start { read_loop }.abort_on_exception = true
123
169
  DRb.thread.join
124
170
  end
@@ -128,7 +174,7 @@ module Phut
128
174
  @logger.info 'Shutting down...'
129
175
  FileUtils.rm pid_file if running?
130
176
  DRb.stop_service
131
- fail $ERROR_INFO if $ERROR_INFO
177
+ raise $ERROR_INFO if $ERROR_INFO
132
178
  end
133
179
 
134
180
  def trap_sigint
@@ -138,14 +184,14 @@ module Phut
138
184
 
139
185
  def shutdown_loop
140
186
  loop do
141
- break if @stop
187
+ break if @kill
142
188
  sleep 0.1
143
189
  end
144
190
  shutdown
145
191
  end
146
192
 
147
193
  def create_pid_file
148
- fail "#{@options.fetch(:name)} is already running." if running?
194
+ raise "#{@options.fetch(:name)} is already running." if running?
149
195
  update_pid_file
150
196
  end
151
197
 
@@ -153,10 +199,6 @@ module Phut
153
199
  File.open(pid_file, 'w') { |file| file << Process.pid }
154
200
  end
155
201
 
156
- def running?
157
- FileTest.exists?(pid_file)
158
- end
159
-
160
202
  def pid_file
161
203
  File.join @options.fetch(:pid_dir), "vhost.#{@options.fetch(:name)}.pid"
162
204
  end