auger 1.3.8 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -8,21 +8,21 @@ The primary goal of Auger is test-driven operations: unit testing for
8
8
  application admins. The library can also be used as a framework for
9
9
  implmenting automated tests.
10
10
 
11
- * these are the sorts of questions auger can answer:
11
+ These are the sorts of questions auger can answer:
12
12
 
13
- * is port :80 on my application webservers open? does /index.html
13
+ * is port :80 on my application webservers open? does /index.html
14
14
  contain a response tag that we know should be served from a given
15
15
  backend data source?
16
16
 
17
- * is redis running? is it configured as a master? a slave?
17
+ * is redis running? is it configured as a master? a slave?
18
18
 
19
- * is elasticsearch responding on all my hosts it should be? what's
19
+ * is elasticsearch responding on all my hosts it should be? what's
20
20
  the cluster state? do I have the number of data nodes responding
21
21
  that we're supposed to have?
22
22
 
23
- * clearly a lot of this information includes things you should be
24
- graphing. What auger wants to do is give you a quick overview
25
- of current status: green == good, red == ruh roh!
23
+ Clearly a lot of this information includes things you should be
24
+ graphing. What auger wants to do is give you a quick overview
25
+ of current status: green == good, red == ruh roh!
26
26
 
27
27
  ## Plugins
28
28
 
@@ -30,43 +30,38 @@ Specific protocols are implemented using plugins, which are designed
30
30
  to be easy to write wrappers on existing gems. Auger currently includes
31
31
  the following plugins:
32
32
 
33
- * http - http and https requests using `net/http`
34
- * telnet - send arbitrary commands to a port using `net/telnet`
35
33
  * socket - test whether a port is open
34
+ * telnet - send arbitrary commands to a port using `net/telnet`
35
+ * http - http and https requests using `net/http`
36
+ * redis - query redis db info, keys, etc
37
+ * dns - domain and query requests for testing a DNS server
36
38
 
37
39
  ## Installation
38
40
 
39
- * gem install auger
41
+ gem install auger
40
42
 
41
43
  ### If you want to run the latest source:
42
44
 
43
- * `git clone git@github.com/blah/auger` TODO => fix github url
44
- * `cd auger; bundle install && rake install`
45
+ git clone git@github.com/brewster/auger
46
+ cd auger; bundle install && rake install
45
47
 
46
48
  ## Command-line client usage
47
49
 
48
- * sample configs included in cfg/examples/ can be moved into cfg/ and
49
- then run via `aug redis` etc.
50
-
51
- * if you've installed as a gem, the examples will be located wherever your gems get installed
50
+ `aug -h` will print usage details.
52
51
 
53
- * one quick way to find them should be `cd $GEM_HOME/gems/auger-x.x.x/cfg/examples`
52
+ Sample configs are included in `auger/cfg/examples` (if installed as a
53
+ gem, look in `$GEM_HOME/gems/auger-x.x.x/cfg/examples`). Use them as a
54
+ basis to write your own tests, which auger can find in one of three ways:
54
55
 
55
- * alternatively, you can place your configs anywhere you'd like and
56
- set the env_var: `AUGER_CFG=/path/to/your/configs/prod:/path/to/your/configs/stage`
56
+ * in the directory `auger/cfg`
57
+ * from the config load path defined in environment variable `AUGER_CFG`.
58
+ e.g. `AUGER_CFG=~/aug_cfg/prod:~/aug_cfg/staging`
59
+ * using a relative path
57
60
 
58
- * now call your tests via `aug name_of_my_config`, e.g. `aug redis`
61
+ Reference config files using `aug foo`, to find `foo.rb` in
62
+ `auger/cfg` or `AUGER_CFG` path, or `aug path/to/foo.rb` to find it directly.
59
63
 
60
- * configs should take the format `name.rb`
61
-
62
- * `aug -l` will print available tests
63
- * `aug -h` will print usage details
64
-
65
- ## Writing tests
66
-
67
- Tests are written as ruby code describing a test configuration. Files
68
- containing tests should be placed in the path described by the `AUGER_CFG`
69
- environment variable.
64
+ `aug -l` will print available config files in `AUGER_CFG` path.
70
65
 
71
66
  ### Example 1 - testing a webserver response
72
67
 
@@ -194,7 +189,7 @@ a checkmark or an 'x'.
194
189
  require 'json'
195
190
 
196
191
  project "Elasticsearch" do
197
- servers 'prod-es-[01-04]'
192
+ server 'prod-es-[01-04]'
198
193
 
199
194
  http 9200 do
200
195
  get "/_cluster/health" do
