cloudflock 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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
+