fog_tracker 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ config/accounts.yml
5
+ pkg/*
6
+ *~
7
+ .DS_Store
8
+ *.tmproj
9
+ db/*.sqlite3
10
+ log/*.log
11
+ tmp/**/*
12
+ doc
13
+ doc/**/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in fog_tracker.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,6 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rake', :task => 'spec' do
5
+ watch(%r{^*.rb})
6
+ end
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ Fog Tracker
2
+ ================
3
+ Uses the Fog gem to track the state of cloud computing resources across multiple accounts, with multiple service providers.
4
+
5
+ *BETA VERSION - needs functional testing with more cloud computing providers*
6
+
7
+
8
+ ----------------
9
+ What is it?
10
+ ----------------
11
+ The Fog Tracker uses the [Fog gem](https://github.com/fog/fog) to periodically poll one or more cloud computing accounts, and determines the state of their associated cloud computing "Resources": compute instances, disk volumes, stored objects, and so on. The most recent state of all Resources is saved in memory (as Fog objects), and can be accessed repeatedly with no network overhead, using a simple, Regular-Expression-based query.
12
+
13
+
14
+ ----------------
15
+ Why is it?
16
+ ----------------
17
+ The Fog Tracker is intended to be a foundation library, on top of which more complex cloud dashboard or management applications can be built. It allows such applications to decouple their requests to cloud service providers from their access to the results of those requests.
18
+
19
+
20
+ ----------------
21
+ Where is it? (Installation)
22
+ ----------------
23
+ Install the Fog Tracker gem (and its dependencies if necessary) from RubyGems
24
+
25
+ gem install fog_tracker [rake bundler]
26
+
27
+
28
+ ----------------
29
+ How is it [done]? (Usage)
30
+ ----------------
31
+ 1) Just require the gem, and create a `FogTracker::Tracker`. Pass it some account information in a hash, perhaps loaded from a YAML file:
32
+
33
+ require 'fog_tracker'
34
+ tracker = FogTracker::Tracker.new(YAML::load(File.read 'accounts.yml'))
35
+ tracker.start
36
+
37
+ Here are the contents of a sample `accounts.yml`:
38
+
39
+ AWS EC2 development account:
40
+ :provider: AWS
41
+ :service: Compute
42
+ :credentials:
43
+ :aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
44
+ :aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
45
+ :polling_time: 180
46
+ :exclude_resources:
47
+ - :flavors
48
+ - :images
49
+ Rackspace development account:
50
+ :provider: Rackspace
51
+ :service: Compute
52
+ :credentials:
53
+ :rackspace_api_key: XXXXXXXXXXXXXXXXXXXX
54
+ :rackspace_username: XXXXXXXXX
55
+ :polling_time: 180
56
+
57
+ 2) The tracker will run asynchronously, with one thread per account. You can call `start()` and `stop()` on it, and query the resulting collections of Fog Resource objects using a filter-based query:
58
+
59
+ # get all Compute instances across all accounts and providers
60
+ tracker.query("*::Compute::*::servers")
61
+
62
+ # get all Amazon EC2 Resources, of all types, across all accounts
63
+ tracker["*::Compute::AWS::*"] # the [] operator is the same as query()
64
+
65
+ # get all S3 objects in a given account
66
+ tracker["my production account::Storage::AWS::files"]
67
+
68
+ The query string format is: "`account name::service::provider::collection`"
69
+
70
+ You can also pass a Proc to the Tracker at initialization, which will be invoked whenever an account's Resources have been updated -- see the API docs for details.
71
+
72
+
73
+ ----------------
74
+ Who is it? (Contribution / Development)
75
+ ----------------
76
+ This Gem was created by Benton Roberts _(benton@bentonroberts.com)_ The project is still in its early stages, and needs to be tested with many more of Fog's cloud providers. Helping hands are appreciated!
77
+
78
+ 1) Install project dependencies.
79
+
80
+ gem install rake bundler
81
+
82
+ 2) Fetch the project code and bundle up...
83
+
84
+ git clone https://github.com/benton/fog_tracker.git
85
+ cd fog_tracker
86
+ bundle
87
+
88
+ 3) Run the tests:
89
+
90
+ rake
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ # Set up bundler
2
+ %w{rubygems bundler bundler/gem_tasks}.each {|dep| require dep}
3
+ Bundler.setup(:default, :test, :development)
4
+
5
+ # Load all tasks from 'lib/tasks'
6
+ Dir["#{File.dirname(__FILE__)}/lib/tasks/*.rake"].sort.each {|ext| load ext}
7
+
8
+ desc 'Default: run specs.'
9
+ task :default => :spec
data/bin/tracker ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # == Synopsis
4
+ # Uses the Fog gem to track the status of cloud computing resources
5
+ #
6
+ # == Usage
7
+ # tracker.rb [options] ACCOUNTS_CONFIG_FILE.YML
8
+ #
9
+ # == Options (all options can be put into the config file)
10
+ # -d, --delay [INTEGER] Seconds between status updates. default = 180
11
+ # -l, --log-level [LEVEL] Sets Log4r level for console output. default = INFO
12
+ # -h, --help Displays help message
13
+ #
14
+ # == Author
15
+ # Benton Roberts
16
+
17
+ require 'optparse'
18
+ require 'fog_tracker'
19
+ LOG_LVLS = {
20
+ "DEBUG" => ::Logger::DEBUG,
21
+ "INFO" => ::Logger::INFO,
22
+ "WARN" => ::Logger::WARN,
23
+ "ERROR" => ::Logger::ERROR,
24
+ "FATAL" => ::Logger::FATAL
25
+ }
26
+
27
+ module FogTracker
28
+ class FogTrackerConsoleApp
29
+
30
+ def initialize
31
+ @log = FogTracker.default_logger(STDOUT)
32
+ parse_options
33
+ @log.info "Loading account information from #{ARGV[0]}"
34
+ @accounts = YAML::load(File.open(ARGV[0]))
35
+ @tracker = FogTracker::Tracker.new(
36
+ @accounts, {:logger => @log, :delay => @opts[:delay]}
37
+ )
38
+ end
39
+
40
+ def go
41
+ @tracker.start
42
+ while true do
43
+ sleep 60 # Loop forever
44
+ end
45
+ end
46
+
47
+ def parse_options
48
+ @opts = {:log_level => 'INFO'}
49
+ optparse = OptionParser.new do |opts|
50
+ opts.banner = "Usage: tracker [options] ACCOUNTS.YML"
51
+ opts.on('-d', '--delay SECONDS', Integer,
52
+ 'Number of seconds between status updates') do |delay|
53
+ @opts[:delay] = delay
54
+ end
55
+ opts.on('-l', '--log-level LEVEL', 'Set logging level') do |log_level|
56
+ @opts[:log_level] = log_level.upcase
57
+ end
58
+ opts.on('-h', '--help', 'Display this help message') do
59
+ puts opts ; exit
60
+ end
61
+ end
62
+ optparse.parse!
63
+ @log.level = LOG_LVLS[@opts[:log_level]] if LOG_LVLS[@opts[:log_level]]
64
+ if ARGV.count < 1
65
+ @log.error "A YAML file with account info must be specified"
66
+ exit 1
67
+ end
68
+ end
69
+
70
+ end
71
+ end
72
+
73
+ FogTracker::FogTrackerConsoleApp.new.go
@@ -0,0 +1,25 @@
1
+ AWS EC2 production account:
2
+ :provider: AWS
3
+ :service: Compute
4
+ :credentials:
5
+ :aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
6
+ :aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
7
+ :polling_time: 120
8
+ :exclude_resources:
9
+ AWS EC2 development account:
10
+ :provider: AWS
11
+ :service: Compute
12
+ :credentials:
13
+ :aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
14
+ :aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
15
+ :polling_time: 180
16
+ :exclude_resources:
17
+ - :flavors
18
+ - :images
19
+ Rackspace development account:
20
+ :provider: Rackspace
21
+ :service: Compute
22
+ :credentials:
23
+ :rackspace_api_key: XXXXXXXXXXXXXXXXXXXX
24
+ :rackspace_username: XXXXXXXXX
25
+ :polling_time: 180
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "fog_tracker/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "fog_tracker"
7
+ s.version = FogTracker::VERSION
8
+ s.authors = ["Benton Roberts"]
9
+ s.email = ["benton@bentonroberts.com"]
10
+ s.homepage = "http://github.com/benton/fog_tracker"
11
+ s.summary = %q{Tracks the state of cloud computing resources across }+
12
+ %q{multiple accounts with multiple service providers}
13
+ s.description = %q{This gem peridically polls mutiple cloud computing }+
14
+ %q{services using the fog gem, asynchronously updating the }+
15
+ %q{state of the resulting collections of Fog Resources.}
16
+
17
+ s.rubyforge_project = "fog_tracker"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+
24
+ # Runtime dependencies
25
+ s.add_dependency "fog"
26
+
27
+ # Development / Test dependencies
28
+ s.add_development_dependency "rake"
29
+ s.add_development_dependency "rspec"
30
+ s.add_development_dependency "rdoc"
31
+ s.add_development_dependency "guard"
32
+ s.add_development_dependency "guard-rake"
33
+ s.add_development_dependency "ruby_gntp"
34
+ end
@@ -0,0 +1,23 @@
1
+ require 'logger'
2
+
3
+ # Load all ruby files from 'fog_tracker' directory
4
+ Dir[File.join(File.dirname(__FILE__), "fog_tracker/**/*.rb")].each {|f| require f}
5
+
6
+ module FogTracker
7
+
8
+ # The default polling interval in seconds
9
+ # This. can be overriden when a FogTracker::Tracker is created, either
10
+ # in the +accounts+ definitions, or in the +options+ parameter
11
+ DEFAULT_POLLING_TIME = 300 # by default, poll all accounts every 5 minutes
12
+
13
+ # Returns a slightly-modified version of the default Ruby Logger
14
+ def self.default_logger(output = nil)
15
+ logger = ::Logger.new(output)
16
+ logger.sev_threshold = Logger::INFO
17
+ logger.formatter = proc {|lvl, time, prog, msg|
18
+ "#{lvl} #{time.strftime '%Y-%m-%d %H:%M:%S %Z'}: #{msg}\n"
19
+ }
20
+ logger
21
+ end
22
+
23
+ end
@@ -0,0 +1,96 @@
1
+ module FogTracker
2
+
3
+ # Tracks a single Fog account in an ActiveRecord database
4
+ class AccountTracker
5
+ require 'fog'
6
+
7
+ attr_reader :name, :account, :log, :delay
8
+ attr_reader :resource_trackers
9
+
10
+ # Creates an object for tracking a single Fog account
11
+ #
12
+ # ==== Attributes
13
+ #
14
+ # * +account_name+ - a human-readable name for the account (String)
15
+ # * +account+ - a Hash of account information (see accounts.yml.example)
16
+ # * +options+ - Hash of optional parameters
17
+ #
18
+ # ==== Options
19
+ #
20
+ # * +:delay+ - Default time between polling of accounts
21
+ # * +:callback+ - A Method or Proc to call each time an account is polled.
22
+ # It should take the name of the account as its only required parameter
23
+ # * +:logger+ - a Ruby Logger-compatible object
24
+ def initialize(account_name, account, options={})
25
+ @name = account_name
26
+ @account = account
27
+ @callback = options[:callback]
28
+ @log = options[:logger] || FogTracker.default_logger
29
+ @delay = options[:delay] || account[:polling_time] ||
30
+ FogTracker::DEFAULT_POLLING_TIME
31
+ @log.debug "Creating tracker for account #{@name}."
32
+ create_resource_trackers
33
+ end
34
+
35
+ # Starts a background thread, which updates all @resource_trackers
36
+ def start
37
+ if not running?
38
+ @log.debug "Starting tracking for account #{@name}..."
39
+ @timer = Thread.new do
40
+ begin
41
+ while true do
42
+ @log.info "Polling account #{@name}..."
43
+ @resource_trackers.each {|tracker| tracker.update}
44
+ @callback.call @name if @callback
45
+ sleep @delay
46
+ end
47
+ rescue Exception => e
48
+ @log.error "Exception polling account #{name}: #{e.message}"
49
+ @log.error e.backtrace.join("\n")
50
+ exit 99
51
+ end
52
+ end
53
+ else
54
+ @log.info "Already tracking account #{@name}"
55
+ end
56
+ end
57
+
58
+ # Stops all the @resource_trackers
59
+ def stop
60
+ if running?
61
+ @log.info "Stopping tracker for #{name}..."
62
+ @timer.kill
63
+ @timer = nil
64
+ else
65
+ @log.info "Tracking already stopped for account #{@name}"
66
+ end
67
+ end
68
+
69
+ # Returns true or false depending on whether this tracker is polling
70
+ def running? ; @timer != nil end
71
+
72
+ # Returns a Fog::Connection object to this account's Fog service
73
+ def connection
74
+ service_mod = ::Fog::const_get @account[:service]
75
+ provider_class = service_mod.send(:const_get, @account[:provider])
76
+ @fog_service ||= provider_class.new(@account[:credentials])
77
+ end
78
+
79
+ # Returns an Array of resource types (Strings) to track
80
+ def tracked_types
81
+ types = connection.collections - (account[:exclude_resources] || [])
82
+ types.map {|type| type.to_s}
83
+ end
84
+
85
+ private
86
+
87
+ # Creates and returns an Array of ResourceTracker objects -
88
+ # one for each resource type associated with this account's service
89
+ def create_resource_trackers
90
+ @resource_trackers = tracked_types.map do |fog_collection_name|
91
+ FogTracker::ResourceTracker.new(fog_collection_name.to_s, self)
92
+ end
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,95 @@
1
+ module FogTracker
2
+ module Query
3
+ class QueryProcessor
4
+
5
+ QUERY_PATTERN = %r{(.*)::(.*)::(.*)::(.*)}
6
+
7
+ # Creates an object for filtering Resources from a set of AccountTrackers
8
+ #
9
+ # ==== Attributes
10
+ #
11
+ # * +trackers+ - a Hash of AccountTrackers, indexed by Account name
12
+ # * +options+ - Hash of optional parameters
13
+ #
14
+ # ==== Options
15
+ #
16
+ # * +:logger+ - a Ruby Logger-compatible object
17
+ def initialize(trackers, options={})
18
+ @trackers = trackers
19
+ @log = options[:logger] || FogTracker.default_logger
20
+ end
21
+
22
+ # Returns an Array of Resources, filtered by +query+
23
+ def execute(query)
24
+ acct_pattern, svc_pattern, prov_pattern, col_pattern = parse_query(query)
25
+ results_by_account = get_results_by_account(acct_pattern)
26
+ results = filter_by_service(results_by_account, svc_pattern)
27
+ results = filter_by_provider(results, prov_pattern)
28
+ results = filter_by_collection(results, col_pattern)
29
+ end
30
+
31
+ private
32
+
33
+ # Returns an Array of 4 RegEx objeccts based on the +query_string+
34
+ # for matching [account name, service, provider, collection]
35
+ def parse_query(query_string)
36
+ @log.debug "Parsing Query #{query_string}"
37
+ tokenize(query_string).map {|token| regex_from_token(token)}
38
+ end
39
+
40
+ # Returns an array of 4 String tokens by splitting +query_string+
41
+ def tokenize(query_string)
42
+ query_string.strip!
43
+ if match = query_string.match(QUERY_PATTERN)
44
+ match.captures
45
+ else
46
+ raise "Bad query: '#{query_string}'"
47
+ end
48
+ end
49
+
50
+ # Converts a String +token+ into a RegEx for matching query values
51
+ def regex_from_token(token)
52
+ token = '.*' if token == '*' # a single wildcard is a special case
53
+ %r{^\s*#{token}\s*$}i # otherwise, interpret as a RegEx
54
+ end
55
+
56
+ # Returns a subset of all Resources, filtered only by +acct_name_pattern+
57
+ def get_results_by_account(acct_name_pattern)
58
+ results = Array.new
59
+ @trackers.each do |account_name, account_tracker|
60
+ if account_name.match acct_name_pattern
61
+ account_tracker.resource_trackers.each do |resource_tracker|
62
+ results += resource_tracker.collection
63
+ end
64
+ end
65
+ end
66
+ results
67
+ end
68
+
69
+ # filters an Array of Fog Resources by Service
70
+ def filter_by_service(resources, service_pattern)
71
+ resources.select do |resource|
72
+ service_name = resource.class.name.match(/Fog::(\w+)::/)[1]
73
+ service_name.match service_pattern
74
+ end
75
+ end
76
+
77
+ # filters an Array of Fog Resources by Provider
78
+ def filter_by_provider(resources, provider_pattern)
79
+ resources.select do |resource|
80
+ provider_name = resource.class.name.match(/Fog::\w+::(\w+)::/)[1]
81
+ provider_name.match provider_pattern
82
+ end
83
+ end
84
+
85
+ # filters an Array of Fog Resources by collection name (Resource Type)
86
+ def filter_by_collection(resources, collection_pattern)
87
+ resources.select do |resource|
88
+ collection_class = resource.collection.class.name.match(/::(\w+)$/)[1]
89
+ collection_class.to_underscore.match collection_pattern
90
+ end
91
+ end
92
+
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,33 @@
1
+ module FogTracker
2
+
3
+ # Tracks a single Fog Resource type for a single Account
4
+ class ResourceTracker
5
+
6
+ attr_accessor :collection
7
+
8
+ # Creates an object for tracking a single Fog account
9
+ #
10
+ # ==== Attributes
11
+ #
12
+ # * +resource_type+ - the Fog collection name (String) for this resource type
13
+ # * +account_tracker+ - the AccountTracker for this tracker's @collection
14
+ def initialize(resource_type, account_tracker)
15
+ @type = resource_type
16
+ @account_tracker = account_tracker
17
+ @account = account_tracker.account
18
+ @account_name = account_tracker.name
19
+ @log = account_tracker.log
20
+ @collection = Array.new
21
+ @log.debug "Created tracker for #{@type} on #{@account_name}."
22
+ end
23
+
24
+ # Polls the account's connection for updated info on all existing
25
+ # instances of the relevant resource type, and saves them as @collection
26
+ def update
27
+ @log.info "Polling #{@type} on #{@account_name}..."
28
+ @collection = @account_tracker.connection.send(@type)
29
+ @log.info "Discovered #{@collection.count} #{@type} on #{@account_name}."
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,91 @@
1
+ module FogTracker
2
+
3
+ # Tracks one or more Fog accounts and exposes a query() on the results
4
+ class Tracker
5
+
6
+ attr_accessor :accounts
7
+
8
+ # Creates an object for tracking Fog accounts
9
+ #
10
+ # ==== Attributes
11
+ #
12
+ # * +accounts+ - a Hash of account information (see accounts.yml.example)
13
+ # * +options+ - Hash of optional parameters
14
+ #
15
+ # ==== Options
16
+ #
17
+ # * +:delay+ - Time between polling of accounts. Overrides per-account value
18
+ # * +:callback+ - A Proc to call each time an account is polled.
19
+ # It should take the name of the account as its only required parameter
20
+ # * +:logger+ - a Ruby Logger-compatible object
21
+ def initialize(accounts = {}, options = {})
22
+ @accounts = accounts
23
+ @delay = options[:delay]
24
+ @callback = options[:callback]
25
+ @log = options[:logger] || FogTracker.default_logger
26
+ # Create a Hash that maps account names to AccountTrackers
27
+ create_trackers
28
+ end
29
+
30
+ # Invokes the start method on all the @trackers
31
+ def start
32
+ if not running?
33
+ @log.info "Tracking #{@trackers.keys.count} accounts..."
34
+ @trackers.each_value {|tracker| tracker.start}
35
+ @running = true
36
+ else
37
+ @log.info "Already tracking #{@trackers.keys.count} accounts"
38
+ end
39
+ end
40
+
41
+ # Invokes the stop method on all the @trackers
42
+ def stop
43
+ if running?
44
+ @log.info "Stopping tracker..."
45
+ @trackers.each_value {|tracker| tracker.stop}
46
+ @running = false
47
+ else
48
+ @log.info "Tracking already stopped"
49
+ end
50
+ end
51
+
52
+ # Returns true or false/nil depending on whether this tracker is polling
53
+ def running? ; @running end
54
+
55
+ # Returns an array of Resource types (Strings) for a given account
56
+ def types_for_account(account_name)
57
+ @trackers[account_name].tracked_types
58
+ end
59
+
60
+ # Returns an array of Resources matching the +query_string+
61
+ #
62
+ # ==== Attributes
63
+ #
64
+ # * +query_string+ - a String used to filter the discovered Resources
65
+ # it might look like: "Account Name::Compute::AWS::servers"
66
+ def query(query_string)
67
+ FogTracker::Query::QueryProcessor.new(
68
+ @trackers, :logger => @log
69
+ ).execute(query_string)
70
+ end
71
+ alias :[] :query
72
+
73
+ # Returns this tracker's logger, for changing logging dynamically
74
+ def logger
75
+ @log
76
+ end
77
+
78
+ private
79
+
80
+ # Creates a Hash of AccountTracker objects, indexed by account name
81
+ def create_trackers
82
+ @trackers = Hash.new
83
+ @accounts.each do |name, account|
84
+ @log.debug "Setting up tracker for account #{name}"
85
+ @trackers[name] = AccountTracker.new(name, account,
86
+ {:delay => @delay, :callback => @callback, :logger => @log})
87
+ end
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,10 @@
1
+ # taken from ActiveSupport
2
+ class String
3
+ def to_underscore
4
+ self.gsub(/::/, '/').
5
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
6
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
7
+ tr("-", "_").
8
+ downcase
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module FogTracker
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,6 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ desc "Run specs"
4
+ RSpec::Core::RakeTask.new do |t|
5
+ t.rspec_opts = ['--color', '--format documentation', '-r ./spec/spec_helper.rb']
6
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ module FogTracker
4
+
5
+ describe AccountTracker do
6
+
7
+ before(:each) do
8
+ @tracker = AccountTracker.new(
9
+ FAKE_ACCOUNT_NAME, FAKE_ACCOUNT, :logger => LOG
10
+ )
11
+ end
12
+
13
+ it "exposes its Hash of account information" do
14
+ @tracker.connection.should_not == nil
15
+ end
16
+ it "exposes the account name" do
17
+ @tracker.name.should == FAKE_ACCOUNT_NAME
18
+ end
19
+ it "exposes the connection to its Fog service" do
20
+ @tracker.account.should == FAKE_ACCOUNT
21
+ end
22
+ it "exposes the connection to its logger" do
23
+ @tracker.log.should_not == nil
24
+ end
25
+ it "exposes a collection of ResourceTrackers" do
26
+ @tracker.resource_trackers.size.should be > 0
27
+ end
28
+
29
+ describe '#start' do
30
+ it "sends update() to its ResourceTrackers" do
31
+ @tracker.resource_trackers.each do |resource_tracker|
32
+ resource_tracker.should_receive(:update)
33
+ end
34
+ @tracker.start
35
+ sleep THREAD_STARTUP_DELAY # wait for background thread to start
36
+ end
37
+ end
38
+
39
+ describe '#stop' do
40
+ it "sets running? to false" do
41
+ @tracker.start ; @tracker.stop
42
+ @tracker.running?.should be_false
43
+ end
44
+ it "kills its timer thread" do
45
+ @tracker.start ; @tracker.stop
46
+ @tracker.resource_trackers.first.should_not_receive(:update)
47
+ sleep THREAD_STARTUP_DELAY # wait to make sure no update()s are sent
48
+ end
49
+ end
50
+
51
+ describe '#running?' do
52
+ it "returns true if the AccountTracker is running" do
53
+ @tracker.start
54
+ @tracker.running?.should be_true
55
+ end
56
+ it "returns false if the AccountTracker is stopped" do
57
+ @tracker.running?.should be_false
58
+ end
59
+ end
60
+
61
+ describe '#tracked_types' do
62
+ it "returns a list of Resource types tracked for its account" do
63
+ @tracker.tracked_types.size.should be > 0
64
+ @tracker.tracked_types.first.should be_an_instance_of String
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ module FogTracker
4
+ module Query
5
+
6
+ describe QueryProcessor do
7
+
8
+ QUERY['matching all Resources'] = '*::*::*::*'
9
+ QUERY['by account name'] = 'Fake Account \d+::*::*::*'
10
+ QUERY['by Fog Service'] = '*::FakeService::*::*'
11
+ QUERY['by Fog Provider'] = '*::*::FakeProvider::*'
12
+ QUERY['by Fog collection name'] = '*::*::*::fake_collection_type1'
13
+
14
+
15
+ it "should define a Query Pattern for parsing queries" do
16
+ QueryProcessor::QUERY_PATTERN.should_not == nil
17
+ QueryProcessor::QUERY_PATTERN.should be_an_instance_of(Regexp)
18
+ end
19
+
20
+ describe "#execute" do
21
+ context "with no discovered Resources" do
22
+ # Try each of the QUERY entries above against an empty set
23
+ QUERY.each do |name, query|
24
+ it "should return an empty Array for a query #{name}" do
25
+ QueryProcessor.new(
26
+ {FAKE_ACCOUNT_NAME => mock_account_tracker}, :logger => LOG
27
+ ).execute(query).should == []
28
+ end
29
+ end
30
+ end
31
+
32
+ context "with a pre-populated, diverse set of Resources" do
33
+ # Create a prepopulated QueryProcessor
34
+ NUMBER_OF_ACCOUNTS = 8
35
+ RESOURCES_PER_ACCOUNT = 2
36
+ NUMBER_OF_COLLECTIONS = [5, NUMBER_OF_FAKE_RESOURCE_TYPES].min
37
+ before(:each) do
38
+ account_trackers =
39
+ (1..NUMBER_OF_ACCOUNTS).inject({}) do |t, account_index|
40
+ t["Fake Account #{account_index}"] = mock_account_tracker(
41
+ NUMBER_OF_COLLECTIONS, RESOURCES_PER_ACCOUNT
42
+ ) ; t
43
+ end
44
+ @processor = QueryProcessor.new(account_trackers, :logger => LOG)
45
+ end
46
+ # Run the above QUERY entries against the prepopulated set
47
+ context "when running the query matching all Resources" do
48
+ it "should return all Resources" do
49
+ @processor.execute(QUERY['matching all Resources']).size.should ==
50
+ NUMBER_OF_ACCOUNTS * NUMBER_OF_COLLECTIONS * RESOURCES_PER_ACCOUNT
51
+ end
52
+ end
53
+ context "when running a query by account name" do
54
+ it "should return all Resources for that account only" do
55
+ @processor.execute(QUERY['by account name']).size.should ==
56
+ NUMBER_OF_ACCOUNTS * NUMBER_OF_COLLECTIONS * RESOURCES_PER_ACCOUNT
57
+ @processor.execute('wrong account::*::*::*').size.should == 0
58
+ end
59
+ end
60
+ context "when running a query by Fog service name" do
61
+ it "should return all Resources for that service only" do
62
+ @processor.execute(QUERY['by Fog Service']).size.should ==
63
+ NUMBER_OF_ACCOUNTS * NUMBER_OF_COLLECTIONS * RESOURCES_PER_ACCOUNT
64
+ @processor.execute('*::wrong service::*::*').size.should == 0
65
+ end
66
+ end
67
+ context "when running a query by Fog provider name" do
68
+ it "should return all Resources for that provider only" do
69
+ @processor.execute(QUERY['by Fog Provider']).size.should ==
70
+ NUMBER_OF_ACCOUNTS * NUMBER_OF_COLLECTIONS * RESOURCES_PER_ACCOUNT
71
+ @processor.execute('*::*::wrong provider::*').size.should == 0
72
+ end
73
+ end
74
+ context "when running a query by Fog collection name" do
75
+ it "should return all Resources for that collection only" do
76
+ @processor.execute(QUERY['by Fog collection name']).size.should ==
77
+ NUMBER_OF_ACCOUNTS * RESOURCES_PER_ACCOUNT
78
+ @processor.execute('*::*::*::wrong collection').size.should == 0
79
+ true
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ module FogTracker
4
+
5
+ describe ResourceTracker do
6
+
7
+ before(:each) do
8
+ @account_tracker = mock_account_tracker
9
+ @tracker = ResourceTracker.new(
10
+ FAKE_COLLECTION, @account_tracker
11
+ )
12
+ end
13
+
14
+ it "exposes a collection of Fog objects" do
15
+ @tracker.collection.should == []
16
+ end
17
+
18
+ describe '#update' do
19
+ it "refreshes its resource collection from its AccountTracker" do
20
+ @account_tracker.connection.should_receive(FAKE_COLLECTION)
21
+ @tracker.update
22
+ end
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ module FogTracker
4
+ describe Tracker do
5
+
6
+ ACCOUNTS = {
7
+ "fake account" => {
8
+ :provider => 'AWS',
9
+ :service => 'Compute',
10
+ :credentials => {
11
+ :aws_access_key_id => 'X',
12
+ :aws_secret_access_key => 'X'
13
+ }, :exclude_resources => [ :spot_requests ]
14
+ }
15
+ }
16
+
17
+ before(:each) do
18
+ @tracker = Tracker.new(ACCOUNTS, :logger => LOG)
19
+ end
20
+
21
+ it "exposes a Hash of account information" do
22
+ @tracker.accounts.should == ACCOUNTS
23
+ end
24
+ it "exposes a Logger object for reporting its activity" do
25
+ @tracker.logger.should == LOG
26
+ end
27
+
28
+ describe '#types_for_account' do
29
+ it "returns an array of collection names for the given account" do
30
+ [ "addresses", "flavors", "images", "key_pairs", "security_groups",
31
+ "servers", "snapshots", "tags", "volumes"].each do |collection|
32
+ @tracker.types_for_account("fake account").should include(collection)
33
+ end
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,11 @@
1
+ Bundler.require # Load all gems and libs
2
+
3
+ # Require RSpec support files. Logging is configured there
4
+ support_files = Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")]
5
+ support_files.sort.each {|f| require f}
6
+
7
+ # RSpec configuration block
8
+ RSpec.configure do |config|
9
+ config.mock_with :rspec # == Mock Framework
10
+ #config.fixture_path = "#{::Rails.root}/spec/fixtures"
11
+ end
@@ -0,0 +1,6 @@
1
+ # Setup a global Logger for all tests
2
+ LOG_LEVEL = ::Logger::WARN
3
+ LOG = FogTracker.default_logger(STDOUT)
4
+ LOG.info "Logging configured in #{File.basename __FILE__}."
5
+ LOG.level = LOG_LEVEL
6
+ #ActiveRecord::Base.logger = LOG # Uncomment for ActiveRecord outputs
@@ -0,0 +1,100 @@
1
+ require 'fog'
2
+ Fog.mock!
3
+
4
+ # Establish some constants
5
+ module FogTracker
6
+ FAKE_COLLECTION = 'servers'
7
+ FAKE_ACCOUNT_NAME = 'Fake EC2 Account'
8
+ FAKE_ACCOUNT = {
9
+ :provider => 'AWS',
10
+ :service => 'Compute',
11
+ :polling_time => 10,
12
+ :credentials => {
13
+ :aws_access_key_id => "fake user",
14
+ :aws_secret_access_key => 'fake password'
15
+ },
16
+ :exclude_resources => [
17
+ :spot_requests, # No Fog mocks for this resource
18
+ #:account,
19
+ #:flavors,
20
+ #:images,
21
+ #:addresses,
22
+ #:volumes,
23
+ #:snapshots,
24
+ #:tags,
25
+ #:servers,
26
+ #:security_groups,
27
+ #:key_pairs,
28
+ #:spot_instance_requests
29
+ ],
30
+ }
31
+ FAKE_ACCOUNTS = {FAKE_ACCOUNT_NAME => FAKE_ACCOUNT}
32
+ module Query
33
+ QUERY = {} # Used in query_processor_spec.rb
34
+ end
35
+ end
36
+
37
+ # Create some fake Fog Resource and Collection Classes
38
+ NUMBER_OF_FAKE_RESOURCE_TYPES = 30
39
+ module Fog
40
+ module FakeService
41
+ module FakeProvider
42
+ (1..NUMBER_OF_FAKE_RESOURCE_TYPES).each do |class_index|
43
+ eval(%Q{
44
+ class FakeCollectionType#{class_index} ; end
45
+ class FakeResourceType#{class_index}
46
+ def collection ; FakeCollectionType#{class_index}.new end
47
+ end
48
+ })
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def mock_account_tracker(num_collections = 1, resources_per_collection = 0)
55
+ fake_account_tracker = double('mock_account_tracker')
56
+ fake_account_tracker.stub(:account).and_return(Hash.new)
57
+ fake_account_tracker.stub(:name).and_return("fake account tracker")
58
+ fake_account_tracker.stub(:log).and_return(LOG)
59
+ fake_account_tracker.stub(:connection).and_return(mock_fog_connection)
60
+ # create an array of mock ResourceTrackers
61
+ trackers = (1..num_collections).map do |class_index|
62
+ mock_resource_tracker(
63
+ Fog::FakeService::FakeProvider.const_get("FakeResourceType#{class_index}"),
64
+ resources_per_collection
65
+ )
66
+ end
67
+ fake_account_tracker.stub(:resource_trackers).and_return(trackers)
68
+ fake_account_tracker
69
+ end
70
+
71
+ def mock_fog_connection
72
+ fake_fog_connection = double('mock_fog_connection')
73
+ fake_fog_connection.stub(FogTracker::FAKE_COLLECTION).and_return([ 1, 2, 3 ])
74
+ fake_fog_connection
75
+ end
76
+
77
+ def mock_resource_tracker
78
+ fake_resource_tracker = double('mock_resource_tracker')
79
+ fake_resource_tracker.stub(:update)
80
+ fake_resource_tracker
81
+ end
82
+
83
+ def mock_fog_resource(resource_class)
84
+ fake_resource = resource_class.new
85
+ fake_collection_class =
86
+ fake_resource.stub(:collection).and_return(
87
+ collection_class.new
88
+ )
89
+ fake_resource
90
+ end
91
+
92
+ def mock_resource_tracker(resource_class, number_of_resources = 0)
93
+ fake_resource_tracker = double("mock_resource_tracker")
94
+ resources = Array.new
95
+ number_of_resources.times do
96
+ resources << resource_class.new
97
+ end
98
+ fake_resource_tracker.stub(:collection).and_return(resources)
99
+ fake_resource_tracker
100
+ end
@@ -0,0 +1,3 @@
1
+ # When message expectations are set for background threads, sometimes
2
+ # they come in a after a short delay
3
+ THREAD_STARTUP_DELAY = 1
metadata ADDED
@@ -0,0 +1,163 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fog_tracker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Benton Roberts
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-25 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: fog
16
+ requirement: &2151920400 !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: *2151920400
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &2151919380 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2151919380
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &2151918740 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *2151918740
47
+ - !ruby/object:Gem::Dependency
48
+ name: rdoc
49
+ requirement: &2151917800 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *2151917800
58
+ - !ruby/object:Gem::Dependency
59
+ name: guard
60
+ requirement: &2151916140 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *2151916140
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-rake
71
+ requirement: &2151915260 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *2151915260
80
+ - !ruby/object:Gem::Dependency
81
+ name: ruby_gntp
82
+ requirement: &2151914080 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *2151914080
91
+ description: This gem peridically polls mutiple cloud computing services using the
92
+ fog gem, asynchronously updating the state of the resulting collections of Fog Resources.
93
+ email:
94
+ - benton@bentonroberts.com
95
+ executables:
96
+ - tracker
97
+ extensions: []
98
+ extra_rdoc_files: []
99
+ files:
100
+ - .gitignore
101
+ - Gemfile
102
+ - Guardfile
103
+ - README.md
104
+ - Rakefile
105
+ - bin/tracker
106
+ - config/accounts.yml.example
107
+ - fog_tracker.gemspec
108
+ - lib/fog_tracker.rb
109
+ - lib/fog_tracker/account_tracker.rb
110
+ - lib/fog_tracker/query/query_processor.rb
111
+ - lib/fog_tracker/resource_tracker.rb
112
+ - lib/fog_tracker/tracker.rb
113
+ - lib/fog_tracker/util/string_underscore.rb
114
+ - lib/fog_tracker/version.rb
115
+ - lib/tasks/rspec.rake
116
+ - spec/lib/fog_tracker/account_tracker_spec.rb
117
+ - spec/lib/fog_tracker/query/query_processor_spec.rb
118
+ - spec/lib/fog_tracker/resource_tracker_spec.rb
119
+ - spec/lib/fog_tracker/tracker_spec.rb
120
+ - spec/spec_helper.rb
121
+ - spec/support/_configure_logging.rb
122
+ - spec/support/_mocks.rb
123
+ - spec/support/_threading_support.rb
124
+ homepage: http://github.com/benton/fog_tracker
125
+ licenses: []
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ segments:
137
+ - 0
138
+ hash: 3974745377539768248
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ none: false
141
+ requirements:
142
+ - - ! '>='
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ segments:
146
+ - 0
147
+ hash: 3974745377539768248
148
+ requirements: []
149
+ rubyforge_project: fog_tracker
150
+ rubygems_version: 1.8.15
151
+ signing_key:
152
+ specification_version: 3
153
+ summary: Tracks the state of cloud computing resources across multiple accounts with
154
+ multiple service providers
155
+ test_files:
156
+ - spec/lib/fog_tracker/account_tracker_spec.rb
157
+ - spec/lib/fog_tracker/query/query_processor_spec.rb
158
+ - spec/lib/fog_tracker/resource_tracker_spec.rb
159
+ - spec/lib/fog_tracker/tracker_spec.rb
160
+ - spec/spec_helper.rb
161
+ - spec/support/_configure_logging.rb
162
+ - spec/support/_mocks.rb
163
+ - spec/support/_threading_support.rb