@@ -341,13 +336,15 @@ ZSH completion:
341
336
 
342
337
  ## Pull Requests
343
338
 
344
- * yes please
345
- * new plugins and genereal bug fixes, updates, etc are all welcome
346
- * generally, we'd prefer you do the following to submit a pull:
347
- * fork
348
- * create a local topic branch
349
- * make your changes and push
350
- * submit your pull request
339
+ Yes please.
340
+
341
+ New plugins and general bug fixes, updates, etc are all welcome.
342
+
343
+ Generally, we'd prefer you do the following to submit a pull:
344
+ * fork
345
+ * create a local topic branch
346
+ * make your changes and push
347
+ * submit your pull request
351
348
 
352
349
  ## License
353
350
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.8
1
+ 1.4.0
data/bin/aug CHANGED
@@ -14,7 +14,7 @@ require 'auger'
14
14
 
15
15
  ## set opts
16
16
  options = {}
17
- optparse = OptionParser.new do |opts|
17
+ OptionParser.new do |opts|
18
18
  opts.banner = "Usage: aug [-h|--help] [-l|--list] [-v|--version] cfg"
19
19
 
20
20
  if ARGV[0] == nil
@@ -22,50 +22,33 @@ optparse = OptionParser.new do |opts|
22
22
  exit
23
23
  end
24
24
 
25
- opts.on('-l', '--list', 'List available configs and exit.') do
26
- list = AUGER_CFG.map do |dir|
27
- Dir["#{dir}/*.rb"].map{ |file| File.basename(file).sub(/\.rb$/, '') }
28
- end
29
- puts list.flatten.sort
30
- exit
31
- end
32
-
33
25
  opts.on('-h', '--help', 'Display help') do
34
26
  puts opts
35
27
  exit
36
28
  end
37
29
 
38
- opts.on('-s', '--server REGEX', 'Limit to server names matching regex.') do |s|
39
- options[:server] = s
40
- end
41
-
42
- opts.on('-t', '--time', 'Show request times.') do |t|
43
- options[:time] = true
44
- end
45
-
46
30
  opts.on('-v', '--version', 'Display version and exit.') do
47
- puts Auger::VERSION.color(:green)
31
+ puts Auger::VERSION
48
32
  exit
49
33
  end
50
- end
51
- optparse.parse!
52
-
53
- ## cfg file can be e.g. 'imagine' or relative path
54
- cfg =
55
- if File.exists?(ARGV[0])
56
- ARGV[0]
57
- elsif path = AUGER_CFG.find { |path| File.exists?("#{path}/#{ARGV[0]}.rb") }
58
- [path, "#{ARGV[0]}.rb"].join(File::SEPARATOR)
59
- else
60
- raise ArgumentError, "config #{ARGV[0]} not found"
34
+
35
+ opts.on('-l', '--list', 'List available configs and exit.') do
36
+ puts(AUGER_CFG.map do |dir|
37
+ Dir["#{dir}/*.rb"].map{ |file| File.basename(file).sub(/\.rb$/, '') }
38
+ end.flatten.sort)
39
+ exit
61
40
  end
41
+ end.parse!
42
+
43
+ ## load plugins
44
+ Dir["#{File.dirname(File.dirname(__FILE__))}/lib/plugins/*.rb"].each { |file| require file }
62
45
 
63
46
  ## pretty-print Result object
64
47
  module Auger
65
48
  class Result
66
49
 
67
50
  def format
68
- output =
51
+ output =
69
52
  case self.outcome
70
53
  when TrueClass then "\u2713"
71
54
  when MatchData then outcome.captures.empty? ? "\u2713" : outcome.captures.join(' ')
@@ -88,48 +71,61 @@ module Auger
88
71
  end
89
72
  else :green
90
73
  end
91
-
74
+
92
75
  return output.color(color)
93
76
  end
94
77
  end
95
78
  end
96
79
 
80
+ ## find cfg in path, or just use local file
81
+ cfg = AUGER_CFG.map do |path|
82
+ File.join(path, "#{ARGV[0]}.rb")
83
+ end.find do |file|
84
+ File.exists?(file)
85
+ end || ARGV[0]
86
+
87
+ ## load config file and loop projects
97
88
  Auger::Config.load(cfg).projects.each do |project|
98
89
 
