vgh 0.1.2 → 0.2.0

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.
@@ -0,0 +1,112 @@
1
+ module VGH
2
+
3
+ # Creates the snapshots
4
+ def snap_and_tag(*args)
5
+ EC2::Snapshot.new(*args)
6
+ end
7
+
8
+ module EC2
9
+
10
+ # Creates a snapshot of the specified volume.
11
+ #
12
+ # == Usage
13
+ #
14
+ # Snapshot.new(volume_id, description, tags)
15
+ #
16
+ class Snapshot
17
+
18
+ # Create and tag snapshot, and also purge expired ones
19
+ # @param [String] volume_id The ID of the volume to snapshot
20
+ # @param [String] description The description for the new snapshot
21
+ # @param [Hash] tags A Hash containing the names and values of the tags
22
+ # @return [Snapshot] The Snapshot object.
23
+ def initialize(volume_id, description, tags)
24
+ @volume_id = volume_id
25
+ @description = description
26
+ @tags = tags
27
+ create_snapshot
28
+ tag_snapshot
29
+ purge_backups
30
+ return snapshot
31
+ end
32
+
33
+ # @return [String] The Volume ID
34
+ attr_reader :volume_id
35
+
36
+ # @return [String] The description of the snapshot
37
+ attr_reader :description
38
+
39
+ # @return [Hash] The tags hash
40
+ attr_reader :tags
41
+
42
+ # @return [Object] The Snapshot object
43
+ attr_reader :snapshot
44
+
45
+ # Creates a snapshot for the specified volume
46
+ # @return [Object] The newly created snapshot object
47
+ def create_snapshot
48
+ @snapshot = ec2.volumes[volume_id].
49
+ create_snapshot("#{description}")
50
+ message.info "Created snapshot \"#{snapshot.id}\""
51
+ return @snapshot
52
+ end
53
+
54
+ # Tags a Snapshot
55
+ def tag_snapshot
56
+ snap = snapshot
57
+ message.info "Tagging snapshot \"#{snap.id}\""
58
+ tags.map {|key, value|
59
+ ec2.tags.create(snap, key, {:value => value})
60
+ }
61
+ end
62
+
63
+ # Purges expired snapshots
64
+ def purge_backups
65
+ expired_backups.each do |snap|
66
+ message.info "Deleting expired snapshot (#{snap.id})"
67
+ snap.delete
68
+ end
69
+ end
70
+
71
+ # Creates a list of expired snapshots according to the expiration time
72
+ # specified in the app's configuration file
73
+ # @return [Array] An array of expired snapshot objects
74
+ def expired_backups
75
+ @expired_backups = []
76
+ all_backups.each {|snap|
77
+ if snap.start_time < (Time.now - backup_expiration*24*60*60)
78
+ @expired_backups.push snap
79
+ end
80
+ }
81
+ return @expired_backups
82
+ end
83
+
84
+ # Check for a an expiration period in the configuration file
85
+ def backup_expiration
86
+ expiration = config[:expiration]
87
+ if expiration.nil?
88
+ @backup_expiration = 7
89
+ else
90
+ @backup_expiration = expiration
91
+ end
92
+ return @backup_expiration.to_i
93
+ end
94
+
95
+ # Returns a list of snapshots that are named the same with the current FQDN.
96
+ def all_backups
97
+ @all ||= ec2.snapshots.
98
+ with_owner('self').
99
+ tagged('Name').tagged_values(fqdn)
100
+ end
101
+
102
+ end # class Snapshot
103
+
104
+ end # module EC2
105
+ end # module VGH
106
+
107
+ require 'vgh/system'
108
+ require 'vgh/output'
109
+ require 'vgh/configuration'
110
+ require 'vgh/ec2'
111
+ require 'vgh/ec2/metadata'
112
+ require 'vgh/ec2/volume'
@@ -0,0 +1,77 @@
1
+ module VGH
2
+ module EC2
3
+
4
+ # Collects information about the EBS volumes attached to the current instance.
5
+ # == Usage
6
+ # volumes = Volume.new
7
+ # puts volumes.list
8
+ # puts volumes.list_tagged('MyTag')
9
+ #
10
+ class Volume
11
+
12
+ # Creates an array with the IDs of all the volumes attached to the current
13
+ # instance
14
+ # @return [Array]
15
+ def list
16
+ @list = []
17
+ mappings.map {|device, info| @list.push(info.volume.id)}
18
+ return @list
19
+ end
20
+
21
+ # Creates an array with the IDs of all the volumes attached to the current
22
+ # instance, that contain a specific tag key
23
+ # @param [String] tag_key The Tag to look for
24
+ # @return [Array]
25
+ def list_tagged(tag_key)
26
+ @list_tagged = []
27
+ list.each {|vid|
28
+ volume_tags(vid).map {|volume_tag|
29
+ @list_tagged.push(vid) if volume_tag.key == tag_key
30
+ }
31
+ }
32
+ return @list_tagged
33
+ end
34
+
35
+ # Returns a Hash containing the block device mappings of the current instance.
36
+ # @return [Hash]
37
+ def mappings
38
+ message.info "Creating a list of volumes..."
39
+ @mappings ||= instance.block_device_mappings
40
+ end
41
+
42
+ # The current instance object
43
+ # @return [Instance] An instance object
44
+ def instance
45
+ @instance ||= ec2.instances[instance_id]
46
+ end
47
+
48
+ # Get volume's Name tag
49
+ # @param [String] volume_id The ID of the volume
50
+ # @return [String] The tag of the volume or (NOTAG) if a tag does not exists.
51
+ def name_tag(volume_id)
52
+ @name_tag = 'NOTAG'
53
+ volume_tags(volume_id).each {|tag|
54
+ @name_tag = tag.value if tag.key == 'Name'
55
+ }
56
+ return @name_tag
57
+ end
58
+
59
+ # Returns a collection of tags for the specified volume.
60
+ # @param [String] volume_id The id of the volume to query for tags.
61
+ # @return [TagsCollection] An array of tag objects
62
+ def volume_tags(volume_id)
63
+ @volume_tags = ec2.tags.
64
+ filter('resource-type', 'volume').
65
+ filter('resource-id', volume_id)
66
+ end
67
+
68
+ end # class Volume
69
+
70
+ end # module EC2
71
+ end # module VGH
72
+
73
+
74
+ require 'vgh/output'
75
+ require 'vgh/configuration'
76
+ require 'vgh/ec2'
77
+ require 'vgh/ec2/metadata'
data/lib/vgh/ec2.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'aws-sdk'
2
+
3
+ module VGH
4
+
5
+ # Creates a global ec2 method.
6
+ def ec2
7
+ $ec2 = AWS::EC2.new
8
+ end
9
+
10
+ # EC2 Module
11
+ module EC2
12
+ end
13
+
14
+ end # module VGH
15
+
data/lib/vgh/output.rb CHANGED
@@ -99,8 +99,6 @@ END_HEADER
99
99
  def footer
