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,148 +1,150 @@
1
- require 'active_support/core_ext/class/attribute_accessors'
2
- require 'pio'
3
- require 'phut/null_logger'
4
- require 'phut/setting'
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/class/attribute'
4
+ require 'active_support/core_ext/module/delegation'
5
+ require 'phut/finder'
6
+ require 'phut/link'
5
7
  require 'phut/shell_runner'
8
+ require 'phut/vsctl'
9
+ require 'pio'
6
10
 
7
11
  module Phut
8
- # Open vSwitch controller.
12
+ # Open vSwitch controller
9
13
  # rubocop:disable ClassLength
10
14
  class OpenVswitch
11
- cattr_accessor(:all, instance_reader: false) { [] }
12
-
13
- def self.create(dpid, port_number = 6653, name = nil,
14
- logger = NullLogger.new)
15
- new(dpid, port_number, name, logger).tap do |vswitch|
16
- conflict = find_by(name: vswitch.name)
17
- fail "The name #{vswitch.name} conflicts with #{conflict}." if conflict
18
- all << vswitch
19
- end
20
- end
15
+ extend ShellRunner
16
+ extend Finder
17
+
18
+ class_attribute :bridge_prefix
19
+ self.bridge_prefix = ''
21
20
 
22
- def self.dump_flows(dpid, port_number = 6653, name = nil,
23
- logger = NullLogger.new)
24
- OpenVswitch.new(dpid, port_number, name, logger).dump_flows
21
+ def self.create(args)
22
+ found = find_by(name: args[:name]) || find_by(dpid: args[:dpid])
23
+ raise "a Vswitch #{found.inspect} already exists" if found
24
+ new(args).__send__ :start
25
25
  end
26
26
 
27
- def self.shutdown(dpid, port_number = 6653, name = nil,
28
- logger = NullLogger.new)
29
- OpenVswitch.new(dpid, port_number, name, logger).stop!
27
+ def self.destroy(name)
28
+ find_by!(name: name).destroy
30
29
  end
31
30
 
32
- def self.find_by(queries)
33
- queries.inject(all) do |memo, (attr, value)|
34
- memo.find_all { |vswitch| vswitch.__send__(attr) == value }
35
- end.first
31
+ def self.destroy_all
32
+ all.each(&:destroy)
36
33
  end
37
34
 
38
- def self.each(&block)
39
- all.each(&block)
35
+ def self.all
36
+ Vsctl.list_br(bridge_prefix).map do |bridge_attrs|
37
+ new(bridge_attrs)
38
+ end.sort_by(&:dpid)
40
39
  end
41
40
 
42
41
  def self.select(&block)
43
42
  all.select(&block)
44
43
  end
45
44
 
46
- class AlreadyRunning < StandardError; end
45
+ def self.dump_flows(name)
46
+ find_by!(name: name).dump_flows
47
+ end
47
48
 
48
49
  include ShellRunner
49
50
 
50
- attr_reader :dpid
51
- alias_method :datapath_id, :dpid
52
- attr_reader :network_devices
51
+ private_class_method :new
53
52
 
54
- def initialize(dpid, port_number, name, logger)
53
+ def initialize(dpid:, openflow_version: 1.0, name: nil, tcp_port: 6653)
55
54
  @dpid = dpid
56
- @port_number = port_number
57
55
  @name = name
58
- @network_devices = []
59
- @logger = logger
60
- end
61
-
62
- def name
63
- @name || format('%#x', @dpid)
56
+ @openflow_version = openflow_version
57
+ @tcp_port = tcp_port
58
+ @vsctl = Vsctl.new(name: default_name, bridge: default_bridge)
64
59
  end
65
60
 
66
61
  def to_s
67
62
  "vswitch (name = #{name}, dpid = #{format('%#x', @dpid)})"
68
63
  end
69
64
 
