cloudflock 0.4.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.
Files changed (33) hide show
  1. data/bin/flock +32 -0
  2. data/bin/flock-profile +17 -0
  3. data/bin/flock-servers +24 -0
  4. data/bin/flock.default +24 -0
  5. data/lib/cloudflock/error.rb +26 -0
  6. data/lib/cloudflock/interface/cli/app/common/servers.rb +128 -0
  7. data/lib/cloudflock/interface/cli/app/servers/migrate.rb +357 -0
  8. data/lib/cloudflock/interface/cli/app/servers/profile.rb +88 -0
  9. data/lib/cloudflock/interface/cli/app/servers.rb +2 -0
  10. data/lib/cloudflock/interface/cli/console.rb +213 -0
  11. data/lib/cloudflock/interface/cli/opts/servers.rb +20 -0
  12. data/lib/cloudflock/interface/cli/opts.rb +87 -0
  13. data/lib/cloudflock/interface/cli.rb +15 -0
  14. data/lib/cloudflock/patch/fog.rb +113 -0
  15. data/lib/cloudflock/remote/ssh.rb +311 -0
  16. data/lib/cloudflock/target/servers/data/exceptions/base.txt +44 -0
  17. data/lib/cloudflock/target/servers/data/exceptions/platform/amazon.txt +10 -0
  18. data/lib/cloudflock/target/servers/data/exceptions/platform/debian.txt +0 -0
  19. data/lib/cloudflock/target/servers/data/exceptions/platform/redhat.txt +5 -0
  20. data/lib/cloudflock/target/servers/data/exceptions/platform/suse.txt +1 -0
  21. data/lib/cloudflock/target/servers/data/post-migration/chroot/base.txt +1 -0
  22. data/lib/cloudflock/target/servers/data/post-migration/chroot/platform/amazon.txt +19 -0
  23. data/lib/cloudflock/target/servers/data/post-migration/pre/base.txt +3 -0
  24. data/lib/cloudflock/target/servers/data/post-migration/pre/platform/amazon.txt +4 -0
  25. data/lib/cloudflock/target/servers/migrate.rb +466 -0
  26. data/lib/cloudflock/target/servers/platform/v1.rb +97 -0
  27. data/lib/cloudflock/target/servers/platform/v2.rb +93 -0
  28. data/lib/cloudflock/target/servers/platform.rb +133 -0
  29. data/lib/cloudflock/target/servers/profile.rb +394 -0
  30. data/lib/cloudflock/target/servers.rb +5 -0
  31. data/lib/cloudflock/version.rb +3 -0
  32. data/lib/cloudflock.rb +10 -0
  33. metadata +128 -0
