vmreverter 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MmJiODIzNGFjNTY0ZTZhNWI3NDViN2U4ZWU1ZDQwNDQyYTQxYTAzNA==
5
+ data.tar.gz: !binary |-
6
+ NzZkZjlmN2YwMmY5NWMzYWVmOTJjMzQ0MGEzNzJhZThhMTYxOGUyNg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZjUyZTljN2JiMDY1NzRjZGFmOTI2MTk3ZmNmZDg2YzE1NTk4NTNhZjQ4YWUx
10
+ MDhmODUwZTAzYmYwNjgyNGRkYWRiOTJhZDJiOTJiMzRkYzFkNzBiZjA2Mjkx
11
+ YmZhYjc5ZjQ1ZmY4NWMyYzk4MTliMGE1MDE4OTAyMTk3ZTI3NGY=
12
+ data.tar.gz: !binary |-
13
+ YzAxYWZhZTU1ZmE1NzQzYTU0MDhiZDlmZjgwODdkZGM2YmU4ZjY4MDU4OGVh
14
+ N2ZjYjE3Y2FmNDFkMGM4ODU4NjE4YTExZDgxMjc0MDRjN2U3OWQ2YjdhYzgy
15
+ NzQwMGFhYzk4MTM0NDU5ZGNiMmIzNTU5M2ZmMmY5MjczYTQ2ZWU=
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ *.dat
4
+ .bundle
5
+ .config
6
+ build.sh
7
+ coverage
8
+ InstalledFiles
9
+ lib/bundler/man
10
+ pkg
11
+ rdoc
12
+ spec/reports
13
+ test/tmp
14
+ test/version_tmp
15
+ tmp
16
+
17
+ # YARD artifacts
18
+ .yardoc
19
+ _yardoc
20
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "rake"
4
+
5
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,12 @@
1
+ Copyright (c) 2014, Scott MacGregor
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+
8
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
+
10
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # VM Reverter
2
+
3
+ This gem is used to revert a collection of virtual machines from many hypervisor frameworks using a yaml file back to specific snapshots.
4
+
5
+ ## Supported Hypervisors
6
+
7
+ * VMWare VSphere Server [vshpere] ~> rbvmomi
8
+ * AWS [aws] ~> Blimpy / Fog
9
+
10
+ ## Vsphere Credentials
11
+
12
+ ```yaml
13
+ # ~/.fog
14
+ :default:
15
+ :vsphere_server: 'vsphere.mydomain.com'
16
+ :vsphere_username: 'john_doe'
17
+ :vsphere_password: '$3cr3+_$@uc3'
18
+ ```
19
+ ## Configuration File
20
+
21
+ ### Required YAML Header:
22
+
23
+ * HOSTS
24
+ * vm-name
25
+
26
+ ### Required Fields:
27
+
28
+ * snapshots
29
+ * hypervisor
30
+
31
+ ### Optional fields:
32
+
33
+ * power - overide the saved state of the power of the snapshot [up|down|destroy]
34
+ * tag - add a meta tag to a host
35
+
36
+ ## Configuration File - Revert
37
+
38
+ revert.conf
39
+
40
+ ```yaml
41
+ HOSTS:
42
+ test-server01:
43
+ hypervisor: vsphere
44
+ snapshot: gold-image
45
+ tag: interesting
46
+ power: up
47
+ test-server02:
48
+ hypervisor: vsphere
49
+ snapshot: gold-image
50
+ test-server03:
51
+ hypervisor: aws
52
+ aws-options:
53
+ ami-size: m1.small
54
+ ami-region: us-west-2
55
+ security-group: clients
56
+ ```
57
+
58
+
59
+ ### Configuration Example - Stop VMS
60
+
61
+ This configuration will remove the hosts from aws.
62
+
63
+ ```yaml
64
+ HOSTS:
65
+ test-server01:
66
+ hypervisor: aws
67
+ power: down
68
+ test-server02:
69
+ hypervisor: vsphere
70
+ power: down
71
+ ```
72
+
73
+
74
+ ### Configuration Example - Destruction
75
+
76
+ This configuration will remove the hosts from the hypervisor (aws only).
77
+
78
+ ```yaml
79
+ HOSTS:
80
+ test-server01:
81
+ hypervisor: aws
82
+ power: destroy
83
+ test-server02:
84
+ hypervisor: aws
85
+ power: destroy
86
+ ```
87
+
88
+ ## CLI Usage
89
+
90
+ Using local conf file and turning on all machines after reverting.
91
+
92
+ ```
93
+ $> vmreverter -c ./revert.conf
94
+ ```
95
+ ## Tested Against
96
+
97
+ * Ruby 1.9.3
98
+ * VSphere 5.5
99
+
100
+ ## TODO
101
+
102
+ * Unit Tests
103
+ * API Mocks
104
+
105
+ Further Hypervisor implementations:
106
+
107
+ * OpenStack ~> Blimpy
108
+ * VirtualBox ~> Vagrant
109
+
110
+
111
+ ## Legal notes
112
+
113
+ This is some wildly refactored code (..legal speak..) from the puppet_acceptance gem. puppet labs, puppet_acceptance, or its authors do not endorse this code.
114
+
115
+ Changes include namespace swaps, class additions, class removals, method additions, method removals, and complete code refactoring.
116
+
117
+ All original code maintains its copyright from its original author(puppetlabs) and licensing of Apache 2, and only change sets from git rev
118
+
119
+ https://github.com/puppetlabs/puppet-acceptance/commit/68272162d0b30905f498a4d71ff641374d3eb8f0
120
+
121
+ onward (circa Feb 2014) within the vmreverter namespace will include permissive of BSD-3 LICENSING as such approved by law.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ task :default => [:test]
6
+
7
+ Rake::TestTask.new do |test|
8
+ test.libs << "test"
9
+ test.test_files = Dir[ "test/test_*.rb" ]
10
+ test.verbose = true
11
+ end
data/bin/vmreverter ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems' unless defined?(Gem)
4
+ require 'vmreverter'
5
+
6
+ Vmreverter::CLI.new.execute!
7
+
8
+ puts "done."
9
+ exit 0
@@ -0,0 +1,17 @@
1
+ puppet-acceptance - Puppet's system level acceptance test harness
2
+
3
+ Copyright (C) 2011-2013 Puppet Labs Inc
4
+
5
+ Puppet Labs can be contacted at: info@puppetlabs.com
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
@@ -0,0 +1,50 @@
1
+ module Vmreverter
2
+ class CLI
3
+ def initialize
4
+ @options = Vmreverter::Options.parse_args
5
+ @logger = Vmreverter::Logger.new(@options)
6
+ @options[:logger] = @logger
7
+
8
+ if not @options[:config]
9
+ report_and_raise(@logger, ArgumentError.new("Missing config, specify one (-c or --config)!"), "CLI: initialize")
10
+ end
11
+
12
+ @logger.debug("Options")
13
+ @options.each do |opt, val|
14
+ if val and val != []
15
+ @logger.debug("\t#{opt.to_s}:")
16
+ if val.kind_of?(Array)
17
+ val.each do |v|
18
+ @logger.debug("\t\t#{v.to_s}")
19
+ end
20
+ else
21
+ @logger.debug("\t\t#{val.to_s}")
22
+ end
23
+ end
24
+ end
25
+
26
+ @config = Vmreverter::ConfigTester.new(@options[:config], @options)
27
+
28
+ end
29
+
30
+ def execute!
31
+
32
+ begin
33
+ trap(:INT) do
34
+ @logger.warn "Interrupt received; exiting..."
35
+ exit(1)
36
+ end
37
+
38
+ begin
39
+ @vmmanager = Vmreverter::VMManager.new(@config, @options, @logger)
40
+ @vmmanager.invoke
41
+ @vmmanager.close_connection
42
+ rescue => e
43
+ raise e
44
+ end
45
+
46
+ end #trap
47
+ end #execute!
48
+
49
+ end
50
+ end
@@ -0,0 +1,63 @@
1
+
2
+
3
+ module Vmreverter
4
+ # Config was taken by Ruby.
5
+ class ConfigTester
6
+
7
+ attr_accessor :logger
8
+ def initialize(config_file, options)
9
+ @options = options
10
+ @logger = options[:logger]
11
+ @config = load_file(config_file)
12
+ end
13
+
14
+ def [](key)
15
+ @config[key]
16
+ end
17
+
18
+ def load_file(config_file)
19
+ if config_file.is_a? Hash
20
+ config = config_file
21
+ else
22
+ config = YAML.load_file(config_file)
23
+
24
+ # Make sure the tag array is present for all hosts
25
+ config['HOSTS'].each_key do |host|
26
+ config['HOSTS'][host]['tags'] ||= []
27
+
28
+ report_and_raise(@logger, RuntimeError.new("Missing hypervisor: (#{host})"), "ConfigTester::load_file") unless config['HOSTS'][host].include? "hypervisor"
29
+ hypervisor = config['HOSTS'][host]['hypervisor'].downcase
30
+ #check to see if this host has a hypervisor
31
+ report_and_raise(@logger, RuntimeError.new("Invalid hypervisor: #{hypervisor} (#{host})"), "ConfigTester::load_file") unless Vmreverter::HYPERVISOR_TYPES.include? hypervisor
32
+
33
+ #check to see if this host has a hypervisor
34
+ report_and_raise(@logger, RuntimeError.new("Missing snapshot: (#{host})"), "ConfigTester::load_file") unless config['HOSTS'][host].include? "snapshot"
35
+
36
+ end
37
+
38
+ end
39
+
40
+ # Merge some useful date into the config hash
41
+ config['CONFIG'] ||= {}
42
+
43
+ config
44
+ end
45
+
46
+
47
+ # Print out test configuration
48
+ def dump
49
+
50
+ # Access "tags" for each host
51
+ @config["HOSTS"].each_key do|host|
52
+ @config["HOSTS"][host]['tags'].each do |tag|
53
+ @logger.notify "Tags for #{host} #{tag}"
54
+ end
55
+ end
56
+
57
+ # Access @config keys/values
58
+ @config["CONFIG"].each_key do|cfg|
59
+ @logger.notify "Config Key|Val: #{cfg} #{@config["CONFIG"][cfg].inspect}"
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,121 @@
1
+ module Vmreverter
2
+ class AWS
3
+
4
+ #AWS support will dynamically create a Security Group for you if you specify ports in the Blimpfile, this means you can easily stand up a machine with specific ports open.
5
+ #Blimpy uses a unique hash of the ports to avoid re-creating the Security Groups unless necessary.
6
+ #Blimpy will import your ~/.ssh/id_rsa.pub or ~/.ssh/id_dsa.pub into a Key Pair in every region that you use in your Blimpfiles.
7
+ def initialize(blimpy_hosts, options, config)
8
+ @options = options
9
+ @config = config
10
+ @logger = options[:logger]
11
+ @blimpy_hosts = blimpy_hosts
12
+ require 'rubygems' unless defined?(Gem)
13
+ require 'yaml' unless defined?(YAML)
14
+ begin
15
+ require 'blimpy'
16
+ rescue LoadError
17
+ raise "Unable to load Blimpy, please ensure its installed"
18
+ end
19
+
20
+ @fleet = Blimpy.fleet do |fleet|
21
+ @blimpy_hosts.each do |host|
22
+ #use snapshot provided for this host - This is an AMI!
23
+ # host
24
+ # ami-size: m1.small
25
+ # ami-region: 'us-west-2'
26
+ # security-group: 'Clients'
27
+
28
+ if not host['snapshot']
29
+ raise "No snapshot/ami provided for AWS provisioning"
30
+ end
31
+
32
+ @logger.debug "Configuring hypervision AWS for host #{host.name}(#{host['snapshot']}:#{host['amisize']}) "
33
+
34
+ fleet.add(:aws) do |ship|
35
+ ship.name = host.name
36
+ ship.group = host['security-group']
37
+ ship.image_id = host['snapshot']
38
+ ship.flavor = host['amisize'] || 'm1.small'
39
+ ship.region = host['ami-region'] || 'us-west-2'
40
+ ship.tags = host['tags']
41
+ ship.username = 'root'
42
+ end #fleet
43
+ @logger.debug "Configuration completed."
44
+ end #blimpy_hosts
45
+ end#fleet
46
+
47
+ return self
48
+ end #init
49
+
50
+ def invoke
51
+ if (@config['HOSTS'][name]['launch'] == :on)
52
+ revert
53
+ end
54
+ end
55
+
56
+ def close_connection
57
+ @fleet = nil
58
+ end
59
+
60
+ private
61
+
62
+ def revert
63
+ @logger.notify "Begin Launching AWS Hosts"
64
+
65
+ # Attempt to start the fleet, we wrap it with some error handling that deals
66
+ # with generic Fog errors and retrying in case these errors are transient.
67
+ fleet_retries = 0
68
+ begin
69
+ @fleet.start
70
+ rescue Fog::Errors::Error => ex
71
+ fleet_retries += 1
72
+ if fleet_retries <= 3
73
+ sleep_time = rand(10) + 10
74
+ @logger.notify("Calling fleet.destroy, sleeping #{sleep_time} seconds and retrying fleet.start due to Fog::Errors::Error (#{ex.message}), retry attempt #{fleet_retries}.")
75
+ begin
76
+ timeout(30) do
77
+ @fleet.destroy
78
+ end
79
+ rescue
80
+ end
81
+ sleep sleep_time
82
+ retry
83
+ else
84
+ @logger.error("Retried Fog #{fleet_retries} times, giving up and throwing the exception")
85
+ raise ex
86
+ end
87
+ end
88
+ end
89
+
90
+ # https://github.com/rtyler/blimpy/blob/2d2c711bfcb129f5eb0346f08da62e0cfcde7917/lib/blimpy/fleet.rb#L135
91
+ def power toggle=:on
92
+ @logger.notify "Power #{toggle} AWS boxes"
93
+ if (toggle == :off)
94
+ start = Time.now
95
+ @fleet.stop
96
+ @logger.notify "Spent %.2f seconds halting" % (Time.now - start)
97
+ else (toggle == :on)
98
+ start = Time.now
99
+ @fleet.start
100
+ @logger.notify "Spent %.2f seconds booting" % (Time.now - start)
101
+ end
102
+ end
103
+
104
+ def destroy
105
+ #fleet = Blimpy.fleet do |fleet|
106
+ # @blimpy_hosts.each do |host|
107
+ # fleet.add(:aws) do |ship|
108
+ # ship.name = host.name
109
+ # end
110
+ # end
111
+ #end
112
+
113
+ @logger.notify "Destroying Blimpy boxes"
114
+ #fleet.destroy
115
+ @fleet.destroy
116
+ end
117
+
118
+
119
+
120
+ end
121
+ end
@@ -0,0 +1,96 @@
1
+ # Apache Licensed - (github/puppetlabs) ripped from puppet_acceptance. ** See Legal notes
2
+ # Changes include namespace swaps, method removal, method additions, and complete code refactoring
3
+ module Vmreverter
4
+ class Vsphere
5
+
6
+ def initialize(vsphere_hosts, options, config)
7
+ @options = options
8
+ @config = config
9
+ @logger = options[:logger]
10
+ @vsphere_hosts = vsphere_hosts
11
+ require 'yaml' unless defined?(YAML)
12
+ vsphere_credentials = VsphereHelper.load_config options[:auth]
13
+
14
+ @logger.notify "Connecting to vSphere at #{vsphere_credentials[:server]}" + " with credentials for #{vsphere_credentials[:user]}"
15
+
16
+ @vsphere_helper = VsphereHelper.new( vsphere_credentials )
17
+
18
+ #Transpose Hash for @vsphere_vms = {"test-server01" => "gold-image", "test-server02" => "silver-image"}
19
+ @vsphere_vms = {}
20
+ @vsphere_hosts.each do |host|
21
+ @vsphere_vms[host] = config['HOSTS'][host]['snapshot']
22
+ end
23
+
24
+ #Index Hosts Available via rbvmomi
25
+ vms = @vsphere_helper.find_vms(@vsphere_vms.keys)
26
+
27
+ # Test if host exists and host's snapshot requested exists
28
+ @vsphere_vms.each_pair do |name, snap|
29
+ #Find Host in Index
30
+ report_and_raise(@logger, RuntimeError.new("Couldn't find VM #{name} in vSphere!"), "VSphere::initialize") unless vm = vms[name]
31
+ #snap ~> config['HOSTS'][vm]['snapshot']
32
+ report_and_raise(@logger, RuntimeError.new("Could not find snapshot '#{snap}' for VM #{vm.name}!"), "VSphere::initialize") unless @vsphere_helper.find_snapshot(vm, snap)
33
+ end
34
+
35
+ return self
36
+ end
37
+
38
+ def invoke
39
+ revert
40
+ end
41
+
42
+ def close_connection
43
+ @vsphere_helper.close_connection
44
+ end
45
+
46
+ private
47
+
48
+ def revert
49
+ @logger.notify "Begin Reverting"
50
+ @vsphere_vms.each_pair do |name, snap|
51
+
52
+ vm = @vsphere_helper.find_vms(@vsphere_vms.keys)[name]
53
+ @logger.notify "Reverting #{vm.name} to snapshot '#{snap}'"
54
+ start = Time.now
55
+
56
+ # This will block for each snapshot...
57
+ # The code to issue them all and then wait until they are all done sucks
58
+ snapshot = @vsphere_helper.find_snapshot(vm, snap)
59
+ snapshot.RevertToSnapshot_Task.wait_for_completion
60
+
61
+ time = Time.now - start
62
+ @logger.notify "Spent %.2f seconds reverting" % time
63
+
64
+ if (@config['HOSTS'][name]['power'] == 'up')
65
+ host_power_on(vm)
66
+ elsif (@config['HOSTS'][name]['power'] == 'down')
67
+ host_power_off(vm)
68
+ else
69
+ @logger.notify "VM #{name} defaulting to snapshot '#{snap}' power setting"
70
+ end
71
+
72
+ end
73
+ end
74
+
75
+ def host_power_on(vm)
76
+ unless vm.runtime.powerState == "poweredOn"
77
+ @logger.notify "Booting #{vm.name}"
78
+ start = Time.now
79
+ vm.PowerOnVM_Task.wait_for_completion
80
+ @logger.notify "Spent %.2f seconds booting #{vm.name}" % (Time.now - start)
81
+ end
82
+ end
83
+
84
+ def host_power_off(vm)
85
+ unless vm.runtime.powerState == "poweredOff"
86
+ @logger.notify "Shutting down #{vm.name}"
87
+ start = Time.now
88
+ vm.PowerOffVM_Task.wait_for_completion
89
+ @logger.notify "Spent %.2f seconds halting #{vm.name}" % (Time.now - start)
90
+ end
91
+ end
92
+
93
+
94
+
95
+ end
96
+ end