70
- # rubocop:disable MethodLength
71
- # rubocop:disable AbcSize
72
- def run
73
- sh "sudo ovs-vsctl add-br #{bridge_name}"
74
- sh "sudo /sbin/sysctl -w net.ipv6.conf.#{bridge_name}.disable_ipv6=1 -q"
75
- @network_devices.each do |each|
76
- sh "sudo ovs-vsctl add-port #{bridge_name} #{each}"
77
- end
78
- sh "sudo ovs-vsctl set bridge #{bridge_name}" \
79
- " protocols=#{Pio::OpenFlow.version}" \
80
- " other-config:datapath-id=#{dpid_zero_filled}"
81
- sh "sudo ovs-vsctl set-controller #{bridge_name} "\
82
- "tcp:127.0.0.1:#{@port_number} "\
83
- "-- set controller #{bridge_name} connection-mode=out-of-band"
84
- sh "sudo ovs-vsctl set-fail-mode #{bridge_name} secure"
85
- rescue
86
- raise AlreadyRunning, "Open vSwitch (dpid = #{@dpid}) is already running!"
87
- end
88
- alias_method :start, :run
89
- # rubocop:enable MethodLength
90
- # rubocop:enable AbcSize
91
-
92
- def stop
93
- return unless running?
94
- stop!
65
+ def inspect
66
+ "#<Vswitch name: \"#{name}\", "\
67
+ "dpid: #{@dpid.to_hex}, "\
68
+ "openflow_version: #{openflow_version}, "\
69
+ "tcp_port: #{tcp_port}>"
95
70
  end
96
71
 
97
- def stop!
98
- fail "Open vSwitch (dpid = #{@dpid}) is not running!" unless running?
99
- sh "sudo ovs-vsctl del-br #{bridge_name}"
72
+ def name
73
+ /^#{bridge_prefix}(\S+)$/ =~ bridge
74
+ Regexp.last_match(1)
100
75
  end
101
- alias_method :shutdown, :stop!
102
76
 
103
- def bring_port_up(port_number)
104
- sh "sudo ovs-ofctl mod-port #{bridge_name} #{port_number} up"
105
- end
77
+ delegate :bridge_prefix, to: 'self.class'
78
+ delegate :bridge, to: :@vsctl
79
+ delegate :dpid, to: :@vsctl
80
+ alias datapath_id dpid
81
+ delegate :openflow_version, to: :@vsctl
82
+ delegate :tcp_port, to: :@vsctl
83
+
84
+ delegate :add_numbered_port, to: :@vsctl
85
+ delegate :add_port, to: :@vsctl
86
+ delegate :bring_port_down, to: :@vsctl
87
+ delegate :bring_port_up, to: :@vsctl
88
+ delegate :ports, to: :@vsctl
89
+ delegate :delete_bridge, to: :@vsctl
90
+ alias destroy delete_bridge
91
+ delegate :delete_controller, to: :@vsctl
92
+ alias stop delete_controller
106
93
 
107
- def bring_port_down(port_number)
108
- sh "sudo ovs-ofctl mod-port #{bridge_name} #{port_number} down"
94
+ def run(port)
95
+ raise "An Open vSwitch #{inspect} is already running" if @vsctl.tcp_port
96
+ @vsctl.tcp_port = port
109
97
  end
110
98
 
99
+ # rubocop:disable MethodLength
100
+ # rubocop:disable LineLength
111
101
  def dump_flows
112
- output =
113
- `sudo ovs-ofctl dump-flows #{bridge_name} -O #{Pio::OpenFlow.version}`
114
- output.split("\n").inject('') do |memo, each|
115
- memo + ((/^(NXST|OFPST)_FLOW reply/=~ each) ? '' : each.lstrip + "\n")
102
+ openflow_version = case @vsctl.openflow_version
103
+ when 1.0
104
+ :OpenFlow10
105
+ when 1.3
106
+ :OpenFlow13
107
+ else
108
+ raise "Unknown OpenFlow version: #{@vsctl.openflow_version}"
109
+ end
110
+ sudo("ovs-ofctl dump-flows #{bridge} -O #{openflow_version}").
111
+ split("\n").inject('') do |memo, each|
112
+ memo + (/^(NXST|OFPST)_FLOW reply/ =~ each ? '' : each.lstrip + "\n")
116
113
  end
117
114
  end
115
+ # rubocop:enable MethodLength
116
+ # rubocop:enable LineLength
118
117
 