99
- threads = Hash.new { |h,k| h[k] = [] }
100
-
101
- ## run tests
102
- project.connections.each do |connection|
103
- servers = project.servers(connection.roles)
104
- servers.select! { |server| server.name.match(options[:server]) } if options[:server]
105
- servers.map do |server|
106
- threads[server.name] << Thread.new do
107
- conn = connection.do_open(server)
108
- connection.requests.map do |request|
109
- response, time = request.do_run(conn)
90
+ servers = project.servers.map do |server|
91
+
92
+ pipes = project.connections(*server.roles).map do |connection|
93
+ read, write = IO.pipe
94
+
95
+ Process.fork do
96
+ read.close # child does not need
97
+ conn = connection.try_open(server)
98
+
99
+ responses = connection.requests.map do |request|
100
+ response, time = request.try_run(conn)
110
101
  request.tests.map do |test|
111
- result = test.run(response)
112
- result.time = time
113
- result
102
+ [ test.name, test.run(response).format ]
114
103
  end
115
104
  end
105
+
106
+ Marshal.dump(responses, write)
107
+ connection.try_close(conn)
116
108
  end
109
+
110
+ write.close # parent does not need
111
+ read # return the pipe
117
112
  end
113
+
114
+ [ server, pipes ] # return server and its read pipes
118
115
  end
119
116
 
117
+ Process.waitall
118
+
120
119
  ## width of test name column
121
- max_test_length =
122
- project.connections.map{|c| c.requests.map{|r| r.tests.map{|t| t.name.length}}}.flatten.max
120
+ max_test_length = project.tests.map { |test| test.name.length }.max
123
121
 
124
122
  ## print results
125
- threads.keys.each do |server|
126
- puts "[#{server.color(:cyan)}]"
127
- threads[server].each do |thread|
128
- results = thread.value # this waits on thread
129
- results.flatten.each do |result|
130
- output = " %+#{max_test_length}s %-30s" % [result.test.name, result.format]
131
- output << "[#{result.time}]".color(:blue) if options[:time]
132
- puts output
123
+ servers.each do |server, pipes|
124
+ puts "[#{server.name.color(:cyan)}]"
125
+
126
+ pipes.each do |pipe|
127
+ Marshal.load(pipe).flatten(1).each do |test, result|
128
+ puts " %+#{max_test_length}s %-30s" % [ test, result ]
133
129
  end
134
130
  end
135
131
  end
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ ## this is the simplest auger client, written for educational purposes;
5
+ ## not intended for actual use
6
+
7
+ AUGER_DIR = File.dirname(File.dirname(__FILE__))
8
+ AUGER_LIB = File.join(AUGER_DIR, 'lib')
9
+
10
+ ## relative path to libs (in case not installed as a gem)
11
+ $LOAD_PATH.unshift(AUGER_LIB) unless $LOAD_PATH.include?(AUGER_LIB)
12
+ require "auger"
13
+
14
+ ## load plugins
15
+ Dir["#{File.dirname(File.dirname(__FILE__))}/lib/plugins/*.rb"].each { |file| require file }
16
+
17
+ ## load config file and loop projects
18
+ Auger::Config.load(ARGV[0]).projects.each do |project|
19
+
20
+ project.servers.each do |server|
21
+ puts "[#{server.name}]"
22
+
23
+ project.connections(*server.roles).each do |connection|
24
+ conn = connection.try_open(server)
25
+
26
+ connection.requests.each do |request|
27
+ response, time = request.try_run(conn)
28
+
29
+ request.tests.each do |test|
30
+ result = test.run(response)
31
+ puts " #{test.name}: #{result.outcome}"
32
+ end
33
+
34
+ end
35
+
36
+ connection.try_close(conn)
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -30,7 +30,7 @@ module Auger
30
30
  end
31
31
 
32
32
  ## call plugin open() and return plugin-specific connection object, or exception
33
- def do_open(server)
33
+ def try_open(server)
34
34
  options = @options.merge(server.options)
35
35
  begin
36
36
  self.open(server.name, options)
@@ -39,6 +39,15 @@ module Auger
39
39
  end
40
40
  end
41
41
 
42
+ ## safe way to call plugin close() (rescue if the connection did not exist)
43
+ def try_close(conn)
44
+ begin
45
+ self.close(conn)
46
+ rescue => e
47
+ e
48
+ end
49
+ end
50
+
42
51
  end
43
52
 
44
53
  end
@@ -3,7 +3,7 @@ require 'host_range'
3
3
  module Auger
4
4
 
5
5
  class Project
6
- attr_accessor :name, :fqdns, :hosts, :connections, :roles
6
+ attr_accessor :name, :connections, :servers
7
7
 
8
8
  def self.load(name, &block)
9
9
  project = new(name)
@@ -13,51 +13,44 @@ module Auger
13
13
 
14
14
  def initialize(name)
15
15
  @name = name
