From 4223d5b1ef34ccf06dba447b4ea06f84001c4374 Mon Sep 17 00:00:00 2001 From: Christoph Hartmann Date: Wed, 7 Oct 2015 23:47:57 +0200 Subject: [PATCH 1/5] implement interface for linux --- lib/resources/interface.rb | 51 ++++++++++++++++++++++++++++++++++++++ lib/vulcano/resource.rb | 1 + 2 files changed, 52 insertions(+) create mode 100644 lib/resources/interface.rb diff --git a/lib/resources/interface.rb b/lib/resources/interface.rb new file mode 100644 index 000000000..5eb5c3e2c --- /dev/null +++ b/lib/resources/interface.rb @@ -0,0 +1,51 @@ +# encoding: utf-8 +# author: Christoph Hartmann +# author: Dominik Richter + +# Usage: +# describe interface('eth0') do +# it { should exist } +# it { should be_up } +# its(:speed) { should eq 1000 } +# end + +require 'utils/convert' + +class NetworkInterface < Vulcano.resource(1) + include Converter + + name 'interface' + + def initialize(iface) + @iface = iface + @cache = nil + end + + def exists? + !interface_info.nil? + end + + def up? + return false if interface_info.nil? || !interface_info.key?('operstate') + key, _value = interface_info['operstate'].first + key == 'up' + end + + def speed + return nil if interface_info.nil? || !interface_info.key?('speed') + key, _value = interface_info['speed'].first + convert_to_i(key) + end + + private + + def interface_info + return @cache if !@cache.nil? + # will return "[mtu]\n1500\n[type]\n1" + cmd = vulcano.command("find /sys/class/net/#{@iface}/ -type f -maxdepth 1 -exec sh -c 'echo \"[$(basename {})]\"; cat {} || echo -n' \\;") + return nil if cmd.exit_status.to_i != 0 + + # parse values, we only recieve values, therefore we threat them as keys + @cache = SimpleConfig.new(cmd.stdout.chomp).params + end +end diff --git a/lib/vulcano/resource.rb b/lib/vulcano/resource.rb index 9be6a6363..f91c8cf43 100644 --- a/lib/vulcano/resource.rb +++ b/lib/vulcano/resource.rb @@ -34,6 +34,7 @@ require 'resources/gem' require 'resources/group' require 'resources/group_policy' require 'resources/inetd_conf' +require 'resources/interface' require 'resources/json' require 'resources/kernel_module' require 'resources/kernel_parameter' From 932b34e8de0ea480dd141359b52af4ebac2907bb Mon Sep 17 00:00:00 2001 From: Christoph Hartmann Date: Thu, 8 Oct 2015 10:06:49 +0200 Subject: [PATCH 2/5] externalize linux handling in separate provider --- lib/resources/interface.rb | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/resources/interface.rb b/lib/resources/interface.rb index 5eb5c3e2c..ffb777c91 100644 --- a/lib/resources/interface.rb +++ b/lib/resources/interface.rb @@ -19,6 +19,13 @@ class NetworkInterface < Vulcano.resource(1) def initialize(iface) @iface = iface @cache = nil + + @interface_provider = nil + if vulcano.os.linux? + @interface_provider = LinuxInterface.new(vulcano) + else + return skip_resource 'The `interface` resource is not supported on your OS yet.' + end end def exists? @@ -31,6 +38,7 @@ class NetworkInterface < Vulcano.resource(1) key == 'up' end + # returns link speed in Mbits/sec def speed return nil if interface_info.nil? || !interface_info.key?('speed') key, _value = interface_info['speed'].first @@ -40,9 +48,22 @@ class NetworkInterface < Vulcano.resource(1) private def interface_info + return @cache if !@cache.nil? + @cache = @interface_provider.interface_info(@iface) if !@interface_provider.nil? + end +end + +class InterfaceInfo + def initialize(vulcano) + @vulcano = vulcano + end +end + +class LinuxInterface < InterfaceInfo + def interface_info(iface) return @cache if !@cache.nil? # will return "[mtu]\n1500\n[type]\n1" - cmd = vulcano.command("find /sys/class/net/#{@iface}/ -type f -maxdepth 1 -exec sh -c 'echo \"[$(basename {})]\"; cat {} || echo -n' \\;") + cmd = @vulcano.command("find /sys/class/net/#{iface}/ -type f -maxdepth 1 -exec sh -c 'echo \"[$(basename {})]\"; cat {} || echo -n' \\;") return nil if cmd.exit_status.to_i != 0 # parse values, we only recieve values, therefore we threat them as keys From 46853e74b69341afb49acbb0539cbd8beb0419fd Mon Sep 17 00:00:00 2001 From: Christoph Hartmann Date: Thu, 8 Oct 2015 11:44:56 +0200 Subject: [PATCH 3/5] fix lint issues --- test/helper.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/helper.rb b/test/helper.rb index 2e169b2e2..bb68f4f50 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -124,12 +124,12 @@ class MockLoader 'service sendmail onestatus' => cmd.call('service-sendmail-onestatus'), # services for system 5 e.g. centos6, debian 6 'service sshd status' => cmd.call('service-sshd-status'), - 'find /etc/rc*.d -name S*' => cmd.call('find-etc-rc-d-name-S'), + 'find /etc/rc*.d -name S*' => cmd.call('find-etc-rc-d-name-S'), 'ls -1 /etc/init.d/' => cmd.call('ls-1-etc-init.d'), # user information for linux 'id root' => cmd.call('id-root'), 'getent passwd root' => cmd.call('getent-passwd-root'), - 'chage -l root' => cmd.call('chage-l-root'), + 'chage -l root' => cmd.call('chage-l-root'), # user info for mac 'id chartmann' => cmd.call('id-chartmann'), 'dscl -q . -read /Users/chartmann NFSHomeDirectory PrimaryGroupID RecordName UniqueID UserShell' => cmd.call('dscl'), @@ -138,7 +138,7 @@ class MockLoader # user info for windows '650b6b72a66316418b25421a54afe21a230704558082914c54711904bb10e370' => cmd.call('GetUserAccount'), # group info for windows - 'Get-WmiObject Win32_Group | Select-Object -Property Caption, Domain, Name, SID, LocalAccount | ConvertTo-Json' => cmd.call('GetWin32Group'), + 'Get-WmiObject Win32_Group | Select-Object -Property Caption, Domain, Name, SID, LocalAccount | ConvertTo-Json' => cmd.call('GetWin32Group'), } # set os emulation From 153c670952ea2661fb9ba27049d8bdccd03dfd84 Mon Sep 17 00:00:00 2001 From: Christoph Hartmann Date: Thu, 8 Oct 2015 12:11:55 +0200 Subject: [PATCH 4/5] introduce better network interface abstraction, add test cases --- lib/resources/interface.rb | 42 ++++++++++++++++++--------- test/helper.rb | 8 +++++ test/unit/mock/cmd/find-net-interface | 9 ++++++ test/unit/resource_interface_test.rb | 24 +++++++++++++++ 4 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 test/unit/mock/cmd/find-net-interface create mode 100644 test/unit/resource_interface_test.rb diff --git a/lib/resources/interface.rb b/lib/resources/interface.rb index ffb777c91..36a3e180e 100644 --- a/lib/resources/interface.rb +++ b/lib/resources/interface.rb @@ -12,13 +12,10 @@ require 'utils/convert' class NetworkInterface < Vulcano.resource(1) - include Converter - name 'interface' def initialize(iface) @iface = iface - @cache = nil @interface_provider = nil if vulcano.os.linux? @@ -29,31 +26,28 @@ class NetworkInterface < Vulcano.resource(1) end def exists? - !interface_info.nil? + !interface_info.nil? && !interface_info[:name].nil? end def up? - return false if interface_info.nil? || !interface_info.key?('operstate') - key, _value = interface_info['operstate'].first - key == 'up' + interface_info.nil? ? false : interface_info[:up] end # returns link speed in Mbits/sec def speed - return nil if interface_info.nil? || !interface_info.key?('speed') - key, _value = interface_info['speed'].first - convert_to_i(key) + interface_info.nil? ? nil : interface_info[:speed] end private def interface_info - return @cache if !@cache.nil? + return @cache if defined?(@cache) @cache = @interface_provider.interface_info(@iface) if !@interface_provider.nil? end end class InterfaceInfo + include Converter def initialize(vulcano) @vulcano = vulcano end @@ -61,12 +55,34 @@ end class LinuxInterface < InterfaceInfo def interface_info(iface) - return @cache if !@cache.nil? # will return "[mtu]\n1500\n[type]\n1" cmd = @vulcano.command("find /sys/class/net/#{iface}/ -type f -maxdepth 1 -exec sh -c 'echo \"[$(basename {})]\"; cat {} || echo -n' \\;") return nil if cmd.exit_status.to_i != 0 # parse values, we only recieve values, therefore we threat them as keys - @cache = SimpleConfig.new(cmd.stdout.chomp).params + params = SimpleConfig.new(cmd.stdout.chomp).params + + # abort if we got an empty result-set + return nil if params.empty? + + # parse state + state = false + if params.key?('operstate') + operstate, _value = params['operstate'].first + state = operstate == 'up' + end + + # parse speed + speed = nil + if params.key?('speed') + speed, _value = params['speed'].first + speed = convert_to_i(speed) + end + + { + name: iface, + up: state, + speed: speed, + } end end diff --git a/test/helper.rb b/test/helper.rb index bb68f4f50..796ed1934 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -79,6 +79,11 @@ class MockLoader stdout = ::File.read(::File.join(scriptpath, '/unit/mock/cmd/'+x)) mock.mock_command(stdout, '', 0) } + + empty = lambda { + mock.mock_command('', '', 0) + } + mock.commands = { 'ps aux' => cmd.call('ps-aux'), 'type win_secpol.cfg' => cmd.call('secedit-export'), @@ -139,6 +144,9 @@ class MockLoader '650b6b72a66316418b25421a54afe21a230704558082914c54711904bb10e370' => cmd.call('GetUserAccount'), # group info for windows 'Get-WmiObject Win32_Group | Select-Object -Property Caption, Domain, Name, SID, LocalAccount | ConvertTo-Json' => cmd.call('GetWin32Group'), + # network interface + '9e80f048a1af5a0f6ab8a465e46ea5ed5ba6587e9b5e54a7a0c0a1a02bb6f663' => cmd.call('find-net-interface'), + 'c33821dece09c8b334e03a5bb9daefdf622007f73af4932605e758506584ec3f' => empty.call, } # set os emulation diff --git a/test/unit/mock/cmd/find-net-interface b/test/unit/mock/cmd/find-net-interface new file mode 100644 index 000000000..e9482e757 --- /dev/null +++ b/test/unit/mock/cmd/find-net-interface @@ -0,0 +1,9 @@ +[speed] +10000 +[phys_switch_id] +[duplex] +full +[address] +02:42:ac:11:00:11 +[operstate] +up diff --git a/test/unit/resource_interface_test.rb b/test/unit/resource_interface_test.rb new file mode 100644 index 000000000..bbd9fb6cd --- /dev/null +++ b/test/unit/resource_interface_test.rb @@ -0,0 +1,24 @@ +# encoding: utf-8 +# author: Christoph Hartmann +# author: Dominik Richter + +require 'helper' +require 'vulcano/resource' + +describe 'Vulcano::Resources::Group' do + + # ubuntu 14.04 + it 'verify interface on ubuntu' do + resource = MockLoader.new(:ubuntu1404).load_resource('interface', 'eth0') + _(resource.exists?).must_equal true + _(resource.up?).must_equal true + _(resource.speed).must_equal 10000 + end + + it 'verify invalid interface on ubuntu' do + resource = MockLoader.new(:ubuntu1404).load_resource('interface', 'eth1') + _(resource.exists?).must_equal false + _(resource.up?).must_equal false + _(resource.speed).must_equal nil + end +end From 9d92abf5244c1e14e77e8996852816c369bf6e59 Mon Sep 17 00:00:00 2001 From: Christoph Hartmann Date: Thu, 8 Oct 2015 13:01:09 +0200 Subject: [PATCH 5/5] add windows support to network adapter --- lib/resources/interface.rb | 34 ++++++++++++++++++++++++++++ test/helper.rb | 1 + test/unit/mock/cmd/Get-NetAdapter | 24 ++++++++++++++++++++ test/unit/resource_interface_test.rb | 30 ++++++++++++++++++++++++ 4 files changed, 89 insertions(+) create mode 100644 test/unit/mock/cmd/Get-NetAdapter diff --git a/lib/resources/interface.rb b/lib/resources/interface.rb index 36a3e180e..f65f97211 100644 --- a/lib/resources/interface.rb +++ b/lib/resources/interface.rb @@ -20,6 +20,8 @@ class NetworkInterface < Vulcano.resource(1) @interface_provider = nil if vulcano.os.linux? @interface_provider = LinuxInterface.new(vulcano) + elsif vulcano.os.windows? + @interface_provider = WindowsInterface.new(vulcano) else return skip_resource 'The `interface` resource is not supported on your OS yet.' end @@ -86,3 +88,35 @@ class LinuxInterface < InterfaceInfo } end end + +class WindowsInterface < InterfaceInfo + def interface_info(iface) + # gather all network interfaces + cmd = @vulcano.command('Get-NetAdapter | Select-Object -Property Name, InterfaceDescription, Status, State, MacAddress, LinkSpeed, ReceiveLinkSpeed, TransmitLinkSpeed, Virtual | ConvertTo-Json') + + # filter network interface + begin + net_adapter = JSON.parse(cmd.stdout) + rescue JSON::ParserError => _e + return nil + end + + # ensure we have an array of groups + net_adapter = [net_adapter] if !net_adapter.is_a?(Array) + + # select the requested interface + adapters = net_adapter.each_with_object([]) do |adapter, adapter_collection| + # map object + info = { + name: adapter['Name'], + up: adapter['State'] == 2, + speed: adapter['ReceiveLinkSpeed'] / 1000, + } + adapter_collection.push(info) if info[:name].casecmp(iface) == 0 + end + + return nil if adapters.size == 0 + warn "[Possible Error] detected multiple network interfaces with the name #{iface}" if adapters.size > 1 + adapters[0] + end +end diff --git a/test/helper.rb b/test/helper.rb index 796ed1934..847818330 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -147,6 +147,7 @@ class MockLoader # network interface '9e80f048a1af5a0f6ab8a465e46ea5ed5ba6587e9b5e54a7a0c0a1a02bb6f663' => cmd.call('find-net-interface'), 'c33821dece09c8b334e03a5bb9daefdf622007f73af4932605e758506584ec3f' => empty.call, + 'Get-NetAdapter | Select-Object -Property Name, InterfaceDescription, Status, State, MacAddress, LinkSpeed, ReceiveLinkSpeed, TransmitLinkSpeed, Virtual | ConvertTo-Json' => cmd.call('Get-NetAdapter'), } # set os emulation diff --git a/test/unit/mock/cmd/Get-NetAdapter b/test/unit/mock/cmd/Get-NetAdapter new file mode 100644 index 000000000..07aa11f77 --- /dev/null +++ b/test/unit/mock/cmd/Get-NetAdapter @@ -0,0 +1,24 @@ +[ + { + "Name": "vEthernet (Intel(R) PRO 1000 MT Network Connection - Virtual Switch)", + "InterfaceDescription": "Hyper-V Virtual Ethernet Adapter #2", + "Status": "Up", + "State": 2, + "MacAddress": "00-0C-29-E3-48-9B", + "LinkSpeed": "10 Gbps", + "ReceiveLinkSpeed": 10000000000, + "TransmitLinkSpeed": 10000000000, + "Virtual": true + }, + { + "Name": "Ethernet0", + "InterfaceDescription": "Intel(R) PRO/1000 MT Network Connection", + "Status": "Not Present", + "State": 3, + "MacAddress": "00-0C-29-E3-48-9B", + "LinkSpeed": "0 bps", + "ReceiveLinkSpeed": 0, + "TransmitLinkSpeed": 0, + "Virtual": false + } +] diff --git a/test/unit/resource_interface_test.rb b/test/unit/resource_interface_test.rb index bbd9fb6cd..cd70f6cec 100644 --- a/test/unit/resource_interface_test.rb +++ b/test/unit/resource_interface_test.rb @@ -21,4 +21,34 @@ describe 'Vulcano::Resources::Group' do _(resource.up?).must_equal false _(resource.speed).must_equal nil end + + it 'verify interface on windows' do + resource = MockLoader.new(:windows).load_resource('interface', 'ethernet0') + _(resource.exists?).must_equal true + _(resource.up?).must_equal false + _(resource.speed).must_equal 0 + end + + it 'verify interface on windows' do + resource = MockLoader.new(:windows).load_resource('interface', 'vEthernet (Intel(R) PRO 1000 MT Network Connection - Virtual Switch)') + _(resource.exists?).must_equal true + _(resource.up?).must_equal true + _(resource.speed).must_equal 10000000 + end + + it 'verify invalid interface on windows' do + resource = MockLoader.new(:windows).load_resource('interface', 'eth1') + _(resource.exists?).must_equal false + _(resource.up?).must_equal false + _(resource.speed).must_equal nil + end + + # undefined + it 'verify interface on unsupported os' do + resource = MockLoader.new(:undefined).load_resource('interface', 'eth0') + _(resource.exists?).must_equal false + _(resource.up?).must_equal false + _(resource.speed).must_equal nil + end + end