100
100
  stdout <<END_FOOTER
101
101
 
102
- ###############################################################################
103
- RUBY ROCKS!!!
104
102
  ###############################################################################"
105
103
  END_FOOTER
106
104
  end
@@ -12,70 +12,84 @@ module System
12
12
  #
13
13
  # == Usage:
14
14
  #
15
- # lvm = System::LV.new
16
- # lvm.suspend_lvs
15
+ # lvm = System::LVM.new
16
+ # lvm.suspend
17
17
  # # run the code that takes the snapshot
18
- # lvm.resume_lvs
18
+ # lvm.resume
19
19
  #
20
- class LV
20
+ class LVM
21
21
 
22
22
  # Loads variables and checks if LVM Tools are installed
23
23
  def initialize
24
- @dmcmd = '/sbin/dmsetup'
25
24
  installed?
26
25
  end
27
26
 
27
+ # @return [String] The dmsetup system command
28
+ def dm_cmd
29
+ @dmcmd ||= '/sbin/dmsetup'
30
+ end
31
+
28
32
  # Warn message if LVM tools are not installed
29
33
  def installed?
30
- message.warn "LVM Tools are not installed" unless File.exists?(@dmcmd)
34
+ if File.exists?(dm_cmd)
35
+ return true
36
+ else
37
+ message.warn "LVM Tools are not installed"
38
+ return false
39
+ end
40
+ end
41
+
42
+ # @return [String] A list of logical volumes present
43
+ def lvs
44
+ if ( installed? and System.is_root? )
45
+ @lvs ||= `#{dm_cmd} ls | /bin/grep -E -v 'swap|root'`
46
+ else
47
+ message.warn "Listing logical volume needs root privileges!"
48
+ return nil
49
+ end
31
50
  end