119
- def running?
120
- system "sudo ovs-vsctl br-exists #{bridge_name}"
121
- end
122
-
123
- def add_network_device(network_device)
124
- network_device.port_number = @network_devices.size + 1
125
- @network_devices << network_device
118
+ def <=>(other)
119
+ dpid <=> other.dpid
126
120
  end
127
121
 
128
122
  private
129
123
 
130
- def bridge_name
131
- 'br' + name
132
- end
133
-
134
- def restart
135
- stop
136
- start
124
+ # rubocop:disable LineLength
125
+ def default_name
126
+ if @name
127
+ if (bridge_prefix + @name).length > Vsctl::MAX_BRIDGE_NAME_LENGTH
128
+ raise "Name '#{@name}' is too long (should be <= #{Vsctl::MAX_BRIDGE_NAME_LENGTH - bridge_prefix.length} chars)"
129
+ end
130
+ return @name
131
+ end
132
+ raise 'DPID is not set' unless @dpid
133
+ format('%#x', @dpid)
137
134
  end
135
+ # rubocop:enable LineLength
138
136
 
139
- def network_device
140
- "vsw_#{name}"
137
+ def default_bridge
138
+ bridge_prefix + default_name
141
139
  end
142
140
 
143
- def dpid_zero_filled
144
- hex = format('%x', @dpid)
145
- '0' * (16 - hex.length) + hex
141
+ def start
142
+ @vsctl.add_bridge
143
+ @vsctl.openflow_version = @openflow_version
144
+ @vsctl.dpid = @dpid
145
+ @vsctl.tcp_port = @tcp_port
146
+ @vsctl.set_fail_mode_secure
147
+ self
146
148
  end
147
149
  end
148
150
  # rubocop:enable ClassLength
@@ -1,19 +1,50 @@
1
- require 'phut/configuration'
2
- require 'phut/null_logger'
1
+ # frozen_string_literal: true
2
+
3
+ require 'phut/link'
3
4
  require 'phut/syntax'
5
+ require 'phut/vhost'
6
+ require 'phut/vswitch'
4
7
 
5
8
  module Phut
6
9
  # Configuration DSL parser.
7
10
  class Parser
8
- def initialize(logger = NullLogger.new)
9
- @logger = logger
11
+ def initialize(file)
12
+ @file = file
13
+ @netns = []
14
+ end
15
+
16
+ def parse
17
+ Syntax.new(@netns).instance_eval IO.read(@file), @file
18
+ Link.all.each do |link|
19
+ Vswitch.all.each do |vswitch|
20
+ device = link.device(vswitch.name)
21
+ vswitch.add_port device if device
22
+ end
23
+ end
24
+ Vhost.all.each(&:set_default_arp_table)
25
+ Vhost.connect_link
26
+ update_netns_interfaces
27
+ end
28
+
29
+ private
30
+
31
+ def update_netns_interfaces
32
+ @netns.each do |each|
33
+ netns =
34
+ Netns.create(name: each[:name],
35
+ ip_address: each[:ip], netmask: each[:netmask],
36
+ route: { net: each[:net], gateway: each[:gateway] },
37
+ mac_address: each[:mac_address], vlan: each[:vlan])
38
+ netns.device = find_network_device(each.name)
39
+ end
10
40
  end
11
41
 
12
- def parse(file)
13
- Configuration.new do |config|
14
- Syntax.new(config, @logger).instance_eval IO.read(file), file
15
- config.update_connections
42
+ def find_network_device(name)
43
+ Link.all.each do |each|
44
+ device = each.device(name)
45
+ return device if device
16
46
  end
47
+ nil
17
48
  end
18
49
  end
19
50
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'socket'
2
4
 
3
5
  module Phut
@@ -19,8 +21,10 @@ module Phut
19
21
  end
20
22
  end
21
23
 
24
+ # rubocop:disable MethodMissing
22
25
  def method_missing(method, *args)
23
26
  @socket.__send__ method, *args
24
27
  end
28
+ # rubocop:enable MethodMissing
25
29
  end
