virtuoso-ensure-state 0.0.6.beta
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +23 -0
- data/LICENSE +14 -0
- data/README +12 -0
- data/bin/virtuoso-ensure-state +10 -0
- data/lib/ensure-state.rb +3 -0
- data/lib/ensure-state/cli.rb +84 -0
- data/lib/ensure-state/cli/option_parser.rb +145 -0
- data/lib/ensure-state/engine.rb +81 -0
- data/lib/ensure-state/errors/ensure_state_error.rb +10 -0
- data/lib/ensure-state/errors/invalid_vm_backend_error.rb +12 -0
- data/lib/ensure-state/errors/vm_backend_error.rb +12 -0
- data/lib/ensure-state/errors/vm_backend_errors/backend_unavailable_error.rb +12 -0
- data/lib/ensure-state/errors/vm_backend_errors/invalid_state_error.rb +12 -0
- data/lib/ensure-state/errors/vm_backend_errors/state_transition_error.rb +12 -0
- data/lib/ensure-state/errors/vm_backend_errors/vm_not_found_error.rb +12 -0
- data/lib/ensure-state/version.rb +16 -0
- data/lib/ensure-state/vm.rb +101 -0
- data/lib/ensure-state/vm_backend/base.rb +96 -0
- data/lib/ensure-state/vm_backend/virtualbox.rb +118 -0
- metadata +105 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
0.0.6.beta
|
2
|
+
====
|
3
|
+
* Fix permission issues in gem deployment
|
4
|
+
|
5
|
+
0.0.5.beta
|
6
|
+
====
|
7
|
+
* Sync code versioning schema
|
8
|
+
|
9
|
+
0.0.4.beta
|
10
|
+
====
|
11
|
+
* Up version to fix back library push
|
12
|
+
|
13
|
+
0.0.3.beta
|
14
|
+
====
|
15
|
+
* Fix issue with require paths in gemspec
|
16
|
+
|
17
|
+
0.0.2.beta
|
18
|
+
=====
|
19
|
+
* Initial feature complete release of virtualbox backend
|
20
|
+
|
21
|
+
0.0.1.beta
|
22
|
+
=====
|
23
|
+
* Initial release
|
data/LICENSE
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Copyright (C) 2010 3Crowd Technologies, Inc.
|
2
|
+
|
3
|
+
This program is free software: you can redistribute it and/or modify
|
4
|
+
it under the terms of the GNU General Public License as published by
|
5
|
+
the Free Software Foundation, either version 3 of the License, or
|
6
|
+
(at your option) any later version.
|
7
|
+
|
8
|
+
This program is distributed in the hope that it will be useful,
|
9
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
GNU General Public License for more details.
|
12
|
+
|
13
|
+
You should have received a copy of the GNU General Public License
|
14
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
data/README
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
README for Virtuoso-ensure-state
|
2
|
+
=============================
|
3
|
+
Virtuoso-ensure-state is a program which converges virtual machines into a certain state. Essentially,
|
4
|
+
it's an easy way to specify a state machine to ensure that your virtual machine remain in a state you
|
5
|
+
control.
|
6
|
+
|
7
|
+
Example
|
8
|
+
=======
|
9
|
+
./virtuoso-ensure-state -b virtualbox -m myvirtualmachine --ifpowered_off start --ifpaused resume --no-gui --silent
|
10
|
+
|
11
|
+
would silently start the virtualbox based virtual machine if it were powered off and would resume it if it were paused, and would
|
12
|
+
attempt to start it in the background in a non-gui mode.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Ensure library path is in load path
|
4
|
+
library_path = File.join(File.dirname(__FILE__),'..','lib')
|
5
|
+
$:.push(library_path) unless $:.include?(library_path)
|
6
|
+
|
7
|
+
require 'ensure-state'
|
8
|
+
require 'ensure-state/cli'
|
9
|
+
|
10
|
+
Virtuoso::EnsureState::CLI.run!(ARGV)
|
data/lib/ensure-state.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'ensure-state/cli/option_parser'
|
2
|
+
require 'ensure-state/engine'
|
3
|
+
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
module Virtuoso
|
7
|
+
module EnsureState
|
8
|
+
class CLI
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
# Creates and starts a new CLI instance for ensure-state.
|
13
|
+
# By default reads options from command line ARGV.
|
14
|
+
def run!(run_flags=ARGV)
|
15
|
+
self.new(run_flags).run!
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :logger
|
21
|
+
|
22
|
+
def initialize(raw_run_flags)
|
23
|
+
@raw_run_flags = raw_run_flags
|
24
|
+
@logger = Logger.new(STDOUT)
|
25
|
+
|
26
|
+
# defaults
|
27
|
+
|
28
|
+
@logger.level = Logger::WARN
|
29
|
+
@logger.progname = self.class.name
|
30
|
+
end
|
31
|
+
|
32
|
+
# Starts a virtual machine convergence run using the ensure-state system guided by the run_flags passed into the initializer.
|
33
|
+
def run!
|
34
|
+
cli_parser = parse_run_flags(@raw_run_flags)
|
35
|
+
# display any informational messages emitted by the parser
|
36
|
+
puts cli_parser.messages.join("\n") unless cli_parser.messages.empty?
|
37
|
+
# exit if suggested by parser
|
38
|
+
exit(cli_parser.suggested_exit_return_code) if cli_parser.exit_after_message_display_suggested
|
39
|
+
|
40
|
+
# setup logger
|
41
|
+
logger.level = Logger::DEBUG if cli_parser.options[:debug]
|
42
|
+
logger.level = Logger::INFO if cli_parser.options[:verbose]
|
43
|
+
|
44
|
+
# nothing within this application should log at a level higher than fatal
|
45
|
+
logger.level = Logger::UNKNOWN if cli_parser.options[:silent]
|
46
|
+
|
47
|
+
logger.debug "Completed parsing command line arguments. Starting convergence engine."
|
48
|
+
|
49
|
+
display_mode = cli_parser.options[:nogui] ? :vrdp : :gui
|
50
|
+
|
51
|
+
# this is where all the actual work happens
|
52
|
+
engine = EnsureState::Engine.new(cli_parser.options[:backend], cli_parser.options[:machine], display_mode, cli_parser.options[:ifinstate], @logger)
|
53
|
+
|
54
|
+
begin
|
55
|
+
engine.converge_vm_state!
|
56
|
+
rescue Errors::BackendUnavailableError => e
|
57
|
+
logger.fatal "Unable to access backend. Message: (#{e.message})"
|
58
|
+
exit(1)
|
59
|
+
rescue Errors::StateTransitionError => e
|
60
|
+
logger.fatal "Could not transition to state. Message: (#{e.message})"
|
61
|
+
exit(1)
|
62
|
+
rescue Errors::VMNotFoundError => e
|
63
|
+
logger.fatal "Unable to find VM. Message: (#{e.message})"
|
64
|
+
exit(1)
|
65
|
+
rescue Errors::InvalidVMBackendError => e
|
66
|
+
logger.fatal "Unable to find specified VM Backend. Message: (#{e.message})"
|
67
|
+
exit(1)
|
68
|
+
rescue StandardError => e
|
69
|
+
logger.fatal "Fatal Error: #{e.inspect}"
|
70
|
+
logger.fatal "Debug Backtrace: \n#{e.backtrace.join("\n")}" if cli_parser.options[:debug]
|
71
|
+
exit(1)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def parse_run_flags(run_flags_to_parse)
|
79
|
+
CLIOptionParser.new(run_flags_to_parse)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'optparse' #stdlib
|
2
|
+
require 'ensure-state/version'
|
3
|
+
|
4
|
+
module Virtuoso
|
5
|
+
module EnsureState
|
6
|
+
class CLIOptionParser
|
7
|
+
|
8
|
+
STATES = [:powered_off, :running, :saved, :paused]
|
9
|
+
ACTIONS = [:start, :pause, :stop, :shutdown, :resume]
|
10
|
+
BACKENDS = [:virtualbox]
|
11
|
+
|
12
|
+
attr_reader :options
|
13
|
+
attr_reader :messages
|
14
|
+
attr_reader :exit_after_message_display_suggested
|
15
|
+
attr_reader :suggested_exit_return_code
|
16
|
+
|
17
|
+
def initialize(args)
|
18
|
+
@messages = Array.new
|
19
|
+
@options = Hash.new
|
20
|
+
@exit_after_message_display_suggested = false
|
21
|
+
@suggested_exit_return_code = 0
|
22
|
+
parse!(args)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def parse!(args)
|
28
|
+
|
29
|
+
# If we have no options, we need to display usage, which is the help option
|
30
|
+
args << '-h' if args.empty?
|
31
|
+
|
32
|
+
# The options specified on the command line will be collected in *options*
|
33
|
+
# We set defaults here.
|
34
|
+
options[:machine] = nil
|
35
|
+
options[:ifinstate] = Hash.new
|
36
|
+
|
37
|
+
opts = OptionParser.new do |opts|
|
38
|
+
opts.banner = "Usage: virtuoso-ensure-state [required options] [options]"
|
39
|
+
opts.separator "Example: virtuoso-ensure-state -b virtualbox -m development_vm --ifPoweredOff powerUp"
|
40
|
+
opts.separator "Version: #{Version.to_s}"
|
41
|
+
|
42
|
+
opts.separator ""
|
43
|
+
opts.separator "Required options:"
|
44
|
+
|
45
|
+
# Mandatory arguments
|
46
|
+
opts.on('-b', '--backend BACKEND',
|
47
|
+
"Backend to use") do |backend|
|
48
|
+
options[:backend] = backend.to_sym
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on('-m', '--machine MACHINE',
|
52
|
+
'Virtual Machine name or UUID this application should manage') do |machine|
|
53
|
+
options[:machine] = machine
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.separator ""
|
57
|
+
opts.separator "available BACKENDs: #{BACKENDS.join(', ')}"
|
58
|
+
|
59
|
+
opts.separator ""
|
60
|
+
opts.separator "Specific options:"
|
61
|
+
|
62
|
+
# Optional arguments
|
63
|
+
STATES.each do |state|
|
64
|
+
|
65
|
+
opts.on("--if#{state} ACTION",
|
66
|
+
"Action to take if a given virtual machine is in state #{state}") do |action|
|
67
|
+
options[:ifinstate][state] = action.to_sym
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.separator ""
|
73
|
+
opts.separator "Available ACTIONs: #{ACTIONS.join(', ')}"
|
74
|
+
opts.separator ""
|
75
|
+
opts.separator "Common options:"
|
76
|
+
|
77
|
+
opts.on('--no-gui', "Attempt to run virtual machines in no-gui mode (dependent on backend)") do
|
78
|
+
options[:nogui] = true
|
79
|
+
end
|
80
|
+
|
81
|
+
opts.on('-s', "--silent", "Run silently (no output)") do
|
82
|
+
options[:silent] = true
|
83
|
+
end
|
84
|
+
|
85
|
+
opts.on('-v', "--[no-]verbose", "Run verbosely") do |v|
|
86
|
+
options[:verbose] = v
|
87
|
+
end
|
88
|
+
|
89
|
+
opts.on('-d', "--debug", "Run with debugging messages and behavior turned on") do
|
90
|
+
options[:debug] = true
|
91
|
+
end
|
92
|
+
|
93
|
+
# No argument, shows at tail. This will print an options summary.
|
94
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
95
|
+
@messages << opts.to_s
|
96
|
+
options[:help_requested] = true
|
97
|
+
suggest_exit_after_message_display
|
98
|
+
end
|
99
|
+
|
100
|
+
opts.on_tail('-v', '--version', 'Show application version') do
|
101
|
+
@messages << Version.to_s
|
102
|
+
suggest_exit_after_message_display
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
opts.parse!(args)
|
108
|
+
|
109
|
+
ensure_required_switches_passed!
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
def ensure_required_switches_passed!
|
114
|
+
return if @options[:help_requested]
|
115
|
+
|
116
|
+
if @options[:backend].nil? || @options[:machine].nil? then
|
117
|
+
@messages << 'Error: Required option not set. Please ensure all required options have been set. See usage by using -h flag.'
|
118
|
+
suggest_exit_after_message_display
|
119
|
+
suggest_exit_return_code(1)
|
120
|
+
end
|
121
|
+
|
122
|
+
unless @options[:backend] && BACKENDS.member?(@options[:backend])
|
123
|
+
@messages << 'Error: Invalid backend specified. Please ensure backend exists in available backend list. See available backends using -h flag.'
|
124
|
+
suggest_exit_after_message_display
|
125
|
+
suggest_exit_return_code(1)
|
126
|
+
end
|
127
|
+
|
128
|
+
unless @options[:ifinstate] && @options[:ifinstate].values.all?{|value|ACTIONS.member?(value)}
|
129
|
+
@messages << 'Error: Invalid action specified for :ifinstate. Ensure all actions are set appropriately. See usage and available actions by using the -h flag.'
|
130
|
+
suggest_exit_after_message_display
|
131
|
+
suggest_exit_return_code(1)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def suggest_exit_return_code(code)
|
136
|
+
@suggested_exit_return_code = code
|
137
|
+
end
|
138
|
+
|
139
|
+
def suggest_exit_after_message_display
|
140
|
+
@exit_after_message_display_suggested = true
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'ensure-state/vm_backend/virtualbox'
|
2
|
+
|
3
|
+
require 'ensure-state/vm'
|
4
|
+
|
5
|
+
require 'ensure-state/errors/invalid_vm_backend_error'
|
6
|
+
|
7
|
+
module Virtuoso
|
8
|
+
module EnsureState
|
9
|
+
|
10
|
+
class Engine
|
11
|
+
|
12
|
+
attr_reader :state_and_action_map, :logger
|
13
|
+
|
14
|
+
# Instantiate a new engine for converging vm state
|
15
|
+
#
|
16
|
+
# @param [EnsureState::VMBackend::Base] backend The VM Backend where the machine specified in vm_name can be found
|
17
|
+
# @param [String] vm_name A name or UUID which uniquely identifies the virtual machine inside the backend specified in backend
|
18
|
+
# @param [Hash<Symbol, Symbol>] state_and_action_map A mapping of states, if a VM is in one of those keyed states, take the specified action. Otherwise, do nothing
|
19
|
+
# @param [Logger] logger The logger for engine information
|
20
|
+
def initialize backend, vm_name, vm_display_mode, state_and_action_map, logger = Logger.new(STDOUT)
|
21
|
+
@backend_name = backend
|
22
|
+
@vm_display_mode = vm_display_mode
|
23
|
+
@vm_name = vm_name
|
24
|
+
@state_and_action_map = state_and_action_map
|
25
|
+
@logger = logger.dup
|
26
|
+
@logger.progname = self.class.name
|
27
|
+
end
|
28
|
+
|
29
|
+
# Converges the specified backend VM state utilizing the state_and_action_map
|
30
|
+
#
|
31
|
+
# @raise [Errors::BackendUnavailableError] The specified backend was unavailable for interaction
|
32
|
+
# @raise [Errors::StateTransitionError] There was an error transitioning a VirtualMachine into a specified state
|
33
|
+
# @raise [Errors::VMNotFoundError] The VM specified as not found by the backend
|
34
|
+
# @raise [Errors::InvalidVMBackendError] The specified VM Backend was not registered
|
35
|
+
def converge_vm_state!
|
36
|
+
logger.debug "Converging VM State"
|
37
|
+
action_to_take = state_and_action_map[vm.state]
|
38
|
+
if action_to_take.nil?
|
39
|
+
logger.info "No action specified for current virtual machine state (#{vm.state})"
|
40
|
+
else
|
41
|
+
logger.info "Taking action: #{action_to_take}"
|
42
|
+
vm.state = action_to_take if backend.can_transition_to_state?(vm.state, action_to_take)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# The backend in which the specified virtual machine should be found
|
47
|
+
#
|
48
|
+
# @raise [Errors::InvalidVMBackendError] The specified VM Backend was not registered
|
49
|
+
def backend
|
50
|
+
@backend ||= load_backend(@backend_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
# The virtual machine to be operated on by this engine
|
54
|
+
#
|
55
|
+
# @raise [Errors::VMNotFoundError] The VM specified was not found by the backend
|
56
|
+
def vm
|
57
|
+
logger.debug("Finding VM (#{@vm_name})")
|
58
|
+
return @vm if @vm
|
59
|
+
@vm = VM.find!(@vm_name, backend, logger)
|
60
|
+
@vm.display_mode = @vm_display_mode
|
61
|
+
@vm
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def load_backend(backend)
|
67
|
+
logger.debug "Finding backend for load call: #{backend}"
|
68
|
+
backend_instance = case backend
|
69
|
+
when :virtualbox
|
70
|
+
VMBackend::VirtualBox.new(logger)
|
71
|
+
else
|
72
|
+
raise Errors::InvalidVMBackendError, "Specified backend (#{backend}) is not a registered backend"
|
73
|
+
end
|
74
|
+
logger.debug "Found backend for load call: #{backend}"
|
75
|
+
return backend_instance
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'ensure-state/vm_backend/base'
|
2
|
+
require 'ensure-state/errors/vm_backend_errors/vm_not_found_error'
|
3
|
+
|
4
|
+
module Virtuoso
|
5
|
+
module EnsureState
|
6
|
+
class VM
|
7
|
+
|
8
|
+
# Find a virtual machine instance on the specified backend
|
9
|
+
#
|
10
|
+
# @params [String] name_or_uuid The name or uuid of the vm to find
|
11
|
+
# @params [VMBackend::Base] backend a virtual machine backend implementation,
|
12
|
+
# usage of the abstract implementation is not recommended
|
13
|
+
# @return [VM, false] The virtual machine instance, or false if the instance is
|
14
|
+
# is not found
|
15
|
+
def self.find name_or_uuid, backend, logger = Logger.new(STDOUT)
|
16
|
+
begin
|
17
|
+
self.find! name_or_uuid, backend, logger
|
18
|
+
rescue Errors::VMNotFoundError => e
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Find a virtual machine instance on the specified backend, raising an exception
|
24
|
+
# if the virtual machine is not found
|
25
|
+
#
|
26
|
+
# @params [String] name_or_uuid The name or uuid of the vm to find
|
27
|
+
# @params [VMBackend::Base] backend a virtual machine backend implementation,
|
28
|
+
# usage of the abstract implementation is not recommended
|
29
|
+
# @return [VM, false] The virtual machine instance
|
30
|
+
# @raise [VMNotFoundError] Exception raised if virtual machine is not found
|
31
|
+
def self.find! name_or_uuid, backend, logger = Logger.new(STDOUT)
|
32
|
+
self.new name_or_uuid, backend, logger
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_accessor :display_mode
|
36
|
+
attr_reader :vm_name, :backend, :logger
|
37
|
+
|
38
|
+
def initialize name_or_uuid, backend, logger = Logger.new(STDOUT)
|
39
|
+
@logger = logger.dup
|
40
|
+
@logger.progname = self.class.name
|
41
|
+
|
42
|
+
@display_mode = nil
|
43
|
+
@vm_name = name_or_uuid
|
44
|
+
@backend = backend
|
45
|
+
verify_backend_is_a_valid_backend @backend
|
46
|
+
@vm = retrieve_backend_reference_to_named_vm @backend, @vm_name
|
47
|
+
end
|
48
|
+
|
49
|
+
# The human readable name of the VM, may be the UUID if there is no human readable
|
50
|
+
# name assigned by the backend
|
51
|
+
#
|
52
|
+
# @return [String] The human readable Virtual Machine name, or uuid if no human readable
|
53
|
+
# name is available
|
54
|
+
def name
|
55
|
+
name = @backend.vm_name(@vm)
|
56
|
+
unless name
|
57
|
+
uuid
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# The state of the vm.
|
62
|
+
#
|
63
|
+
# @return [Symbol] The state of the virtual machine
|
64
|
+
def state
|
65
|
+
state = @backend.vm_state(@vm)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Set the state of the vm
|
69
|
+
#
|
70
|
+
# @raise [Errors::InvalidStateError] Error raised if the specified state is not recognized by the backend, or if the current
|
71
|
+
# state of the virtual machine does not allow for setting the specified state
|
72
|
+
def state= state
|
73
|
+
@backend.vm_set_state!(@vm, state, { :mode => display_mode })
|
74
|
+
end
|
75
|
+
|
76
|
+
# The unique identifier for the VM. Note: The score of the uniqueness is guaranteed by
|
77
|
+
# the backend. It may not be universally unique
|
78
|
+
#
|
79
|
+
# @return [String] The Virtual Machine unique identifier
|
80
|
+
def uuid
|
81
|
+
@backend.vm_uuid(@vm)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def verify_backend_is_a_valid_backend backend
|
87
|
+
logger.debug("Verifying backend type")
|
88
|
+
backend.kind_of? VMBackend::Base
|
89
|
+
end
|
90
|
+
|
91
|
+
def retrieve_backend_reference_to_named_vm backend, named_vm
|
92
|
+
logger.debug("Retrieving backend reference to named vm. Backend: #{backend}. Named VM: #{named_vm}")
|
93
|
+
vm = backend.vm_find named_vm
|
94
|
+
raise Errors::VMNotFoundError if vm.nil?
|
95
|
+
logger.debug("Successfully retrieved backend reference to named vm. Backend Reference Class: #{vm.class.name}")
|
96
|
+
vm
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'ensure-state/errors/vm_backend_errors/state_transition_error'
|
2
|
+
|
3
|
+
module Virtuoso
|
4
|
+
module EnsureState
|
5
|
+
module VMBackend
|
6
|
+
|
7
|
+
class Base
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
# The logger for EnsureState::VMBackend objects
|
12
|
+
#
|
13
|
+
# @return [Logger] The logger
|
14
|
+
def logger
|
15
|
+
@@logger
|
16
|
+
end
|
17
|
+
|
18
|
+
# Set the logger used for EnsureState::VMBackend objects
|
19
|
+
#
|
20
|
+
# @param [Logger] logger A logger conforming to the Ruby 1.8+ Logger class interface
|
21
|
+
def logger= logger
|
22
|
+
@@logger = logger
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
VALID_ACTION_TO_STATE_APPLICATION_MAP = {
|
28
|
+
}
|
29
|
+
|
30
|
+
# Find a virtual machine on the backend
|
31
|
+
#
|
32
|
+
# @param [String] name_or_uuid the name or uuid to find
|
33
|
+
# @return [EnsureState::VM, false] the virtual machine or false if the machine was not found
|
34
|
+
def vm_find name_or_uuid
|
35
|
+
raise NotImplementedError, "#find is not implemented on the abstract VM Backend, please use a concrete implementation"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Retrieve the human readable name of the given virtual machine or false if no human readable name
|
39
|
+
# is available
|
40
|
+
#
|
41
|
+
# @param [EnsureState::VM] virtual_machine The virtual machine on which the name should be retrieved
|
42
|
+
# @return [String, false] The human readable name of the virtual machine, or false if no human readable name is available
|
43
|
+
def vm_name virtual_machine
|
44
|
+
raise NotImplementedError, "#vm_name is not implemented on the abstract VM Backend, please use a concrete implementation"
|
45
|
+
end
|
46
|
+
|
47
|
+
# Retrieve the unique identifier for given virtual machine. This should always be available. The unique
|
48
|
+
# identifier (or uuid) is an arbitrary token, chosen by the backend, that uniquely identifies the virtual machine
|
49
|
+
# within a scope to be decided by the backend
|
50
|
+
#
|
51
|
+
# @param [EnsureState::VM] virtual_machine The virtual machine whose uuid should be retrieved
|
52
|
+
# @return [String] The unique identifier
|
53
|
+
def vm_uuid virtual_machine
|
54
|
+
raise NotImplementedError, "#vm_uuid is not implemented on the abstract VM Backend, please use a concrete implementation"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Retrieve the state of a given virtual machine
|
58
|
+
#
|
59
|
+
# @param [EnsureState::VM] virtual_machine The virtual machine whose state should be retrieved
|
60
|
+
# @return [Symbol] The state of the virtual machine
|
61
|
+
def vm_state virtual_machine
|
62
|
+
raise NotImplementedError, "#vm_state is not implemented on the abstract VM Backend, please use a concrete implementation"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Attempt to set the state of a given virtual machine. You must check the return value to ensure the state of of the virtual
|
66
|
+
# machine in the backend. To be certain please use vm_set_state! which raises exceptions if the specified state could not
|
67
|
+
# be reached
|
68
|
+
#
|
69
|
+
# @param [Ensure_State::VM] virtual_machine The virtual machine whose state should be set
|
70
|
+
# @param [Symbol] state The state to which the machine should be moved
|
71
|
+
# @return [Boolean] True if machine has successfully been moved to the specified state, False if not
|
72
|
+
def vm_set_state virtual_machine, state
|
73
|
+
begin
|
74
|
+
vm_set_state! virtual_machine, state
|
75
|
+
rescue Errors::StateTransitionError => e
|
76
|
+
return false
|
77
|
+
end
|
78
|
+
true
|
79
|
+
end
|
80
|
+
|
81
|
+
# Set the state of a given virtual machine. If the state cannot be reached from the current state or a failure occurs in setting
|
82
|
+
# an exception is raised, depending on the specific error. The exception thrown will always subclass Errors::StateTransitionError
|
83
|
+
#
|
84
|
+
# @param [Ensure_State::VM] virtual_machine The virtual machine whose state will be set
|
85
|
+
# @param [Symbol] state The state to which the machine will be moved
|
86
|
+
# @raise [Errors::StateTransitionError] An exception, or a subclass thereof, which is raised when the specified state could not be reached.
|
87
|
+
# The reason for which is specified by the particular subclass of StateTransitionError
|
88
|
+
def vm_set_state! virtual_machine, state
|
89
|
+
riase NotImplementedError, "#vm_set_state! is not implemented on the abstract VM Backend, please use a concrete implementation"
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'ensure-state/errors/vm_backend_errors/backend_unavailable_error'
|
2
|
+
require 'ensure-state/errors/vm_backend_errors/invalid_state_error'
|
3
|
+
require 'ensure-state/errors/vm_backend_errors/vm_not_found_error'
|
4
|
+
|
5
|
+
require 'ensure-state/vm_backend/base'
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'virtualbox'
|
9
|
+
|
10
|
+
module Virtuoso
|
11
|
+
module EnsureState
|
12
|
+
module VMBackend
|
13
|
+
|
14
|
+
class VirtualBox < Base
|
15
|
+
|
16
|
+
VALID_ACTION_TO_STATE_APPLICATION_MAP = {
|
17
|
+
:start => [:powered_off, :saved],
|
18
|
+
:pause => [:running],
|
19
|
+
:saved_state => [:running],
|
20
|
+
:stop => [:running],
|
21
|
+
:shutdown => [:running],
|
22
|
+
:resume => [:paused]
|
23
|
+
}
|
24
|
+
|
25
|
+
VM_BACKEND_SETTLE_TIME = 0.5 #seconds
|
26
|
+
|
27
|
+
attr_reader :logger
|
28
|
+
|
29
|
+
def initialize(logger = Logger.new(STDOUT))
|
30
|
+
@logger = logger.dup
|
31
|
+
@logger.progname = self.class.name
|
32
|
+
|
33
|
+
logger.debug("Initializing VirtualBox Backend")
|
34
|
+
|
35
|
+
@backend = ::VirtualBox
|
36
|
+
ensure_valid_backend_version @backend
|
37
|
+
end
|
38
|
+
|
39
|
+
def vm_find name_or_uuid
|
40
|
+
logger.debug("Finding VM (#{name_or_uuid})")
|
41
|
+
vm = @backend::VM.find(name_or_uuid)
|
42
|
+
logger.debug("Found VM (#{name_or_uuid})") if vm
|
43
|
+
vm
|
44
|
+
end
|
45
|
+
|
46
|
+
def vm_name virtual_machine
|
47
|
+
virtual_machine.name
|
48
|
+
end
|
49
|
+
|
50
|
+
def vm_uuid virtual_machine
|
51
|
+
virtual_machine.uuid
|
52
|
+
end
|
53
|
+
|
54
|
+
def vm_state virtual_machine
|
55
|
+
virtual_machine.state
|
56
|
+
end
|
57
|
+
|
58
|
+
def vm_set_state! virtual_machine, state_to_set, options = {}
|
59
|
+
ensure_transition_action_valid!(virtual_machine.state, state_to_set)
|
60
|
+
options[:mode] = :gui if state_to_set == :start && options[:mode].nil?
|
61
|
+
state_set_successfully = case state_to_set
|
62
|
+
when :start then
|
63
|
+
logger.debug("Backend starting VM (#{virtual_machine.uuid}/#{virtual_machine.name})")
|
64
|
+
virtual_machine.start(options[:mode])
|
65
|
+
when :pause then
|
66
|
+
logger.debug("Backend pausing VM (#{virtual_machine.uuid}/#{virtual_machine.name})")
|
67
|
+
virtual_machine.pause
|
68
|
+
#FIXME: evil hack, this method doesn't seem to block until completed
|
69
|
+
sleep(VM_BACKEND_SETTLE_TIME)
|
70
|
+
virtual_machine.paused?
|
71
|
+
when :saved_state then
|
72
|
+
logger.debug("Backend saving state of VM (#{virtual_machine.uuid}/#{virtual_machine.name})")
|
73
|
+
virtual_machine.save_state
|
74
|
+
when :stop then
|
75
|
+
logger.debug("Backend stopping VM (#{virtual_machine.uuid}/#{virtual_machine.name})")
|
76
|
+
virtual_machine.stop
|
77
|
+
#FIXME: evil hack, this method doesn't seem to block until completed
|
78
|
+
sleep(VM_BACKEND_SETTLE_TIME)
|
79
|
+
virtual_machine.powered_off?
|
80
|
+
when :shutdown then
|
81
|
+
logger.debug("Backend shutting down VM (#{virtual_machine.uuid}/#{virtual_machine.name})")
|
82
|
+
virtual_machine.shutdown
|
83
|
+
#FIXME: this command should not fail, but the guest operating system won't neccessarily shut down immediately. this is a safe shutdown
|
84
|
+
true
|
85
|
+
when :resume then
|
86
|
+
logger.debug("Backend resuming VM (#{virtual_machine.uuid}/#{virtual_machine.name})")
|
87
|
+
virtual_machine.resume
|
88
|
+
#FIXME: evil hack, this method doesn't seem to block until completed
|
89
|
+
sleep(VM_BACKEND_SETTLE_TIME)
|
90
|
+
virtual_machine.running?
|
91
|
+
end
|
92
|
+
raise Errors::StateTransitionError, "Error transitioning VM to state #{state_to_set}. Virtual Machine backend thinks machine is now in state: #{virtual_machine.state}" unless state_set_successfully
|
93
|
+
end
|
94
|
+
|
95
|
+
def can_transition_to_state? current_state, action
|
96
|
+
action_set = VALID_ACTION_TO_STATE_APPLICATION_MAP[action]
|
97
|
+
!action_set.nil? && action_set.member?(current_state)
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def ensure_transition_action_valid! current_state, action
|
103
|
+
message = VALID_ACTION_TO_STATE_APPLICATION_MAP[action].nil? ? "Setting state to #{action} not implemented" : "Machine not in one of (#{VALID_ACTION_TO_STATE_APPLICATION_MAP[action] }) states. Cannot #{action}"
|
104
|
+
raise Errors::InvalidStateError, message unless can_transition_to_state? current_state, action
|
105
|
+
end
|
106
|
+
|
107
|
+
def ensure_valid_backend_version backend
|
108
|
+
version = backend.version
|
109
|
+
logger.debug("VirtualBox backend version: #{version}")
|
110
|
+
raise Errors::BackendUnavailableError, "Backend could not be loaded" if version.nil?
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: virtuoso-ensure-state
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 31098201
|
5
|
+
prerelease: true
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 6
|
10
|
+
- beta
|
11
|
+
version: 0.0.6.beta
|
12
|
+
platform: ruby
|
13
|
+
authors:
|
14
|
+
- 3Crowd Technologies, Inc.
|
15
|
+
- Justin Lynn
|
16
|
+
autorequire:
|
17
|
+
bindir: bin
|
18
|
+
cert_chain: []
|
19
|
+
|
20
|
+
date: 2010-11-10 00:00:00 -08:00
|
21
|
+
default_executable:
|
22
|
+
dependencies:
|
23
|
+
- !ruby/object:Gem::Dependency
|
24
|
+
name: virtualbox
|
25
|
+
prerelease: false
|
26
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
hash: 9
|
32
|
+
segments:
|
33
|
+
- 0
|
34
|
+
- 7
|
35
|
+
- 5
|
36
|
+
version: 0.7.5
|
37
|
+
type: :runtime
|
38
|
+
version_requirements: *id001
|
39
|
+
description: Ensures the state of virtual machines on the host system. If a Virtual Machine is not in the desired state, report it, and, if desired, take action to put them in that state
|
40
|
+
email:
|
41
|
+
- eng@3crowd.com
|
42
|
+
executables:
|
43
|
+
- virtuoso-ensure-state
|
44
|
+
extensions: []
|
45
|
+
|
46
|
+
extra_rdoc_files: []
|
47
|
+
|
48
|
+
files:
|
49
|
+
- bin/virtuoso-ensure-state
|
50
|
+
- lib/ensure-state.rb
|
51
|
+
- lib/ensure-state/version.rb
|
52
|
+
- lib/ensure-state/vm.rb
|
53
|
+
- lib/ensure-state/cli/option_parser.rb
|
54
|
+
- lib/ensure-state/cli.rb
|
55
|
+
- lib/ensure-state/engine.rb
|
56
|
+
- lib/ensure-state/vm_backend/base.rb
|
57
|
+
- lib/ensure-state/vm_backend/virtualbox.rb
|
58
|
+
- lib/ensure-state/errors/ensure_state_error.rb
|
59
|
+
- lib/ensure-state/errors/vm_backend_errors/backend_unavailable_error.rb
|
60
|
+
- lib/ensure-state/errors/vm_backend_errors/invalid_state_error.rb
|
61
|
+
- lib/ensure-state/errors/vm_backend_errors/state_transition_error.rb
|
62
|
+
- lib/ensure-state/errors/vm_backend_errors/vm_not_found_error.rb
|
63
|
+
- lib/ensure-state/errors/invalid_vm_backend_error.rb
|
64
|
+
- lib/ensure-state/errors/vm_backend_error.rb
|
65
|
+
- LICENSE
|
66
|
+
- README
|
67
|
+
- CHANGELOG
|
68
|
+
has_rdoc: true
|
69
|
+
homepage: http://github.com/3Crowd/virtuoso-ensure-state
|
70
|
+
licenses: []
|
71
|
+
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
hash: 3
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 23
|
92
|
+
segments:
|
93
|
+
- 1
|
94
|
+
- 3
|
95
|
+
- 6
|
96
|
+
version: 1.3.6
|
97
|
+
requirements: []
|
98
|
+
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.3.7
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: Ensures the state of virtual machines on the host system
|
104
|
+
test_files: []
|
105
|
+
|