32
51
 
33
52
  # Test if logical volumes are present
34
53
  # @return [Boolean]
35
54
  def lvs_are_present?
36
- lvs_are_present = false
37
- @lvlist = `#{@dmcmd} ls | /bin/grep -E -v 'swap|root'`
38
- if @lvlist != "No devices found\n" then
39
- lvs_are_present = true
55
+ if lvs != "No devices found\n" then
56
+ return true
57
+ else
58
+ message.info "No logical volumes found."
59
+ return false
40
60
  end
41
- return lvs_are_present
42
61
  end
43
62
 
44
63
  # Suspend all logical volume
45
64
  def suspend
46
- if lvs_are_present?
47
- suspend_lvs
48
- else
49
- message.info "No logical volumes found."
50
- end
65
+ suspend_lvs if lvs_are_present?
51
66
  end
52
67
 
53
68
  # The actual suspend action
54
69
  def suspend_lvs
55
- for lv_name in @lvlist.split[0]
70
+ for lv_name in lvs.split[0]
56
71
  message.info "Suspending Logical Volume '#{lv_name}'..."
57
- `#{@dmcmd} suspend #{lv_name}`
72
+ `#{dm_cmd} suspend #{lv_name}`
58
73
  end
59
74
  end
60
75
 
61
76
  # Resume all logical volumes
62
77
  def resume
63
- if lvs_are_present?
64
- resume_lvs
65
- end
78
+ resume_lvs if lvs_are_present?
66
79
  end
67
80
 
68
81
  # The actual resume action
69
82
  def resume_lvs
70
- for lv_name in @lvlist.split[0]
83
+ for lv_name in lvs.split[0]
71
84
  message.info "Resuming Logical Volume '#{lv_name}'..."
72
- `#{@dmcmd} resume #{lv_name}`
85
+ `#{dm_cmd} resume #{lv_name}`
73
86
  end
74
87
  end
75
88
 
76
- end # class LV
89
+ end # class LVM
77
90
  end # module System
78
91
  end # module VGH
79
92
 
80
93
  require 'vgh/output'
94
+ require 'vgh/system'
81
95
 
@@ -16,20 +16,30 @@ class MySQL
16
16
 
17
17
  # Load defaults
18
18
  def initialize
19
- cfg = app_config
20
19
  @mysqladmin = '/usr/bin/mysqladmin'
21
20
  @mysql = '/usr/bin/mysql'
22
- @user = cfg[:mysql_user]
23
- @password = cfg[:mysql_pwd]
24
21
  end
25
22
 
23
+ # Get MySQL user
24
+ # @return [String]
25
+ def mysql_user
26
+ @mysql_user ||= config[:mysql_user]
27
+ end
28
+
29
+ # Get MySQL password
30
+ # @return [String]
31
+ def mysql_password
32
+ @mysql_password ||= config[:mysql_password]
33
+ end
34
+
35
+
26
36
  # Check if server is running and we have the right credentials
27
37
  # @return [Boolean]
28
38
  def mysql_exists?
29
39
  mysql_exists = false
