diff --git a/features/dsl_netns.feature b/features/dsl_netns.feature new file mode 100644 index 0000000..5bdd1b8 --- /dev/null +++ b/features/dsl_netns.feature @@ -0,0 +1,84 @@ +Feature: The netns DSL directive. + @sudo + Scenario: phut run with "netns(alias) { ip ... }" + Given a file named "network.conf" with: + """ + netns('host1') { + ip '192.168.8.6' + } + netns('host2') { + ip '192.168.8.7' + } + link 'host1', 'host2' + """ + When I do phut run "network.conf" + Then a netns named "host1" launches + And the "netmask" of the netns "host1" should be "/24" + And the "default_gateway" of the netns "host1" should be "" + And a netns named "host2" launches + And the "netmask" of the netns "host2" should be "/24" + And the "default_gateway" of the netns "host2" should be "" + + @sudo + Scenario: phut run with "netns(alias) { ip, netmask, route ... }" + Given a file named "network.conf" with: + """ + netns('host1') { + ip '192.168.8.6' + netmask '255.255.255.128' + route net: '0.0.0.0/0', gateway: '192.168.8.1' + } + netns('host2') { + ip '192.168.8.7' + netmask '255.255.255.128' + route net: '0.0.0.0/0', gateway: '192.168.8.1' + } + link 'host1', 'host2' + """ + When I do phut run "network.conf" + Then a netns named "host1" launches + And the "netmask" of the netns "host1" should be "/25" + And the "default_gateway" of the netns "host1" should be "192.168.8.1" + And a netns named "host2" launches + And the "netmask" of the netns "host2" should be "/25" + And the "default_gateway" of the netns "host2" should be "192.168.8.1" + + @sudo + Scenario: phut run with "netns(alias) { ip, vlan }" + Given a file named "network.conf" with: + """ + netns('host1') { + ip '192.168.8.6' + vlan 10 + } + netns('host2') { + ip '192.168.8.7' + vlan 20 + } + link 'host1', 'host2' + """ + When I do phut run "network.conf" + Then a netns named "host1" launches + And the "vlan" of the netns "host1" should be "10" + And a netns named "host2" launches + And the "vlan" of the netns "host2" should be "20" + + @sudo + Scenario: phut run with "netns(alias) { ip, mac }" + Given a file named "network.conf" with: + """ + netns('host1') { + ip '192.168.8.6' + mac '00:53:00:00:00:01' + } + netns('host2') { + ip '192.168.8.7' + mac '00:53:00:00:00:02' + } + link 'host1', 'host2' + """ + When I do phut run "network.conf" + Then a netns named "host1" launches + And the "mac" of the netns "host1" should be "00:53:00:00:00:01" + And a netns named "host2" launches + And the "mac" of the netns "host2" should be "00:53:00:00:00:02" diff --git a/features/step_definitions/phut_steps.rb b/features/step_definitions/phut_steps.rb index cd26eb2..13e745d 100644 --- a/features/step_definitions/phut_steps.rb +++ b/features/step_definitions/phut_steps.rb @@ -36,6 +36,28 @@ step %(a file named "vhost.#{name}.pid" should exist) end +Then(/^a netns named "(.*?)" launches$/) do |name| + expect(`ip netns`).to match(/^#{name}$/) +end + +# rubocop:disable LineLength +Then(/^the "(.*?)" of the netns "(.*?)" should be "(.*?)"$/) do |key, netns, value| + command = + case key + when 'netmask' + "ip addr list dev #{netns} | grep inet" + when 'default_gateway' + 'ip route list match 0/0' + when 'vlan' + "ip link list dev #{netns}.#{value}" + else + 'ip link list ; ip addr list' + end + + expect(`sudo ip netns exec #{netns} #{command}`).to match(/#{value}/) +end +# rubocop:enable LineLength + Then(/^a link is created between "(.*?)" and "(.*?)"$/) do |name_a, name_b| cd('.') do link = Phut::Parser.new.parse(@config_file).fetch([name_a, name_b].sort) diff --git a/lib/phut/netns.rb b/lib/phut/netns.rb index b9561d6..99a3ec1 100644 --- a/lib/phut/netns.rb +++ b/lib/phut/netns.rb @@ -26,19 +26,55 @@ def initialize(options, name, logger) @logger = logger end - # rubocop:disable AbcSize def run + setup_netns + setup_link + setup_ip + end + + def stop + sh "sudo ip netns delete #{name}" + end + + private + + def setup_netns sh "sudo ip netns add #{name}" sh "sudo ip link set dev #{network_device} netns #{name}" - sh "sudo ip netns exec #{name} ifconfig lo 127.0.0.1" + end + + def setup_link + setup_vlan + setup_mac_address + sh "sudo ip netns exec #{name} ip link set lo up" sh "sudo ip netns exec #{name}"\ - " ifconfig #{network_device} #{ip} netmask #{netmask}" - sh "sudo ip netns exec #{name} route add -net #{net} gw #{gateway}" + " ip link set #{network_device}#{vlan_suffix} up" end - # rubocop:enable AbcSize - def stop - sh "sudo ip netns delete #{name}" + def setup_vlan + return unless vlan + sh "sudo ip netns exec #{name}"\ + " ip link set #{network_device} up" + sh "sudo ip netns exec #{name}"\ + " ip link add link #{network_device} name"\ + " #{network_device}#{vlan_suffix} type vlan id #{vlan}" + end + + def setup_mac_address + sh "sudo ip netns exec #{name}"\ + " ip link set #{network_device}#{vlan_suffix} address #{mac}" if mac + end + + def setup_ip + sh "sudo ip netns exec #{name} ip addr replace 127.0.0.1 dev lo" + sh "sudo ip netns exec #{name}"\ + " ip addr replace #{ip}/#{netmask} dev #{network_device}#{vlan_suffix}" + sh "sudo ip netns exec #{name}"\ + " ip route add #{net} via #{gateway}" if gateway + end + + def vlan_suffix + vlan ? ".#{vlan}" : '' end def method_missing(message, *_args) diff --git a/lib/phut/syntax/netns_directive.rb b/lib/phut/syntax/netns_directive.rb index d32d17b..7a7c4dc 100644 --- a/lib/phut/syntax/netns_directive.rb +++ b/lib/phut/syntax/netns_directive.rb @@ -7,7 +7,13 @@ class NetnsDirective < Directive attribute :netmask def initialize(alias_name, &block) - @attributes = { name: alias_name } + @attributes = + { name: alias_name, + netmask: '255.255.255.0', + net: '0.0.0.0', + gateway: nil, + mac: nil, + vlan: nil } instance_eval(&block) end @@ -20,6 +26,14 @@ def route(options) @attributes[:net] = options.fetch(:net) @attributes[:gateway] = options.fetch(:gateway) end + + def mac(value) + @attributes[:mac] = value + end + + def vlan(value) + @attributes[:vlan] = value + end end end end