16
- @hosts = []
17
- @fqdns = []
16
+ @servers = []
18
17
  @connections = []
19
- @roles = Hash.new { |h,k| h[k] = [] }
20
18
  self
21
19
  end
22
20
 
23
- def role(name, *args)
24
- options = args.last.is_a?(Hash) ? args.pop : {}
25
- servers = args.map { |arg| HostRange.parse(arg) }.flatten
26
- servers.each { |server| roles[name] << Auger::Server.new(server, options) }
27
- end
28
-
21
+ ## set server, or list of server names, with optional roles and options
22
+ ## e.g. server server1, server2, :roleA, :roleB, options => values
29
23
  def server(*args)
30
24
  options = args.last.is_a?(Hash) ? args.pop : {}
31
- roles = []
32
- servers = []
33
- args.each do |arg|
34
- case arg
35
- when Symbol then roles << arg
36
- when String then servers << arg
37
- else raise ArgumentError, "illegal argument to server: #{arg}"
38
- end
25
+ roles = args.select { |arg| arg.class == Symbol }
26
+ servers = args.select { |arg| arg.class == String }.map { |arg| HostRange.parse(arg) }
27
+ @servers += servers.flatten.map do |name|
28
+ Auger::Server.new(name, *roles, options)
39
29
  end
40
- roles = [nil] if roles.empty? # default role
41
- roles.each { |name| role(name, *servers, options) }
42
30
  end
43
31
 
44
- alias :hosts :server
45
-
46
- ## return array of servers for given array of roles (default to all)
47
- def servers(roles = [])
48
- (roles.empty? ? @roles.values : @roles.values_at(*roles))
49
- .flatten
32
+ ## get list of server objects (optionally matching list of roles)
33
+ def servers(*roles)
34
+ if roles.empty?
35
+ @servers
36
+ else
37
+ roles.map do |role|
38
+ @servers.select { |server| server.roles.include?(role) }
39
+ end.flatten.uniq
40
+ end
50
41
  end
51
42
 
52
- alias :host :hosts
53
-
54
- ## add fqdn or return list of fqdns
55
- def fqdns(*ranges)
56
- ranges.empty? ? @fqdns.flatten : @fqdns << [*ranges].map {|r| HostRange.parse(r)}
43
+ ## return all connections, or those matching list of roles;
44
+ ## connections with no roles match all, or find intersection with roles list
45
+ def connections(*roles)
46
+ if roles.empty?
47
+ @connections
48
+ else
49
+ @connections.select { |c| c.roles.empty? or !(c.roles & roles).empty? }
50
+ end
57
51
  end
58
52
 
59
- alias :fqdn :fqdns
60
-
53
+ ## return list of all test objects for this project
61
54
  def tests
62
55
  @connections.map do |connection|
63
56
  connection.requests.map do |request|
@@ -38,7 +38,7 @@ module Auger
38
38
  end
39
39
 
40
40
  ## call plugin run() and return plugin-specfic response object or exception
41
- def do_run(conn)
41
+ def try_run(conn)
42
42
  return conn if conn.is_a? Exception
43
43
  begin
44
44
  arg = @arg
@@ -1,11 +1,12 @@
1
1
  module Auger
2
2
 
3
3
  class Server
4
- attr_accessor :name, :options
4
+ attr_accessor :name, :options, :roles
5
5
 
6
- def initialize(name, options)
6
+ def initialize(name, *args)
7
7
  @name = name
8
- @options = options || {}
8
+ @options = args.last.is_a?(Hash) ? args.pop : {}
9
+ @roles = args
9
10
  end
10
11
 
11
12
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: auger
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.8
4
+ version: 1.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-10-05 00:00:00.000000000 Z
13
+ date: 2012-10-24 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: json
@@ -98,6 +98,7 @@ email:
98
98
  - heffergm@gmail.com
99
99
  executables:
100
100
  - aug
101
+ - aug_simple
101
102
  extensions: []
102
103
  extra_rdoc_files: []
103
104
  files:
@@ -109,6 +110,7 @@ files:
109
110
  - VERSION
110
111
  - auger.gemspec
111
112
  - bin/aug
113
+ - bin/aug_simple
112
114
  - cfg/examples/elasticsearch.rb
113
115
  - cfg/examples/redis.rb
114
116
  - cfg/examples/riak.rb
@@ -148,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
148
150
  version: '0'
149
151
  requirements: []
150
152
  rubyforge_project:
151
- rubygems_version: 1.8.23
153
+ rubygems_version: 1.8.24
152
154
  signing_key:
153
155
  specification_version: 3
154
156
  summary: App && infrastructure testing DSL