vagrant-test-subject 0.0.1

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.
@@ -0,0 +1,2 @@
1
+ 2013-04-17 0.0.1 Clinton Wolfe <clintoncwolfe at gmail dot com>
2
+ * Initial release
data/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ Copyright (c) 2013, OmniTI Computer Consulting, Inc.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are
6
+ met:
7
+
8
+ * Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+ * Redistributions in binary form must reproduce the above
11
+ copyright notice, this list of conditions and the following
12
+ disclaimer in the documentation and/or other materials provided
13
+ with the distribution.
14
+ * Neither the name OmniTI Computer Consulting, Inc. nor the names
15
+ of its contributors may be used to endorse or promote products
16
+ derived from this software without specific prior written
17
+ permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,33 @@
1
+ vagrant-test-subject
2
+ ====================
3
+
4
+ A wrapper around a Vagrant VM, to ease using it as a test subject from rspec and other ruby testing tools.
5
+
6
+ # Synopsis
7
+
8
+ describe "TrafficServer Service" do
9
+ before(:all) do
10
+ @vm = VagrantTestSubject::VM.attach()
11
+ end
12
+ it "should appear as a healthy service" do
13
+ @vm.should have_running_service("trafficserver")
14
+ end
15
+ it "should be listening on localhost:80" do
16
+ @vm.should be_listening_on_localhost(80)
17
+ end
18
+ it "should be listening on external_ip:80" do
19
+ @vm.should be_listening_on_external_ip(80)
20
+ end
21
+ it "should be the right process name on port 80" do
22
+ process = @vm.process_name_listening('127.0.0.1', 80)
23
+ process.should_not be_nil
24
+ process.should match(/\/opt\/ts\/bin\/traffic_manager/)
25
+ end
26
+ it "should respond with HTTP 200 to / on port 80" do
27
+ @vm.http_get('/').should be_http_ok
28
+ end
29
+ end
30
+
31
+ # Maturity
32
+
33
+ Alpha.
@@ -0,0 +1,148 @@
1
+
2
+ # Std lib
3
+ require 'tempfile'
4
+ require 'json'
5
+ require 'net/http'
6
+ require 'socket'
7
+ require 'timeout'
8
+
9
+ # Gemfiles
10
+ require 'net/ssh'
11
+ require 'rspec/http'
12
+
13
+ # Our precious self
14
+ require 'vagrant-test-subject/monkey-patches/rspec-http'
15
+ require 'vagrant-test-subject/ssh'
16
+ require 'vagrant-test-subject/os/redhat'
17
+ require 'vagrant-test-subject/os/omnios'
18
+
19
+ module VagrantTestSubject
20
+
21
+ class VM
22
+ attr_reader :ssh, :vbox_guid, :external_ip
23
+
24
+ # Factory method, reads VM type and then instantiates
25
+ def self.attach(vm_name = 'default')
26
+ os_type = VM.read_vbox_os_type(vm_name)
27
+
28
+ # Yecch
29
+ vbox_os_to_class = {
30
+ 'RedHat_64' => VagrantTestSubject::VM::RedHat,
31
+ 'Red Hat (64 bit)' => VagrantTestSubject::VM::RedHat,
32
+ 'OpenSolaris_64' => VagrantTestSubject::VM::OmniOS,
33
+ }
34
+
35
+ klass = vbox_os_to_class[os_type]
36
+ klass.new(vm_name)
37
+ end
38
+
39
+ def initialize(vm_name = 'default')
40
+ @vm_name = vm_name
41
+ @vbox_guid = VM.read_vbox_guid(vm_name)
42
+ @vm_info = `VBoxManage showvminfo #{@vbox_guid} --machinereadable`.split("\n")
43
+ init_port_map()
44
+ @ssh = VagrantTestSubject::SSH.new(vm_name)
45
+ end
46
+
47
+
48
+ #================================================#
49
+ # VM Info Methods
50
+ #================================================#
51
+ def list_internal_ports
52
+ return @port_map.keys.sort
53
+ end
54
+
55
+ def map_port(internal)
56
+ return @port_map[internal.to_i]
57
+ end
58
+
59
+ #================================================#
60
+ # Testing Helpers
61
+ #================================================#
62
+
63
+ # Returns a Net::HTTP::Response
64
+ def http_get (local_absolute_url)
65
+ uri = URI('http://localhost:' + self.map_port(80).to_s + local_absolute_url)
66
+ response = Net::HTTP.get_response(uri)
67
+ end
68
+ def https_get (local_absolute_url, opts={})
69
+ # TODO - set OpenSSL::SSL::VERIFY_PEER based on opts ?
70
+ uri = URI('http://localhost:' + self.map_port(443).to_s + local_absolute_url)
71
+ response = Net::HTTP.get_response(uri)
72
+ end
73
+
74
+ #================================================#
75
+ # Testing Predicates
76
+ #================================================#
77
+ def has_running_service? (service_name)
78
+ raise "pure virtual"
79
+ end
80
+
81
+ def listening_on_localhost? (vm_port)
82
+ vm_port = vm_port.to_i
83
+ self.listening_ports.find{|lp| ['127.0.0.1', '0.0.0.0', '*'].include?(lp[:ip]) && lp[:port] == vm_port }
84
+ end
85
+
86
+ def listening_on_external_ip? (vm_port)
87
+ vm_port = vm_port.to_i
88
+ self.listening_ports.find{|lp| [@external_ip, '0.0.0.0', '*'].include?(lp[:ip]) && lp[:port] == vm_port }
89
+ end
90
+
91
+ def has_matching_process_listening? (ip, vm_port, process_regex)
92
+ raise "pure virtual"
93
+ end
94
+
95
+ def listening_on_portmap?(vm_port)
96
+ host_port = map_port(vm_port)
97
+
98
+ # This doesn't work - always connects, even to a port that isn't listening.
99
+ return false
100
+
101
+ begin
102
+ #Timeout::timeout(1) do
103
+ begin
104
+ puts "Trying VM host port " + host_port.to_s
105
+ binding.pry
106
+ s = TCPSocket.new('127.0.0.1', host_port )
107
+ s.close
108
+ return true
109
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
110
+ return false
111
+ end
112
+ #end
113
+ #rescue Timeout::Error
114
+ end
115
+ return false
116
+ end
117
+
118
+ #================================================#
119
+ # Guts
120
+ #================================================#
121
+ protected
122
+ def self.read_vbox_guid(vm_name)
123
+ (JSON.parse(IO.read(".vagrant")))['active'][vm_name]
124
+ end
125
+
126
+ def self.read_vbox_os_type(vm_name)
127
+ vbox_guid = read_vbox_guid(vm_name)
128
+ output = `VBoxManage showvminfo #{vbox_guid} --machinereadable | grep ostype`
129
+ output[/".+"/].gsub('"','')
130
+ end
131
+
132
+ def init_port_map
133
+ @port_map = {}
134
+ @vm_info.grep(/Forwarding/) do |rule|
135
+ # Forwarding(0)="2g-183o,tcp,,41080,,80"
136
+ if /Forwarding\(\d+\)="[\w\-]+,(?<proto>\w+),,(?<ext>\d+),,(?<int>\d+)"/ =~ rule then
137
+ @port_map[int.to_i()] = ext.to_i()
138
+ end
139
+ end
140
+ end
141
+
142
+ def init_iface_list
143
+ # TODO - populate @external_ip
144
+ @external_ip = '10.0.2.15'
145
+ end
146
+
147
+ end
148
+ end
@@ -0,0 +1,27 @@
1
+ # What is wrong with you people
2
+ module RSpec
3
+ module Http
4
+ class ResponseCodeMatcher
5
+ # Override, this stupidly doesn't work with the stdlib net/http
6
+ def matches?(target)
7
+ @target = target
8
+ if @target.respond_to? :status then
9
+ return @target.status.to_s == @expected_code.to_s
10
+ elsif @target.respond_to? :code then
11
+ # Net::HTTPResponse
12
+ return @target.code.to_s == @expected_code.to_s
13
+ end
14
+ end
15
+
16
+ def common_message
17
+ status_code = (@target.respond_to? :status) ? @target.status : @target.code
18
+ message = "have a response code of #{@expected_code}, but got #{status_code}"
19
+ if status_code == 302 || status_code == 201
20
+ message += " with a location of #{@target['Location'] || @target['location']}"
21
+ end
22
+ message
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ module VagrantTestSubject
2
+ class VM
3
+ class OmniOS < VM
4
+
5
+ # This is one big TODO
6
+
7
+ def initialize(*args)
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,56 @@
1
+ module VagrantTestSubject
2
+ class VM
3
+ class RedHat < VM
4
+ @@SERVICE_NAMES = {
5
+ 'trafficserver' => 'trafficserver'
6
+ }
7
+
8
+ def initialize(*args)
9
+ super
10
+ end
11
+
12
+ def self.register_service_alias(human_name, os_specific_name)
13
+ @@SERVICE_NAMES[human_name] = os_specific_name
14
+ end
15
+
16
+ def has_running_service? (human_service_name)
17
+ redhat_service_name = @@SERVICE_NAMES[human_service_name] || human_service_name
18
+ output = self.ssh.exec!("/sbin/service #{redhat_service_name} status; echo exit_code:$?")
19
+ /exit_code:(?<exit_code>\d+)/ =~ output
20
+ return exit_code.to_i == 0
21
+ end
22
+
23
+ def listening_ports
24
+ cmd = "netstat -ln --inet | tail -n +3 | awk '{print $4}'"
25
+ open_ports = []
26
+ self.ssh.exec!(cmd).split("\n").each do |line|
27
+ ip, port = line.split(':')
28
+ next unless ip
29
+ open_ports << { :ip => ip, :port => port.to_i }
30
+ end
31
+ open_ports
32
+ end
33
+
34
+ def process_name_listening (ip, vm_port)
35
+ # Note the trailing space after port - prevents 127.0.0.1:80 from matching 127.0.0.1:8084
36
+ cmd = "sudo netstat -lnp --inet | grep '#{ip}:#{vm_port} ' | awk '{print $7}'"
37
+ output = self.ssh.exec!(cmd)
38
+ if output.nil? then
39
+ cmd = "sudo netstat -lnp --inet | grep '0.0.0.0:#{vm_port} ' | awk '{print $7}'"
40
+ output = self.ssh.exec!(cmd)
41
+ end
42
+ return nil if output.nil?
43
+ output.chomp!
44
+
45
+ pid, truncated_process = output.split('/');
46
+ unless pid =~ /^\d+$/ then
47
+ raise "Not sure what this means, when running netstat -nlp:\n#{output}"
48
+ end
49
+
50
+ cmd = "ps -fp #{pid} | tail -n +2 | awk '{print $8}'"
51
+ process_string = self.ssh.exec!(cmd).chomp()
52
+ return process_string
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,41 @@
1
+ module VagrantTestSubject
2
+
3
+ # Encapsulates an SSH channel into a VM
4
+ class SSH
5
+ def initialize(vm_name = 'default')
6
+ # Grab ssh config
7
+ config = `vagrant ssh-config`
8
+
9
+ # Write most of it to a file, grabbing the username and host
10
+ config_file = Tempfile.new("vagrant-ssh-conf")
11
+
12
+ host, username = nil, nil
13
+ config.split(/\n/).each do |line|
14
+ case line
15
+ when /^\s+User\s+(\w+)/
16
+ username = $1
17
+ when /^\s+HostName\s+(\S+)/
18
+ host = $1
19
+ when /^\s*Host\s+#{vm_name}/
20
+ # Ignore
21
+ else
22
+ config_file << line + "\n"
23
+ end
24
+ end
25
+ config_file.flush
26
+
27
+ # make a Net::SSH Session
28
+ # and delegate everything to it
29
+ @session = Net::SSH.start(host, username, :config => config_file.path)
30
+
31
+ config_file.close
32
+
33
+ end
34
+
35
+ def method_missing(*args, &block)
36
+ method_name = args.shift
37
+ @session.send method_name, *args, &block
38
+ end
39
+
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vagrant-test-subject
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Clinton Wolfe
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: net-ssh
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec-http
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Wrapper class for a Vagrant VM, providing access to testing predicates,
47
+ such as port map information, process data, ssh connections, and more.
48
+ email: clinton@NOSPAM.omniti.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - Changelog
54
+ - LICENSE
55
+ - README.md
56
+ - lib/vagrant-test-subject.rb
57
+ - lib/vagrant-test-subject/ssh.rb
58
+ - lib/vagrant-test-subject/monkey-patches/rspec-http.rb
59
+ - lib/vagrant-test-subject/os/redhat.rb
60
+ - lib/vagrant-test-subject/os/omnios.rb
61
+ homepage: https://github.com/clintoncwolfe/vagrant-test-subject
62
+ licenses: []
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 1.8.24
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Wrapper class for a Vagrant VM, providing access to testing predicates
85
+ test_files: []