vagrant-test-subject 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog +2 -0
- data/LICENSE +29 -0
- data/README.md +33 -0
- data/lib/vagrant-test-subject.rb +148 -0
- data/lib/vagrant-test-subject/monkey-patches/rspec-http.rb +27 -0
- data/lib/vagrant-test-subject/os/omnios.rb +12 -0
- data/lib/vagrant-test-subject/os/redhat.rb +56 -0
- data/lib/vagrant-test-subject/ssh.rb +41 -0
- metadata +85 -0
data/Changelog
ADDED
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.
|
data/README.md
ADDED
@@ -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,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: []
|