30
40
  if File.exists?(@mysqladmin)
31
41
  mysql_exists = system "#{@mysqladmin} -s ping"
32
- if ! @user and ! @password
42
+ if ! mysql_user and ! mysql_password
33
43
  message.warning 'WARNING: MySQL exists but no credentials were found!'
34
44
  mysql_exists = false
35
45
  end
@@ -41,7 +51,7 @@ class MySQL
41
51
  def flush
42
52
  if mysql_exists?
43
53
  message.info 'Locking MySQL tables...'
44
- `#{@mysql} -u#{@user} -p#{@password} -e "FLUSH TABLES WITH READ LOCK"`
54
+ `#{@mysql} -u#{mysql_user} -p#{mysql_password} -e "FLUSH TABLES WITH READ LOCK"`
45
55
  end
46
56
  end
47
57
 
@@ -49,7 +59,7 @@ class MySQL
49
59
  def unlock
50
60
  if mysql_exists?
51
61
  message.info 'Unlocking MySQL tables...'
52
- `#{@mysql} -u#{@user} -p#{@password} -e "UNLOCK TABLES"`
62
+ `#{@mysql} -u#{mysql_user} -p#{mysql_password} -e "UNLOCK TABLES"`
53
63
  end
54
64
  end
55
65
 
data/lib/vgh/system.rb CHANGED
@@ -4,7 +4,7 @@ module VGH
4
4
  # the system's one)
5
5
  # @return [String]
6
6
  def fqdn
7
- remote_fqdn = app_config[:fqdn]
7
+ remote_fqdn = config[:fqdn]
8
8
  if remote_fqdn
9
9
  $fqdn ||= remote_fqdn
10
10
  else
@@ -12,15 +12,6 @@ module VGH
12
12
  end
13
13
  end
14
14
 
15
- # Check if the script is run as root
16
- # @return [Boolean]
17
- def is_root?
18
- if Process.uid == 0
19
- return true
20
- else
21
- return false
22
- end
23
- end
24
15
 
25
16
  # This is a parent class for different system actions performed by the scripts
26
17
  # included in this gem. For more information see the classes defined under
@@ -32,12 +23,61 @@ module VGH
32
23
  #
33
24
  module System
34
25
 
26
+ # Check if the script is run as root
27
+ # @return [Boolean]
28
+ def self.is_root?
29
+ if Process.uid == 0
30
+ return true
31
+ else
32
+ return false
33
+ end
34
+ end
35
+
35
36
  # Returns the current system's FQDN
36
37
  # @return [String]
37
38
  def self.fqdn
38
39
  $fqdn ||= `hostname -f`
39
40
  end
40
41
 
42
+ # Returns the current system's FQDN
43
+ # @return [String]
44
+ def self.lock
45
+ unless remotely?
46
+ mysql.flush
47
+ lvm.suspend
48
+ end
49
+ end
50
+
51
+ # Returns the current system's FQDN
52
+ # @return [String]
53
+ def self.unlock
54
+ unless remotely?
55
+ mysql.unlock
56
+ lvm.resume
57
+ end
58
+ end
59
+
60
+ # Checks if this script is run remotely.
61
+ # @return [Boolean]
62
+ def self.remotely?
63
+ cfg = config
64
+ if cfg[:instance] or cfg[:fqdn]
65
+ return true
66
+ else
67
+ return false
68
+ end
69
+ end
70
+
71
+ # Initializes the MySQL class
72
+ def self.mysql
73
+ mysql ||= System::MySQL.new
74
+ end
75
+
76
+ # Initializes the LVM class
77
+ def self.lvm
78
+ lvm ||= System::LVM.new
79
+ end
80
+
41
81
  end # module System
42
82
 
43
83
  end # module VGH
data/lib/vgh/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module VGH
2
2
  # Version number
3
- VERSION = "0.1.2"
3
+ VERSION = "0.2.0"
4
4
 
5
5
  # Returns the version number
