vagrant-test-subject 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []