phut 0.7.7 → 0.7.8

Sign up to get free protection for your applications and to get access to all the features.
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