6
6
  def version
data/lib/vgh.rb CHANGED
@@ -28,6 +28,8 @@ module VGH
28
28
  case app
29
29
  when 'ec2-backup'
30
30
  APPS::EC2_Backup.new.run
31
+ when 'checkpoint'
32
+ APPS::Checkpoint.new.run
31
33
  end
32
34
 
33
35
  # Display footer
@@ -48,6 +50,6 @@ module VGH
48
50
  end # module VGH
49
51
 
50
52
  require "vgh/output"
51
- require "vgh/system"
52
53
  require "vgh/apps"
53
54
  require "vgh/apps/ec2_backup"
55
+ require "vgh/apps/checkpoint"
data/spec/cli_spec.rb CHANGED
@@ -11,34 +11,26 @@ describe VGH::CLI do
11
11
  ARGV[0] = app
12
12
  end
13
13
 
14
- it "Should get the app name as the first argument" do
15
- VGH::CLI.new.options[:app].should eq app
16
- end
17
-
18
- context "When '-v' passed to the command line" do
19
- it "Verbosity should be enabled" do
20
- ARGV.push('-v')
21
- VGH::CLI.new.options[:verbose].should be_true
22
- end
14
+ it 'Should get the app name as the first argument' do
15
+ subject.options[:app].should eq app
23
16
  end
24
17
 
25
- context "When '-v' NOT passed to the command line" do
26
- it "Verbosity should be disabled" do
27
- VGH::CLI.new.options[:verbose].should be_false
28
- end
18
+ it 'Verbosity' do
19
+ subject.options[:verbose].should be_false
29
20
  end
30
21
 
31
- context "When '-l' passed to the command line" do
32
- it "Logging should be enabled" do
33
- ARGV.push('-l')
34
- VGH::CLI.new.options[:logging].should be_true
35
- end
22
+ it 'No Verbosity' do
23
+ ARGV.push('--verbose')
24
+ subject.options[:verbose].should be_true
36
25
  end
37
26
 
38
- context "When '-l' NOT passed to the command line" do
39
- it "Logging should be disabled" do
40
- VGH::CLI.new.options[:logging].should be_false
41
- end
27
+ it 'Logging' do
28
+ ARGV.push('--logging')
29
+ subject.options[:logging].should be_true
30
+ end
31
+
32
+ it 'No Logging' do
33
+ subject.options[:logging].should be_false
42
34
  end
43
35
 
44
36
  end
