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,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