26
30
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'phut/shell_runner'
4
+
5
+ module Phut
6
+ # routing table entry
7
+ class Route
8
+ extend ShellRunner
9
+
10
+ def self.read(netns)
11
+ sudo("ip netns exec #{netns} route -n").split("\n").each do |each|
12
+ match = /^(\S+)\s+(\S+)\s+\S+\s+UG\s+/.match(each)
13
+ next unless match
14
+ return new(net: match[1], gateway: match[2])
15
+ end
16
+ nil
17
+ end
18
+
19
+ attr_reader :net
20
+ attr_reader :gateway
21
+
22
+ def initialize(net:, gateway:)
23
+ @net = net
24
+ @gateway = gateway
25
+ end
26
+
27
+ include ShellRunner
28
+
29
+ def add(netns)
30
+ return unless @net && @gateway
31
+ sudo "ip netns exec #{netns} route add -net #{@net} gw #{@gateway}"
32
+ end
33
+ end
34
+ end
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
1
4
  require 'tmpdir'
2
5
 
3
6
  # Base module.
@@ -6,10 +9,14 @@ module Phut
6
9
  class Setting
7
10
  DEFAULTS = {
8
11
  root: File.expand_path(File.join(File.dirname(__FILE__), '..', '..')),
12
+ logger: Logger.new($stderr).tap do |logger|
13
+ logger.formatter = proc { |_sev, _dtm, _name, msg| msg + "\n" }
14
+ logger.level = Logger::INFO
15
+ end,
9
16
  pid_dir: Dir.tmpdir,
10
17
  log_dir: Dir.tmpdir,
11
18
  socket_dir: Dir.tmpdir
12
- }
19
+ }.freeze
13
20
 
14
21
  def initialize
15
22
  @options = DEFAULTS.dup
@@ -19,12 +26,20 @@ module Phut
19
26
  @options.fetch :root
20
27
  end
21
28
 
29
+ def logger
30
+ @options.fetch :logger
31
+ end
32
+
33
+ def logger=(logger)
34
+ @options[:logger] = logger
35
+ end
36
+
22
37
  def pid_dir
23
38
  @options.fetch :pid_dir
24
39
  end
25
40
 
26
41
  def pid_dir=(path)
27
- fail "No such directory: #{path}" unless FileTest.directory?(path)
42
+ raise "No such directory: #{path}" unless FileTest.directory?(path)
28
43
  @options[:pid_dir] = File.expand_path(path)
29
44
  end
30
45
 
@@ -33,7 +48,7 @@ module Phut
33
48
  end
34
49
 
35
50
  def log_dir=(path)
36
- fail "No such directory: #{path}" unless FileTest.directory?(path)
51
+ raise "No such directory: #{path}" unless FileTest.directory?(path)
37
52
  @options[:log_dir] = File.expand_path(path)
38
53
  end
39
54
 
@@ -42,7 +57,7 @@ module Phut
42
57
  end
43
58
 
44
59
  def socket_dir=(path)
45
- fail "No such directory: #{path}" unless FileTest.directory?(path)
60
+ raise "No such directory: #{path}" unless FileTest.directory?(path)
46
61
  @options[:socket_dir] = File.expand_path(path)
47
62
  end
48
63
  end
@@ -50,8 +65,10 @@ module Phut
50
65
  SettingSingleton = Setting.new
51
66
 
52
67
  class << self
68
+ # rubocop:disable MethodMissing
53
69
  def method_missing(method, *args)
54
70
  SettingSingleton.__send__ method, *args
55
71
  end
72
+ # rubocop:enable MethodMissing
56
73
  end
57
74
  end
@@ -1,9 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'phut/setting'
5
+
1
6
  module Phut
2
7
  # Provides sh method.
3
8
  module ShellRunner
9
+ def sudo(command)
10
+ sh "sudo #{command}"
11
+ end
12
+
4
13
  def sh(command)
5
- system(command) || fail("#{command} failed.")
6
- @logger.debug(command) if @logger
14
+ Phut.logger.debug(command)
15
+ stdout, stderr, status = Open3.capture3(command)
16
+ raise %(Command '#{command}' failed: #{stderr}) unless status.success?
17
+ stdout
7
18
  end
8
19
  end
9
20
  end