virtuoso-ensure-state 0.0.6.beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|