@@ -0,0 +1,44 @@
1
+ require 'helpers/spec'
2
+ require 'vgh'
3
+ require 'vgh/configuration'
4
+
5
+ describe VGH::Configuration do
6
+
7
+ let(:configuration) {VGH::Configuration.allocate}
8
+ let(:cli_confdir) {'/tmp'}
9
+ let(:config_hash) {"---
10
+ :key1: 'value1'
11
+ :key2: 'value2'"}
12
+
13
+ before(:each) do
14
+ configuration.stub(:cli).and_return({:confdir => nil})
15
+ end
16
+
17
+ it 'Should return the default config directory' do
18
+ configuration.global_config_dir.should eq('/etc/vgh')
19
+ end
20
+
21
+ it 'Should return the user config directory' do
22
+ configuration.user_config_dir =~ /.*\.vgh$/
23
+ end
24
+
25
+ it 'Should return a config directory from cli or return users directory' do
26
+ configuration.confdir.should =~ /.*\.vgh/
27
+ configuration.stub(:cli).and_return({:confdir => cli_confdir})
28
+ configuration.confdir.should eq(cli_confdir)
29
+ end
30
+
31
+ it 'Should return the main configuration file' do
32
+ configuration.config_file.should =~ /.*\.vgh\/config\.yml/
33
+ end
34
+
35
+ it 'Should initialize clean' do
36
+ cfg = VGH::Configuration
37
+ [:message, :cli, :log, :aws_config].each {|s|
38
+ cfg.any_instance.stub(s).and_return(Dummy.new)
39
+ }
40
+ cfg.any_instance.stub(:validate).and_return(config_hash)
41
+ subject.config.should eq(config_hash)
42
+ end
43
+
44
+ end
@@ -0,0 +1,14 @@
1
+ require 'helpers/spec'
2
+ require 'vgh/ec2/metadata'
3
+
4
+ describe VGH::EC2::MetaData do
5
+
6
+ let(:response) {'Remote server response'}
7
+ it 'Should receive valid response from the AWS metadata server' do
8
+ subject.stub_chain(:open, :read).and_return(response)
9
+ subject.instance_id.should eq(response)
10
+ subject.root_device.should eq(response)
11
+ end
12
+
13
+ end
14
+
@@ -0,0 +1,39 @@
1
+ require 'helpers/spec'
2
+ require 'aws-sdk'
3
+ require 'vgh/ec2/snapshot'
4
+ require 'vgh/system'
5
+
6
+ describe VGH::EC2::Snapshot do
7
+
8
+ include_context 'AWS Dummy'
9
+ let(:snap) {VGH::EC2::Snapshot.allocate}
10
+
11
+ before(:each) do
12
+ snap.stub(:snapshot).and_return(@snapshot)
13
+ snap.stub(:message).and_return(Dummy.new)
14
+ snap.stub(:tags).and_return(@tag_hash)
15
+ snap.stub(:ec2).and_return(@ec2)
16
+ snap.stub(:fqdn).and_return(@fqdn)
17
+ end
18
+
19
+ it "Should tag a snapshot" do
20
+ snap.tag_snapshot.each{|t|
21
+ t.should be_a AWS::EC2::Tag
22
+ }
23
+ end
24
+
25
+ it "Should list all snapshots" do
26
+ snap.all_backups.should be_an AWS::EC2::SnapshotCollection
27
+ end
28
+
29
+ it "Should create a list of expired backups" do
30
+ snap.expired_backups.should be_an Array
31
+ end
32
+
33
+ it "Should return a backup expiration integer" do
34
+ snap.stub(:config).and_return({:expiration => 5})
35
+ snap.backup_expiration.should eq(5)
36
+ end
37
+
38
+ end
39
+
@@ -0,0 +1,47 @@
1
+ require 'helpers/spec'
2
+
3
+ require 'aws-sdk'
4
+ require 'vgh/ec2/volume'
5
+
6
+ describe VGH::EC2::Volume do
7
+
8
+ include_context 'AWS Dummy'
9
+
10
+ let(:volume) {VGH::EC2::Volume.new}
11
+
12
+ before(:each) do
13
+ volume.stub(:message).and_return(Dummy.new)
14
+ volume.stub(:ec2).and_return(@ec2)
15
+ end
16
+
17
+ it "Should return an instance" do
18
+ volume.stub(:instance_id).and_return(@instance.id)
19
+ volume.instance.should be_a(AWS::EC2::Instance)
20
+ end
21
+
22
+ it "Should create a hash with block device mappings" do
23
+ volume.stub_chain(:instance, :block_device_mappings).
24
+ and_return(@instance_mappings)
25
+ volume.mappings.should be_a_kind_of Hash
26
+ end
27
+
28
+ it "Should return a collection of volume tags" do
29
+ volume.volume_tags(@volume2.id).should be_a(@tag_collection)
30
+ end
31
+
32
+ it "Should return the value of the name tag or (NOTAG)" do
33
+ volume.name_tag(@volume1.id).should be_a String
34
+ end
35
+
36
+ it "Should create a list of volumes" do
37
+ volume.stub(:mappings).and_return(@instance_mappings)
38
+ volume.list.should be_a Array
39
+ end
40
+
41
+ it "Should return a list of volumes that need a checkpoint" do
42
+ volume.stub(:list).and_return([])
43
+ volume.list_tagged('CHECKPOINT').should be_a Array
44
+ end
45
+
46
+ end
47
+