phut 0.7.7 → 0.7.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.rubocop.yml +14 -1
- data/.travis.yml +2 -6
- data/Gemfile +30 -2
- data/Gemfile.lock +156 -0
- data/README.md +7 -2
- data/Rakefile +4 -3
- data/bin/phut +35 -83
- data/bin/vhost +28 -26
- data/features/{dsl.feature → dsl/error.feature} +8 -6
- data/features/{dsl_link.feature → dsl/link.feature} +11 -14
- data/features/dsl/netns.feature +115 -0
- data/features/dsl/vhost.feature +37 -0
- data/features/{dsl_vswitch.feature → dsl/vswitch.feature} +12 -12
- data/features/phut_run.feature +15 -0
- data/features/shell/vswitch#destroy.feature +10 -0
- data/features/shell/vswitch#ports.feature +36 -0
- data/features/shell/vswitch.all.feature +26 -0
- data/features/shell/vswitch.create.feature +30 -0
- data/features/shell/vswitch.destroy.feature +19 -0
- data/features/shell/vswitch.destroy_all.feature +18 -0
- data/features/step_definitions/link_steps.rb +5 -0
- data/features/step_definitions/netns_steps.rb +31 -0
- data/features/step_definitions/phut_steps.rb +5 -34
- data/features/step_definitions/vhost_steps.rb +5 -0
- data/features/step_definitions/vswitch_steps.rb +17 -0
- data/features/support/env.rb +3 -3
- data/features/support/hooks.rb +23 -15
- data/lib/phut.rb +3 -0
- data/lib/phut/finder.rb +19 -0
- data/lib/phut/link.rb +84 -0
- data/lib/phut/netns.rb +111 -22
- data/lib/phut/open_vswitch.rb +98 -96
- data/lib/phut/parser.rb +39 -8
- data/lib/phut/raw_socket.rb +4 -0
- data/lib/phut/route.rb +34 -0
- data/lib/phut/setting.rb +21 -4
- data/lib/phut/shell_runner.rb +13 -2
- data/lib/phut/syntax.rb +31 -14
- data/lib/phut/syntax/directive.rb +9 -1
- data/lib/phut/syntax/netns_directive.rb +13 -2
- data/lib/phut/syntax/vhost_directive.rb +2 -0
- data/lib/phut/syntax/vswitch_directive.rb +3 -1
- data/lib/phut/version.rb +3 -1
- data/lib/phut/veth.rb +68 -0
- data/lib/phut/vhost.rb +99 -58
- data/lib/phut/vhost_daemon.rb +53 -11
- data/lib/phut/vsctl.rb +125 -0
- data/lib/phut/vswitch.rb +10 -0
- data/phut.gemspec +9 -31
- data/tasks/cucumber.rake +5 -1
- data/tasks/flay.rake +2 -0
- data/tasks/flog.rake +3 -1
- data/tasks/gem.rake +2 -0
- data/tasks/minitest.rake +7 -0
- data/tasks/reek.rake +2 -0
- data/tasks/rubocop.rake +2 -0
- data/tasks/yard.rake +2 -0
- data/test/phut/link_test.rb +85 -0
- data/test/phut/netns_test.rb +58 -0
- data/test/phut/open_vswitch_test.rb +125 -0
- data/test/phut/veth_test.rb +48 -0
- data/test/phut/vhost_test.rb +56 -0
- metadata +41 -287
- data/.rspec +0 -3
- data/Guardfile +0 -29
- data/features/dsl_vhost.feature +0 -37
- data/features/phut_kill.feature +0 -27
- data/features/shell.feature +0 -39
- data/lib/phut/configuration.rb +0 -92
- data/lib/phut/null_logger.rb +0 -14
- data/lib/phut/virtual_link.rb +0 -109
- data/spec/phut/parser_spec.rb +0 -66
- data/spec/phut_spec.rb +0 -45
- data/spec/spec_helper.rb +0 -14
- data/tasks/LICENSE +0 -675
- data/tasks/relish.rake +0 -8
- data/tasks/rspec.rake +0 -8
data/lib/phut/open_vswitch.rb
CHANGED
@@ -1,148 +1,150 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require '
|
4
|
-
require '
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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.
|
23
|
-
|
24
|
-
|
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.
|
28
|
-
|
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.
|
33
|
-
|
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.
|
39
|
-
|
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
|
-
|
45
|
+
def self.dump_flows(name)
|
46
|
+
find_by!(name: name).dump_flows
|
47
|
+
end
|
47
48
|
|
48
49
|
include ShellRunner
|
49
50
|
|
50
|
-
|
51
|
-
alias_method :datapath_id, :dpid
|
52
|
-
attr_reader :network_devices
|
51
|
+
private_class_method :new
|
53
52
|
|
54
|
-
def initialize(dpid
|
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
|
-
@
|
59
|
-
@
|
60
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
98
|
-
|
99
|
-
|
72
|
+
def name
|
73
|
+
/^#{bridge_prefix}(\S+)$/ =~ bridge
|
74
|
+
Regexp.last_match(1)
|
100
75
|
end
|
101
|
-
alias_method :shutdown, :stop!
|
102
76
|
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
108
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
120
|
-
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
140
|
-
|
137
|
+
def default_bridge
|
138
|
+
bridge_prefix + default_name
|
141
139
|
end
|
142
140
|
|
143
|
-
def
|
144
|
-
|
145
|
-
|
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
|
data/lib/phut/parser.rb
CHANGED
@@ -1,19 +1,50 @@
|
|
1
|
-
|
2
|
-
|
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(
|
9
|
-
@
|
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
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
data/lib/phut/raw_socket.rb
CHANGED
@@ -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
|
data/lib/phut/route.rb
ADDED
@@ -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
|
data/lib/phut/setting.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/phut/shell_runner.rb
CHANGED
@@ -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
|
-
|
6
|
-
|
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
|