phut 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +3 -0
- data/.rubocop.yml +11 -0
- data/.ruby-version +1 -0
- data/.travis.yml +34 -0
- data/CHANGELOG.md +8 -0
- data/CONTRIBUTING.md +46 -0
- data/Gemfile +4 -0
- data/Guardfile +29 -0
- data/LICENSE +339 -0
- data/README.md +49 -0
- data/Rakefile +10 -0
- data/bin/phut +111 -0
- data/features/dsl.feature +26 -0
- data/features/dsl_link.feature +22 -0
- data/features/dsl_vhost.feature +37 -0
- data/features/dsl_vswitch.feature +45 -0
- data/features/phut.feature +5 -0
- data/features/shell.feature +40 -0
- data/features/step_definitions/phut_steps.rb +30 -0
- data/features/support/env.rb +4 -0
- data/features/support/hooks.rb +34 -0
- data/lib/phut.rb +5 -0
- data/lib/phut/cli.rb +61 -0
- data/lib/phut/configuration.rb +64 -0
- data/lib/phut/null_logger.rb +14 -0
- data/lib/phut/open_vswitch.rb +116 -0
- data/lib/phut/parser.rb +51 -0
- data/lib/phut/phost.rb +92 -0
- data/lib/phut/setting.rb +54 -0
- data/lib/phut/shell_runner.rb +9 -0
- data/lib/phut/syntax.rb +123 -0
- data/lib/phut/version.rb +4 -0
- data/lib/phut/virtual_link.rb +56 -0
- data/phut.gemspec +55 -0
- data/spec/phut/open_vswitch_spec.rb +21 -0
- data/spec/phut/parser_spec.rb +69 -0
- data/spec/phut_spec.rb +43 -0
- data/spec/spec_helper.rb +14 -0
- data/tasks/cucumber.rake +18 -0
- data/tasks/flog.rake +25 -0
- data/tasks/gem.rake +13 -0
- data/tasks/openvswitch.rake +26 -0
- data/tasks/reek.rake +11 -0
- data/tasks/relish.rake +4 -0
- data/tasks/rspec.rake +9 -0
- data/tasks/rubocop.rake +8 -0
- data/tasks/vhost.rake +32 -0
- metadata +426 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'phut/null_logger'
|
3
|
+
require 'phut/open_vswitch'
|
4
|
+
|
5
|
+
module Phut
|
6
|
+
# Parsed DSL data.
|
7
|
+
class Configuration
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :@all, :fetch
|
11
|
+
|
12
|
+
def initialize(logger = NullLogger.new)
|
13
|
+
@all = {}
|
14
|
+
@logger = logger
|
15
|
+
end
|
16
|
+
|
17
|
+
def vswitches
|
18
|
+
@all.values.select { |each| each.is_a? OpenVswitch }
|
19
|
+
end
|
20
|
+
|
21
|
+
def vhosts
|
22
|
+
@all.values.select { |each| each.is_a? Phost }
|
23
|
+
end
|
24
|
+
|
25
|
+
def links
|
26
|
+
@all.values.select { |each| each.is_a? VirtualLink }
|
27
|
+
end
|
28
|
+
|
29
|
+
def run
|
30
|
+
links.each(&:run)
|
31
|
+
vswitches.each(&:run)
|
32
|
+
vhosts.each { |each| each.run vhosts }
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop
|
36
|
+
vswitches.each(&:maybe_stop)
|
37
|
+
vhosts.each(&:maybe_stop)
|
38
|
+
links.each(&:maybe_stop)
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_vswitch(name, attrs)
|
42
|
+
check_name_conflict name
|
43
|
+
@all[name] = OpenVswitch.new(attrs[:dpid], name, @logger)
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_vhost(name, attrs)
|
47
|
+
check_name_conflict name
|
48
|
+
@all[name] = Phost.new(attrs[:ip], attrs[:promisc], name, @logger)
|
49
|
+
end
|
50
|
+
|
51
|
+
# This method smells of :reek:LongParameterList
|
52
|
+
def add_link(name_a, device_a, name_b, device_b)
|
53
|
+
@all[[name_a, name_b]] =
|
54
|
+
VirtualLink.new(name_a, device_a, name_b, device_b, @logger)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def check_name_conflict(name)
|
60
|
+
conflict = @all[name]
|
61
|
+
fail "The name #{name} conflicts with #{conflict}." if conflict
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'phut/null_logger'
|
2
|
+
require 'phut/setting'
|
3
|
+
require 'phut/shell_runner'
|
4
|
+
|
5
|
+
module Phut
|
6
|
+
# Open vSwitch controller.
|
7
|
+
class OpenVswitch
|
8
|
+
include ShellRunner
|
9
|
+
|
10
|
+
OPENFLOWD =
|
11
|
+
"#{Phut.root}/vendor/openvswitch-1.2.2.trema1/tests/test-openflowd"
|
12
|
+
OFCTL =
|
13
|
+
"#{Phut.root}/vendor/openvswitch-1.2.2.trema1/utilities/ovs-ofctl"
|
14
|
+
|
15
|
+
attr_reader :dpid
|
16
|
+
alias_method :datapath_id, :dpid
|
17
|
+
attr_writer :interfaces
|
18
|
+
|
19
|
+
def initialize(dpid, name = nil, logger = NullLogger.new)
|
20
|
+
@dpid = dpid
|
21
|
+
@name = name
|
22
|
+
@interfaces = []
|
23
|
+
@logger = logger
|
24
|
+
end
|
25
|
+
|
26
|
+
def name
|
27
|
+
@name || format('%#x', @dpid)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
"vswitch (name = #{name}, dpid = #{format('%#x', @dpid)})"
|
32
|
+
end
|
33
|
+
|
34
|
+
def run
|
35
|
+
sh "sudo #{OPENFLOWD} #{options.join ' '}"
|
36
|
+
rescue
|
37
|
+
raise "Open vSwitch (dpid = #{@dpid}) is already running!"
|
38
|
+
end
|
39
|
+
alias_method :start, :run
|
40
|
+
|
41
|
+
def stop
|
42
|
+
fail "Open vSwitch (dpid = #{@dpid}) is not running!" unless running?
|
43
|
+
pid = IO.read(pid_file).chomp
|
44
|
+
sh "sudo kill #{pid}"
|
45
|
+
end
|
46
|
+
alias_method :shutdown, :stop
|
47
|
+
|
48
|
+
def maybe_stop
|
49
|
+
return unless running?
|
50
|
+
stop
|
51
|
+
end
|
52
|
+
|
53
|
+
def bring_port_up(port_number)
|
54
|
+
sh "sudo #{OFCTL} mod-port #{network_device} #{port_number} up"
|
55
|
+
end
|
56
|
+
|
57
|
+
def bring_port_down(port_number)
|
58
|
+
sh "sudo #{OFCTL} mod-port #{network_device} #{port_number} down"
|
59
|
+
end
|
60
|
+
|
61
|
+
def dump_flows
|
62
|
+
sh "sudo #{OFCTL} dump-flows #{network_device}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def running?
|
66
|
+
FileTest.exists?(pid_file)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def restart
|
72
|
+
stop
|
73
|
+
start
|
74
|
+
end
|
75
|
+
|
76
|
+
def pid_file
|
77
|
+
"#{Phut.pid_dir}/open_vswitch.#{name}.pid"
|
78
|
+
end
|
79
|
+
|
80
|
+
def network_device
|
81
|
+
"vsw_#{name}"
|
82
|
+
end
|
83
|
+
|
84
|
+
# rubocop:disable MethodLength
|
85
|
+
def options
|
86
|
+
%W(--detach
|
87
|
+
--out-of-band
|
88
|
+
--fail=closed
|
89
|
+
--inactivity-probe=180
|
90
|
+
--rate-limit=40000
|
91
|
+
--burst-limit=20000
|
92
|
+
--pidfile=#{pid_file}
|
93
|
+
--verbose=ANY:file:#{logging_level}
|
94
|
+
--verbose=ANY:console:err
|
95
|
+
--log-file=#{Phut.log_dir}/open_vswitch.#{name}.log
|
96
|
+
--datapath-id=#{dpid_zero_filled}
|
97
|
+
--unixctl=#{Phut.socket_dir}/open_vswitch.#{name}.ctl
|
98
|
+
netdev@#{network_device} tcp:127.0.0.1:6633) +
|
99
|
+
ports_option
|
100
|
+
end
|
101
|
+
# rubocop:enable MethodLength
|
102
|
+
|
103
|
+
def dpid_zero_filled
|
104
|
+
hex = format('%x', @dpid)
|
105
|
+
'0' * (16 - hex.length) + hex
|
106
|
+
end
|
107
|
+
|
108
|
+
def logging_level
|
109
|
+
@logger.level == Logger::DEBUG ? 'dbg' : 'info'
|
110
|
+
end
|
111
|
+
|
112
|
+
def ports_option
|
113
|
+
@interfaces.empty? ? [] : ["--ports=#{@interfaces.join(',')}"]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/phut/parser.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'phut/configuration'
|
2
|
+
require 'phut/null_logger'
|
3
|
+
require 'phut/syntax'
|
4
|
+
|
5
|
+
module Phut
|
6
|
+
# Configuration DSL parser.
|
7
|
+
class Parser
|
8
|
+
def initialize(logger = NullLogger.new)
|
9
|
+
@config = Configuration.new(logger)
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse(file)
|
13
|
+
Syntax.new(@config).instance_eval IO.read(file), file
|
14
|
+
assign_vswitch_interfaces
|
15
|
+
assign_vhost_interface
|
16
|
+
@config
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def assign_vswitch_interfaces
|
22
|
+
@config.vswitches.each do |each|
|
23
|
+
each.interfaces = find_interfaces_by_name(each.name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def assign_vhost_interface
|
28
|
+
@config.vhosts.each do |each|
|
29
|
+
each.interface = find_host_interface_by_name(each.name)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_interfaces_by_name(name)
|
34
|
+
find_device_by_name(name, :name_a, :device_a) +
|
35
|
+
find_device_by_name(name, :name_b, :device_b)
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_host_interface_by_name(name)
|
39
|
+
find_interfaces_by_name(name).tap do |interface|
|
40
|
+
fail "No link found for host #{name}" if interface.empty?
|
41
|
+
fail "Multiple links connect to host #{name}" if interface.size > 1
|
42
|
+
end.first
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_device_by_name(name, name_type, device_type)
|
46
|
+
@config.links.select do |each|
|
47
|
+
each.__send__(name_type) == name
|
48
|
+
end.map(&device_type)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/phut/phost.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'phut/cli'
|
2
|
+
require 'phut/null_logger'
|
3
|
+
require 'phut/setting'
|
4
|
+
require 'phut/shell_runner'
|
5
|
+
require 'pio/mac'
|
6
|
+
|
7
|
+
module Phut
|
8
|
+
# An interface class to phost emulation utility program.
|
9
|
+
class Phost
|
10
|
+
include ShellRunner
|
11
|
+
|
12
|
+
attr_reader :ip
|
13
|
+
attr_reader :mac
|
14
|
+
attr_accessor :interface
|
15
|
+
|
16
|
+
def initialize(ip_address, promisc, name = nil, logger = NullLogger.new)
|
17
|
+
@ip = ip_address
|
18
|
+
@promisc = promisc
|
19
|
+
@name = name
|
20
|
+
@mac = Pio::Mac.new(rand(0xffffffffffff + 1))
|
21
|
+
@logger = logger
|
22
|
+
end
|
23
|
+
|
24
|
+
def name
|
25
|
+
@name || @ip
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"vhost (name = #{name}, ip = #{@ip})"
|
30
|
+
end
|
31
|
+
|
32
|
+
def run(hosts = [])
|
33
|
+
sh "sudo #{executable} #{options.join ' '}"
|
34
|
+
sleep 1
|
35
|
+
set_ip_and_mac_address
|
36
|
+
maybe_enable_promisc
|
37
|
+
add_arp_entries hosts
|
38
|
+
end
|
39
|
+
|
40
|
+
def stop
|
41
|
+
fail "phost (name = #{name}) is not running!" unless running?
|
42
|
+
pid = IO.read(pid_file)
|
43
|
+
sh "sudo kill #{pid}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def maybe_stop
|
47
|
+
return unless running?
|
48
|
+
stop
|
49
|
+
end
|
50
|
+
|
51
|
+
def netmask
|
52
|
+
'255.255.255.255'
|
53
|
+
end
|
54
|
+
|
55
|
+
def running?
|
56
|
+
FileTest.exists?(pid_file)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def set_ip_and_mac_address
|
62
|
+
Phut::Cli.new(self, @logger).set_ip_and_mac_address
|
63
|
+
end
|
64
|
+
|
65
|
+
def maybe_enable_promisc
|
66
|
+
return unless @promisc
|
67
|
+
Phut::Cli.new(self).enable_promisc
|
68
|
+
end
|
69
|
+
|
70
|
+
def add_arp_entries(hosts)
|
71
|
+
hosts.each do |each|
|
72
|
+
Phut::Cli.new(self).add_arp_entry each
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def pid_file
|
77
|
+
"#{Phut.pid_dir}/phost.#{name}.pid"
|
78
|
+
end
|
79
|
+
|
80
|
+
def executable
|
81
|
+
"#{Phut.root}/vendor/phost/src/phost"
|
82
|
+
end
|
83
|
+
|
84
|
+
def options
|
85
|
+
%W(-p #{Phut.pid_dir}
|
86
|
+
-l #{Phut.log_dir}
|
87
|
+
-n #{name}
|
88
|
+
-i #{interface}
|
89
|
+
-D)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/phut/setting.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
|
3
|
+
# Base module.
|
4
|
+
module Phut
|
5
|
+
# Central configuration repository.
|
6
|
+
class Setting
|
7
|
+
DEFAULTS = {
|
8
|
+
root: File.expand_path(File.join(__dir__, '..', '..')),
|
9
|
+
pid_dir: Dir.tmpdir,
|
10
|
+
log_dir: Dir.tmpdir,
|
11
|
+
socket_dir: Dir.tmpdir
|
12
|
+
}
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@options = DEFAULTS.dup
|
16
|
+
end
|
17
|
+
|
18
|
+
def root
|
19
|
+
@options.fetch :root
|
20
|
+
end
|
21
|
+
|
22
|
+
def pid_dir
|
23
|
+
@options.fetch :pid_dir
|
24
|
+
end
|
25
|
+
|
26
|
+
def pid_dir=(path)
|
27
|
+
@options[:pid_dir] = File.expand_path(path)
|
28
|
+
end
|
29
|
+
|
30
|
+
def log_dir
|
31
|
+
@options.fetch :log_dir
|
32
|
+
end
|
33
|
+
|
34
|
+
def log_dir=(path)
|
35
|
+
@options[:log_dir] = File.expand_path(path)
|
36
|
+
end
|
37
|
+
|
38
|
+
def socket_dir
|
39
|
+
@options.fetch :socket_dir
|
40
|
+
end
|
41
|
+
|
42
|
+
def socket_dir=(path)
|
43
|
+
@options[:socket_dir] = File.expand_path(path)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
SettingSingleton = Setting.new
|
48
|
+
|
49
|
+
class << self
|
50
|
+
def method_missing(method, *args)
|
51
|
+
SettingSingleton.__send__ method, *args
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/phut/syntax.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'phut/phost'
|
2
|
+
require 'phut/virtual_link'
|
3
|
+
|
4
|
+
module Phut
|
5
|
+
# DSL syntax definitions.
|
6
|
+
class Syntax
|
7
|
+
# The 'vswitch(name) { ...attributes... }' directive.
|
8
|
+
class VswitchDirective
|
9
|
+
def initialize(alias_name, &block)
|
10
|
+
@attributes = { name: alias_name }
|
11
|
+
instance_eval(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def dpid(value)
|
15
|
+
dpid = if value.is_a?(String) && /^0x/=~ value
|
16
|
+
value.hex
|
17
|
+
else
|
18
|
+
value.to_i
|
19
|
+
end
|
20
|
+
@attributes[:dpid] = dpid
|
21
|
+
@attributes[:name] ||= format('%#x', @attributes[:dpid])
|
22
|
+
end
|
23
|
+
alias_method :datapath_id, :dpid
|
24
|
+
|
25
|
+
def [](key)
|
26
|
+
@attributes[key]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# The 'vhost(name) { ...attributes... }' directive.
|
31
|
+
class VhostDirective
|
32
|
+
# Generates an unused IP address
|
33
|
+
class UnusedIpAddress
|
34
|
+
def initialize
|
35
|
+
@index = 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def generate
|
39
|
+
@index += 1
|
40
|
+
"192.168.0.#{@index}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
UnusedIpAddressSingleton = UnusedIpAddress.new
|
45
|
+
|
46
|
+
def initialize(alias_name, &block)
|
47
|
+
@attributes = { name: alias_name }
|
48
|
+
if block
|
49
|
+
instance_eval(&block) if block
|
50
|
+
else
|
51
|
+
@attributes[:ip] = UnusedIpAddressSingleton.generate
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def ip(value)
|
56
|
+
@attributes[:ip] = value
|
57
|
+
@attributes[:name] ||= value
|
58
|
+
end
|
59
|
+
|
60
|
+
def promisc(on_off)
|
61
|
+
@attributes[:promisc] = on_off
|
62
|
+
end
|
63
|
+
|
64
|
+
def [](key)
|
65
|
+
@attributes[key]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# The 'link name_a, name_b' directive.
|
70
|
+
class LinkDirective
|
71
|
+
# Generates an unique Link ID
|
72
|
+
class LinkId
|
73
|
+
def initialize
|
74
|
+
init
|
75
|
+
end
|
76
|
+
|
77
|
+
def init
|
78
|
+
@index = 0
|
79
|
+
end
|
80
|
+
|
81
|
+
def generate
|
82
|
+
@index += 1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
LinkIdSingleton = LinkId.new
|
87
|
+
|
88
|
+
def initialize(name_a, name_b, link_id)
|
89
|
+
@attributes = {}
|
90
|
+
@attributes[:name_a] = name_a
|
91
|
+
@attributes[:name_b] = name_b
|
92
|
+
link_id = LinkIdSingleton.generate
|
93
|
+
@attributes[:device_a] = "link#{link_id}-0"
|
94
|
+
@attributes[:device_b] = "link#{link_id}-1"
|
95
|
+
end
|
96
|
+
|
97
|
+
def [](key)
|
98
|
+
@attributes[key]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def initialize(config)
|
103
|
+
@config = config
|
104
|
+
LinkDirective::LinkIdSingleton.init
|
105
|
+
end
|
106
|
+
|
107
|
+
def vswitch(alias_name = nil, &block)
|
108
|
+
attrs = VswitchDirective.new(alias_name, &block)
|
109
|
+
@config.add_vswitch attrs[:name], attrs
|
110
|
+
end
|
111
|
+
|
112
|
+
def vhost(alias_name = nil, &block)
|
113
|
+
attrs = VhostDirective.new(alias_name, &block)
|
114
|
+
@config.add_vhost attrs[:name], attrs
|
115
|
+
end
|
116
|
+
|
117
|
+
def link(name_a, name_b)
|
118
|
+
link_id = @config.links.size
|
119
|
+
attrs = LinkDirective.new(name_a, name_b, link_id)
|
120
|
+
@config.add_link name_a, attrs[:device_a], name_b, attrs[:device_b]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|