@@ -0,0 +1,88 @@
1
+ require 'cloudflock/target/servers'
2
+ require 'cloudflock/interface/cli/app/common/servers'
3
+
4
+ # Public: The Profile class provides the interface to produces profiles
5
+ # describing servers running Unix-like operating systems.
6
+ class CloudFlock::Interface::CLI::App::Servers::Profile
7
+ include CloudFlock::Interface::CLI::App::Common::Servers
8
+ include CloudFlock::Target::Servers
9
+ CLI = CloudFlock::Interface::CLI::Console
10
+
11
+ # Public: Begin Servers migration on the command line
12
+ #
13
+ # opts - Hash containing options mappings.
14
+ def initialize(opts)
15
+ resume = opts[:resume]
16
+ source_host_def = define_source(opts[:config])
17
+ source_host_ssh = CLI.spinner("Logging in to #{source_host_def[:host]}") do
18
+ host_login(source_host_def)
19
+ end
20
+
21
+ profile = CLI.spinner("Checking source host") do
22
+ profile = Profile.new(source_host_ssh)
23
+ profile.build
24
+ profile
25
+ end
26
+ platform = Platform::V2.new(profile[:cpe])
27
+
28
+ memory = profile[:memory]
29
+ memory_percent = memory[:mem_used].to_f / memory[:total] * 100
30
+ swapping = memory[:swapping?]
31
+ ftag = "#{CLI.bold}%15s#{CLI.reset}:"
32
+ hist_mem = profile[:memory_hist][:mem_used]
33
+
34
+ puts
35
+ puts "#{CLI.bold}System Information#{CLI.reset}"
36
+ puts "#{ftag} #{platform} (#{profile[:cpe]})" % "OS"
37
+ puts "#{ftag} #{profile[:arch]}" % "Arch"
38
+ puts "#{ftag} #{profile[:hostname]}" % "Hostname"
39
+ puts
40
+
41
+ puts "#{CLI.bold}CPU Statistics#{CLI.reset}"
42
+ puts "#{ftag} %d" % ["CPU Count", profile[:cpu][:count]]
43
+ puts "#{ftag} %d MHz" % ["CPU Speed", profile[:cpu][:speed]]
44
+ puts
45
+
46
+ puts "#{CLI.bold}Memory Statistics#{CLI.reset}"
47
+ puts "#{ftag} %d MiB" % ["Total RAM", memory[:total]]
48
+ puts "#{ftag} %d MiB (%2.1f%%)" % ["RAM Used", memory[:mem_used],
49
+ memory_percent]
50
+ puts "#{ftag} %d MiB" % ["Swap Used", memory[:swap_used]] if swapping
51
+ puts "#{ftag} %d%%" % ["Hist. RAM Used", hist_mem] unless hist_mem.nil?
52
+ puts
53
+
54
+ puts "#{CLI.bold}Hard Disk Statistics#{CLI.reset}"
55
+ puts "#{ftag} %2.1f GB" % ["Disk Used", profile[:disk]]
56
+ puts
57
+
58
+ puts "#{CLI.bold}System Statistics#{CLI.reset}"
59
+ puts "#{ftag} #{profile[:io][:uptime]}" % "Uptime"
60
+ puts "#{ftag} #{profile[:io][:wait]}" % "I/O Wait"
61
+ puts
62
+
63
+ puts "#{CLI.bold}IP Information#{CLI.reset}"
64
+ puts "#{ftag} #{profile[:ip][:public].join(', ')}" % "Public"
65
+ puts "#{ftag} #{profile[:ip][:private].join(', ')}" % "Private"
66
+ puts
67
+
68
+ puts "#{CLI.bold}MySQL Databases#{CLI.reset}"
69
+ puts "#{ftag} #{profile[:db][:count]}" % "Number"
70
+ puts "#{ftag} #{profile[:db][:size]}" % "Total Size"
71
+ puts
72
+
73
+ puts "#{CLI.bold}Libraries#{CLI.reset}"
74
+ puts "#{ftag} #{profile[:lib][:libc]}" % "LIBC"
75
+ puts "#{ftag} #{profile[:lib][:perl]}" % "Perl"
76
+ puts "#{ftag} #{profile[:lib][:python]}" % "Python"
77
+ puts "#{ftag} #{profile[:lib][:ruby]}" % "Ruby"
78
+ puts "#{ftag} #{profile[:lib][:php]}" % "PHP"
79
+ unless profile.warnings.empty?
80
+ puts
81
+ print CLI.red + CLI.bold
82
+ profile.warnings.each { |warning| puts warning }
83
+ print CLI.reset
84
+ end
85
+
86
+ source_host_ssh.logout!
87
+ end
88
+ end
@@ -0,0 +1,2 @@
1
+ require 'cloudflock/interface/cli'
2
+ require 'cloudflock/interface/cli/app/common/servers'
@@ -0,0 +1,213 @@
1
+ # Public: Provide methods to abstract and simplify interaction with a user via
2
+ # a command-line application.
3
+ #
4
+ # Examples
5
+ #
6
+ # # prompt for a question with free-form input
7
+ # answer = Console.prompt("Question")
8
+ #
9
+ # # Print bolded text
10
+ # puts "#{Console.bold}Bold text#{Console.reset}"
11
+ module CloudFlock::Interface::CLI::Console extend self
12
+ # Public: Prompt user for input, allowing for a default answer and a list of
13
+ # valid responses.
14
+ #
15
+ # question - String containing the question to present to the user.
16
+ # args - Hash containing arguments to control acceptable responses.
17
+ # (default: {}):
18
+ # :default_answer - String containing the default answer. If the
19
+ # default is nil, a non-empty answer MUST be given.
20
+ # :valid_answers - An Array containing all valid responses.
21
+ #
22
+ # Returns a String containing the answer provided by the user.
23
+ def prompt(question, args = {})
24
+ default = args[:default_answer].to_s
25
+ allow_empty = !args[:default_answer].nil?
26
+ valid = args[:valid_answers] || []
27
+
28
+ default_display = default.empty? ? "" : "[%s]" % default.strip
29
+ question.strip!
30
+
31
+ acceptable = false
32
+ until acceptable
33
+ printf("%s %s> ", question, default_display)
34
+ answer = readline.strip
35
+
36
+ if answer.empty? && allow_empty
37
+ acceptable = true
38
+ elsif valid.empty? && !answer.empty?
39
+ acceptable = true
40
+ elsif !(valid.grep(answer)).empty? && !valid.empty?
41
+ acceptable = true
42
+ end
43
+ end
44
+
45
+ answer.empty? ? default.to_s : answer
46
+ end
47
+
48
+ # Public: Wrap Console#prompt but require a Y/N response.
49
+ #
50
+ # question - String containing the question to present to the user.
51
+ # args - Hash containing arguments to control acceptable responses.
52
+ # (default: {}):
53
+ # :default_answer - String containing the default answer.
54
+ #
55
+ # Returns true or false corresponding to Y or N answer respectively.
56
+ def prompt_yn(question, args = {})
57
+ args[:valid] = []
58
+ answer = nil
59
+
60
+ until answer =~ /^[yn]/i
61
+ answer = prompt(question, args)
62
+ end
63
+
64
+ /^n/i.match(answer).nil?
65
+ end
66
+
67
+ # Public: Render a spinner on the command line and yield to a block,
68
+ # reporting success if nothing is raised, otherwise reporting failure.
69
+ #
70
+ # message - Message to be displayed describing the task being evaluated.
71
+ # block - Block to be yielded to determine pass or fail.
72
+ #
73
+ # Returns the result of the yielded block if successful.
74
+ # Raises whatever is raised inside the yielded block.
75
+ def spinner(message, &block)
76
+ success = nil
77
+ result = nil
78
+
79
+ pre = "\r#{bold}#{white} [#{reset}"
80
+ post = "#{bold}#{white}] #{reset}#{message}"
81
+ pre_ok = "\r#{bold}#{white} [#{green} ok "
82
+ pre_fail = "\r#{bold}#{white} [#{red}fail"
83
+
84
+ thread = Thread.new do
85
+ step = 0
86
+ spin = [" ", ". ", ".. ", "... ", "....", " ...", " ..", " ."]
87
+ while success.nil?
88
+ print "#{pre}#{spin[step % 8]}#{post}"
89
+ step += 1
90
+ sleep 0.5
91
+ end
92
+
93
+ if success
94
+ print "#{pre_ok}#{post}\n"
95
+ else
96
+ print "#{pre_fail}#{post}\n"
97
+ end
98
+ end
99
+
100
+ begin
101
+ result = yield
102
+ success = true
103
+ thread.join
104
+ return result
105
+ rescue
106
+ success = false
107
+ thread.join
108
+ raise
109
+ end
110
+ end
111
+
112
+ # Public: Generate a reasonably formatted, printable table.
113
+ #
114
+ # options - An Array containing Hash objects which contain desired options:
115
+ # [{:col1 => val1, :col2 => val2}, {:col1 => val3, :col2 => val4}]
116
+ # labels - A Hash containing key-value pairs to label each key in options.
117
+ # (default: nil)
118
+ #
119
+ # Returns a String containing the grid.
120
+ # Raises ArgumentError if options is not an Array which contains at least one
121
+ # element.
122
+ def build_grid(options, labels = nil)
123
+ raise ArgumentError unless options.kind_of?(Array) and !options[0].nil?
124
+
125
+ if labels.nil?
126
+ options.unshift(options[0].keys.reduce({}) { |c,e| {e => e.to_s} })
127
+ else
128
+ options.unshift(labels)
129
+ end
130
+
131
+ keys = options[0].keys
132
+ max_lengths = keys.reduce({}) { |c,e| c.merge({e => 0}) }
133
+
134
+ options.each do |row|
135
+ row.each_key do |key|
136
+ if max_lengths[key] < row[key].to_s.length
137
+ max_lengths[key] = row[key].to_s.length
138
+ end
139
+ end
140
+ end
141
+
142
+ # Base width = 3n+1
143
+ grid_width = (max_lengths.length * 3) + 1
144
+
145
+ # Construct rule
146
+ grid_rule = "+"
147
+ options[0].each_key { |k| grid_rule << "-" * (max_lengths[k] + 2) + "+" }
148
+
149
+ # Construct grid
150
+ grid = ""
151
+ grid << grid_rule
152
+
153
+ options.each_with_index do |row, idx|
154
+ grid << "\n|"
155
+ keys.each do |key|
156
+ grid << sprintf(" % #{max_lengths[key]}s |", row[key])
157
+ end
158
+
159
+ # Visually separate the labels
160
+ grid << "\n" + grid_rule if idx == 0
161
+ end
162
+ grid << "\n" + grid_rule
163
+ end
164
+
165
+ # Minimal documentation provided for the following functions; terminal
166
+ # control sequences are well documented; here we are only providing
167
+ # shorthand.
168
+
169
+ # Public: Escape sequence
170
+ #
171
+ # Returns a String containing an escaped terminal control sequence.
172
+ def escape(seq); "\033[#{seq}m"; end
173
+
174
+ # Public: Reset terminal control
175
+ #
176
+ # Returns a String the 'reset' terminal control sequence.
177
+ def reset; escape("0"); end
178
+
179
+ # Public: Set text bold
180
+ #
181
+ # Returns a String the terminal control sequence to set text bold.
182
+ def bold; escape("1"); end
183
+
184
+ # Public: Set text underlined
185
+ #
186
+ # Returns a String the terminal control sequence to set text underlined.
187
+ def underline; escape("4"); end
188
+
189
+ # Public: Set text flashing
190
+ #
191
+ # Returns a String the terminal control sequence to set text flashing.
192
+ def annoy; escape("5"); end
193
+
194
+ # Public: Set text red
195
+ #
196
+ # Returns a String the terminal control sequence to set text red.
197
+ def red; escape("31"); end
198
+
199
+ # Public: Set text green
200
+ #
201
+ # Returns a String the terminal control sequence to set text green.
202
+ def green; escape("32"); end
203
+
204
+ # Public: Set text blue
205
+ #
206
+ # Returns a String the terminal control sequence to set text blue.
207
+ def blue; escape("34"); end
208
+
209
+ # Public: Set text white
210
+ #
211
+ # Returns a String the terminal control sequence to set text white.
212
+ def white; escape("37"); end
213
+ end
@@ -0,0 +1,20 @@
1
+ module CloudFlock::Interface::CLI::Opts extend self
2
+ # Internal: Extend the Opts module to provide options specific to the servers
3
+ # migration CLI utility.
4
+ #
5
+ # opts - OptionParser object to which to add options.
6
+ # options - Hash containing options flags and settings for the application.
7
+ #
8
+ # Returns nothing.
9
+ def argv_servers(opts, options)
10
+ opts.on('-o', '--opencloud', 'Perform an Open Cloud Servers migration') do
11
+ options[:function] = :opencloud
12
+ end
13
+ opts.on('-s', '--servers', 'Perform a Cloud Servers migration') do
14
+ options[:function] = :servers
15
+ end
16
+ opts.on('-r', '--resume', 'Resume a migration') do
17
+ options[:resume] = true
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,87 @@
1
+ require 'optparse'
2
+ Dir.glob(File.expand_path("../opts/*", __FILE__), &method(:require))
3
+
4
+ # Public: The CLI Opts module provides methods to abstract and simplify loading
5
+ # configuration information, parsing options and providing context to the
6
+ # application.
7
+ module CloudFlock::Interface::CLI::Opts extend self
8
+ CONFIG_LOCATION="~/.flockrc"
9
+ # Public: Open config files if applicable, overwriting default options with
10
+ # configuration passed files first, then with any options supplied via the
11
+ # command line.
12
+ #
13
+ # function - String or Symbol containing the name of the function to parse
14
+ # arguments for, if applicable. (default: '')
15
+ #
16
+ # Returns a Hash containing an option to value map.
17
+ def parse(function = '')
18
+ options = parse_config_file(CONFIG_LOCATION)
19
+
20
+ argv = parse_argv(function)
21
+ if argv[:config_file].kind_of?(String)
22
+ options.merge(parse_config_file(argv[:config_file]))
23
+ end
24
+
25
+ options.merge(argv)
26
+ end
27
+
28
+ # Internal: Open and parse a given config file.
29
+ #
30
+ # file - String containing path to a configuration file which will be parsed.
31
+ #
32
+ # Returns a Hash containing option-value mappings.
33
+ def parse_config_file(file)
34
+ options = {}
35
+ return options if file.nil?
36
+
37
+ config_string = ""
38
+ if File.exists?(File.expand_path(file))
39
+ config_string = File.open(File.expand_path(file)).read
40
+ end
41
+
42
+ config_string.each_line do |line|
43
+ line.gsub!(/#.*/, "").strip!
44
+ next if line.empty?
45
+
46
+ opt,value = line.split(/\s*/, 2)
47
+ options[opt.to_sym] = value
48
+ end
49
+
50
+ options
51
+ end
52
+
53
+ # Internal: Parse and return options passed via the command line.
54
+ #
55
+ # function - String or Symbol containing the name of the function to parse
56
+ # arguments for. This will cause the OptionParser to search for a
57
+ # argv_function name for the given name of the function.
58
+ #
59
+ # Returns a Hash containing an option to value map.
60
+ def parse_argv(function)
61
+ options = {}
62
+
63
+ optparse = OptionParser.new do |opts|
64
+ opts.on('-v', '--verbose', 'Be verbose') do
65
+ options[:verbose] = true
66
+ end
67
+
68
+ opts.on('-c', '--config FILE', 'Load configuration saved from previous' +
69
+ ' session (useful with -r)') do |file|
70
+ unless File.exists?(File.expand_path(file))
71
+ puts "Configuration file #{file} does not exist! Exiting."
72
+ exit
73
+ end
74
+ options[:config_file] = File.expand_path(file)
75
+ end
76
+
77
+ # Pull in extra options if applicable
78
+ function = ("argv_" + function.to_s).to_sym
79
+ if CloudFlock::Interface::CLI::Opts.respond_to?(function)
80
+ CloudFlock::Interface::CLI::Opts.send(function, opts, options)
81
+ end
82
+ end
83
+ optparse.parse!
84
+
85
+ options
86
+ end
87
+ end
@@ -0,0 +1,15 @@
1
+ require 'cloudflock'
2
+
3
+ module CloudFlock
4
+ module Interface
5
+ module CLI
6
+ module App
7
+ module Common; end
8
+ module Servers; end
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ require 'cloudflock/interface/cli/console'
15
+ require 'cloudflock/interface/cli/opts'
@@ -0,0 +1,113 @@
1
+ require 'fog'
2
+ module Fog
3
+ module Compute
4
+ class RackspaceV2
5
+ class Server
6
+ # Place existing server into rescue mode, allowing for offline editing of configuration. The original server's disk is attached to a new instance of the same base image for a period of time to facilitate working within rescue mode. The original server will be autom atically restored after 90 minutes.
7
+ # @return [Boolean] returns true if call to put server in rescue mode returns success
8
+ # @raise [Fog::Rackspace::Errors::NotFound] - HTTP 404
9
+ # @raise [Fog::Rackspace::Errors::BadRequest] - HTTP 400
10
+ # @raise [Fog::Rackspace::Errors::InternalServerError] - HTTP 500
11
+ # @raise [Fog::Rackspace::Errors::ServiceError]
12
+ # @note Rescue mode is only guaranteed to be active for 90 minutes.
13
+ # @see http://docs.rackspace.com/servers/api/v2/cs-devguide/content/rescue_mode.html
14
+ # @see #unrescue
15
+ #
16
+ # * Status Transition:
17
+ # * ACTIVE -> PREP_RESCUE -> RESCUE
18
+ def rescue
19
+ requires :identity
20
+ data = service.rescue_server(identity)
21
+ merge_attributes(data.body)
22
+ self.state = RESCUE
23
+ true
24
+ end
25
+
26
+ # Remove existing server from rescue mode.
27
+ # @return [Boolean] returns true if call to remove server from rescue mode returns success
28
+ # @raise [Fog::Rackspace::Errors::NotFound] - HTTP 404
29
+ # @raise [Fog::Rackspace::Errors::BadRequest] - HTTP 400
30
+ # @raise [Fog::Rackspace::Errors::InternalServerError] - HTTP 500
31
+ # @raise [Fog::Rackspace::Errors::ServiceError]
32
+ # @note Rescue mode is only guaranteed to be active for 90 minutes.
33
+ # @see http://docs.rackspace.com/servers/api/v2/cs-devguide/content/exit_rescue_mode.html
34
+ # @see #rescue
35
+ #
36
+ # * Status Transition:
37
+ # * RESCUE -> PREP_UNRESCUE -> ACTIVE
38
+ def unrescue
39
+ requires :identity
40
+ service.unrescue_server(identity)
41
+ self.state = ACTIVE
42
+ true
43
+ end
44
+ end
45
+
46
+ class Real
47
+ # Puts server into rescue mode
48
+ # @param [String] server_id id of server to rescue
49
+ # @return [Excon::Response] response
50
+ # @raise [Fog::Rackspace::Errors::NotFound] - HTTP 404
51
+ # @raise [Fog::Rackspace::Errors::BadRequest] - HTTP 400
52
+ # @raise [Fog::Rackspace::Errors::InternalServerError] - HTTP 500
53
+ # @raise [Fog::Rackspace::Errors::ServiceError]
54
+ # @note Rescue mode is only guaranteed to be active for 90 minutes.
55
+ # @see http://docs.rackspace.com/servers/api/v2/cs-devguide/content/rescue_mode.html
56
+ #
57
+ # * Status Transition:
58
+ # * ACTIVE -> PREP_RESCUE -> RESCUE
59
+ def rescue_server(server_id)
60
+ data = {
61
+ 'rescue' => nil
62
+ }
63
+
64
+ request(
65
+ :body => Fog::JSON.encode(data),
66
+ :expects => [200],
67
+ :method => 'POST',
68
+ :path => "servers/#{server_id}/action"
69
+ )
70
+ end
71
+
72
+ # Take server out of rescue mode
73
+ # @param [String] server_id id of server
74
+ # @return [Excon::Response] response
75
+ # @raise [Fog::Rackspace::Errors::NotFound] - HTTP 404
76
+ # @raise [Fog::Rackspace::Errors::BadRequest] - HTTP 400
77
+ # @raise [Fog::Rackspace::Errors::InternalServerError] - HTTP 500
78
+ # @raise [Fog::Rackspace::Errors::ServiceError]
79
+ # @see http://docs.rackspace.com/servers/api/v2/cs-devguide/content/exit_rescue_mode.html
80
+ #
81
+ # * Status Transition:
82
+ # * RESCUE -> PREP_UNRESCUE -> ACTIVE
83
+ def unrescue_server(server_id)
84
+ data = {
85
+ 'unrescue' => nil
86
+ }
87
+
88
+ request(
89
+ :body => Fog::JSON.encode(data),
90
+ :expects => [202],
91
+ :method => 'POST',
92
+ :path => "servers/#{server_id}/action"
93
+ )
94
+ end
95
+ end
96
+
97
+ class Mock
98
+ def rescue_server(server_id)
99
+ server = self.data[:servers][server_id]
100
+ server["status"] = "PREP_RESCUE"
101
+ response(:status => 200)
102
+ end
103
+
104
+ def unrescue_server(server_id)
105
+ server = self.data[:servers][server_id]
106
+ server["status"] = "PREP_UNRESCUE"
107
+ response(:status => 202)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+