cloudflock 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/flock +32 -0
- data/bin/flock-profile +17 -0
- data/bin/flock-servers +24 -0
- data/bin/flock.default +24 -0
- data/lib/cloudflock/error.rb +26 -0
- data/lib/cloudflock/interface/cli/app/common/servers.rb +128 -0
- data/lib/cloudflock/interface/cli/app/servers/migrate.rb +357 -0
- data/lib/cloudflock/interface/cli/app/servers/profile.rb +88 -0
- data/lib/cloudflock/interface/cli/app/servers.rb +2 -0
- data/lib/cloudflock/interface/cli/console.rb +213 -0
- data/lib/cloudflock/interface/cli/opts/servers.rb +20 -0
- data/lib/cloudflock/interface/cli/opts.rb +87 -0
- data/lib/cloudflock/interface/cli.rb +15 -0
- data/lib/cloudflock/patch/fog.rb +113 -0
- data/lib/cloudflock/remote/ssh.rb +311 -0
- data/lib/cloudflock/target/servers/data/exceptions/base.txt +44 -0
- data/lib/cloudflock/target/servers/data/exceptions/platform/amazon.txt +10 -0
- data/lib/cloudflock/target/servers/data/exceptions/platform/debian.txt +0 -0
- data/lib/cloudflock/target/servers/data/exceptions/platform/redhat.txt +5 -0
- data/lib/cloudflock/target/servers/data/exceptions/platform/suse.txt +1 -0
- data/lib/cloudflock/target/servers/data/post-migration/chroot/base.txt +1 -0
- data/lib/cloudflock/target/servers/data/post-migration/chroot/platform/amazon.txt +19 -0
- data/lib/cloudflock/target/servers/data/post-migration/pre/base.txt +3 -0
- data/lib/cloudflock/target/servers/data/post-migration/pre/platform/amazon.txt +4 -0
- data/lib/cloudflock/target/servers/migrate.rb +466 -0
- data/lib/cloudflock/target/servers/platform/v1.rb +97 -0
- data/lib/cloudflock/target/servers/platform/v2.rb +93 -0
- data/lib/cloudflock/target/servers/platform.rb +133 -0
- data/lib/cloudflock/target/servers/profile.rb +394 -0
- data/lib/cloudflock/target/servers.rb +5 -0
- data/lib/cloudflock/version.rb +3 -0
- data/lib/cloudflock.rb +10 -0
- metadata +128 -0
data/bin/flock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
current_path = File.expand_path('../', __FILE__)
|
4
|
+
function = ARGV.shift.to_s
|
5
|
+
bin = File.basename($0, '.dev')
|
6
|
+
|
7
|
+
if function.match(/^[^-]/)
|
8
|
+
target = "#{current_path}/#{bin}-#{function}"
|
9
|
+
else
|
10
|
+
ARGV.unshift(function)
|
11
|
+
target = "#{current_path}/#{bin}.default"
|
12
|
+
end
|
13
|
+
|
14
|
+
if File.exists?(target)
|
15
|
+
load target
|
16
|
+
else
|
17
|
+
print "Function \"#{function}\" not found. " unless target =~ /\.default$/
|
18
|
+
puts "Available functions:"
|
19
|
+
|
20
|
+
functions = []
|
21
|
+
Dir.foreach(current_path) do |file|
|
22
|
+
next unless file.match(/^#{bin}-[^.]*$/)
|
23
|
+
functions << file.gsub(/^#{bin}-/, '')
|
24
|
+
end
|
25
|
+
|
26
|
+
until functions.empty?
|
27
|
+
a, b, c = functions.shift(3)
|
28
|
+
puts "%-15s %-15s %-15s" % [a, b, c].map { |i| i.to_s }
|
29
|
+
end
|
30
|
+
|
31
|
+
exit 1
|
32
|
+
end
|
data/bin/flock-profile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'cloudflock/interface/cli/app/servers/profile'
|
4
|
+
CLI = CloudFlock::Interface::CLI::Console
|
5
|
+
Opts = CloudFlock::Interface::CLI::Opts
|
6
|
+
|
7
|
+
# Trap C-c to kill the application in a friendly manner
|
8
|
+
trap 'INT' do
|
9
|
+
puts "\nCaught SIGINT, exiting..."
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
options = Opts.parse
|
14
|
+
options[:function] = :profile
|
15
|
+
options[:config] = Opts.parse_config_file(options[:config_file])
|
16
|
+
|
17
|
+
CloudFlock::Interface::CLI::App::Servers::Profile.new(options)
|
data/bin/flock-servers
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'cloudflock/interface/cli/app/servers/migrate'
|
4
|
+
CLI = CloudFlock::Interface::CLI::Console
|
5
|
+
Opts = CloudFlock::Interface::CLI::Opts
|
6
|
+
|
7
|
+
# Trap C-c to kill the application in a friendly manner
|
8
|
+
trap 'INT' do
|
9
|
+
puts "\nCaught SIGINT, exiting..."
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
options = Opts.parse(:servers)
|
14
|
+
type_prompt = "Type of migration (servers, opencloud)?"
|
15
|
+
|
16
|
+
if options[:function].nil?
|
17
|
+
options[:function] = CLI.prompt(type_prompt, valid_answers:
|
18
|
+
["servers", "opencloud"],
|
19
|
+
default_answer: "opencloud")
|
20
|
+
end
|
21
|
+
options[:function] = options[:function].to_sym
|
22
|
+
options[:config] = Opts.parse_config_file(options[:config_file])
|
23
|
+
|
24
|
+
CloudFlock::Interface::CLI::App::Servers::Migrate.new(options)
|
data/bin/flock.default
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'cloudflock/interface/cli/app/servers/migrate'
|
4
|
+
CLI = CloudFlock::Interface::CLI::Console
|
5
|
+
Opts = CloudFlock::Interface::CLI::Opts
|
6
|
+
|
7
|
+
# Trap C-c to kill the application in a friendly manner
|
8
|
+
trap 'INT' do
|
9
|
+
puts "\nCaught SIGINT, exiting..."
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
options = Opts.parse(:servers)
|
14
|
+
type_prompt = "Type of migration (servers, opencloud)?"
|
15
|
+
|
16
|
+
if options[:function].nil?
|
17
|
+
options[:function] = CLI.prompt(type_prompt, valid_answers:
|
18
|
+
["servers", "opencloud"],
|
19
|
+
default_answer: "opencloud")
|
20
|
+
end
|
21
|
+
options[:function] = options[:function].to_sym
|
22
|
+
options[:config] = Opts.parse_config_file(options[:config_file])
|
23
|
+
|
24
|
+
CloudFlock::Interface::CLI::App::Servers::Migrate.new(options)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module CloudFlock
|
2
|
+
module Remote
|
3
|
+
class SSH
|
4
|
+
class InvalidHostname < StandardError; end
|
5
|
+
class ConnectionFailed < StandardError; end
|
6
|
+
class LoginFailed < StandardError; end
|
7
|
+
class RootFailed < StandardError; end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Target
|
12
|
+
module Servers
|
13
|
+
module Migrate
|
14
|
+
class LongRunFailed < StandardError; end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Platform
|
18
|
+
class ValueError < StandardError; end
|
19
|
+
class MapUndefined < NameError; end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Profile
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'cloudflock'
|
2
|
+
require 'cloudflock/interface/cli'
|
3
|
+
require 'cloudflock/remote/ssh'
|
4
|
+
|
5
|
+
# Public: The ServersCommon module provides common methods for CLI interaction
|
6
|
+
# pertaining to interaction with remote (Unix) servers.
|
7
|
+
module CloudFlock::Interface::CLI::App::Common::Servers
|
8
|
+
include CloudFlock::Target::Servers
|
9
|
+
|
10
|
+
SSH = CloudFlock::Remote::SSH
|
11
|
+
CLI = CloudFlock::Interface::CLI::Console
|
12
|
+
|
13
|
+
# Internal: Collect information about the source server to be migrated.
|
14
|
+
#
|
15
|
+
# opts - Hash containing any applicable options mappings for the server in
|
16
|
+
# question.
|
17
|
+
#
|
18
|
+
# Returns a Hash containing information pertinent to logging in to a host.
|
19
|
+
def define_source(opts)
|
20
|
+
host = {}
|
21
|
+
|
22
|
+
host[:host] = opts[:source_host] || CLI.prompt("Source host")
|
23
|
+
host[:port] = opts[:source_port] || CLI.prompt("Source SSH port",
|
24
|
+
default_answer: "22")
|
25
|
+
host[:username] = opts[:source_user] || CLI.prompt("Source username",
|
26
|
+
default_answer: "root")
|
27
|
+
host[:password] = opts[:source_pass] || CLI.prompt("Source password",
|
28
|
+
default_answer: "")
|
29
|
+
|
30
|
+
until host[:public_key].kind_of?(String)
|
31
|
+
key = opts[:public_key] || CLI.prompt("SSH Key", default_answer: "")
|
32
|
+
if File.file?(File.expand_path(key)) || key.empty?
|
33
|
+
host[:public_key] = key
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Only need to use sudo if the user isn't root
|
38
|
+
if host[:username] == "root"
|
39
|
+
host[:sudo] = false
|
40
|
+
elsif !opts[:source_sudo].nil?
|
41
|
+
host[:sudo] = opts[:source_sudo]
|
42
|
+
else
|
43
|
+
host[:sudo] = CLI.prompt_yn("Use sudo? (Y/N)", default_answer: "Y")
|
44
|
+
end
|
45
|
+
|
46
|
+
# We need the root pass if non-root and no sudo
|
47
|
+
if host[:username] == "root" || host[:sudo]
|
48
|
+
host[:root_pass] = host[:password]
|
49
|
+
else
|
50
|
+
host[:root_pass] = CLI.prompt("Password for root")
|
51
|
+
end
|
52
|
+
|
53
|
+
host
|
54
|
+
end
|
55
|
+
|
56
|
+
# Internal: Collect information about the destination server to target in a
|
57
|
+
# migration -- only used for resume functions.
|
58
|
+
#
|
59
|
+
# Returns a Hash containing information pertinent to logging in to a host.
|
60
|
+
def define_destination
|
61
|
+
host = Hash.new
|
62
|
+
|
63
|
+
host[:host] = CLI.prompt("Destination host")
|
64
|
+
host[:password] = CLI.prompt("Destination root password")
|
65
|
+
host[:pre_steps] = CLI.prompt_yn("Perform pre-migration steps? (Y/N)")
|
66
|
+
host[:username] = "root"
|
67
|
+
|
68
|
+
host
|
69
|
+
end
|
70
|
+
|
71
|
+
# Internal: Initiate a connection to a given host and obtain root privileges.
|
72
|
+
#
|
73
|
+
# host - Hash containing information for connecting to the host:
|
74
|
+
# :host - String containing the location to which to connect.
|
75
|
+
# :port - String or Fixnum containing the port on which ssh
|
76
|
+
# listens. (default: "22")
|
77
|
+
# :username - String containing the username to use when logging in.
|
78
|
+
# :password - String containing the password for the above user.
|
79
|
+
# :sudo - Boolean value defining whether to use sudo to obtain
|
80
|
+
# root. (default: false)
|
81
|
+
# :root_pass - String containing the password to use to obtain root,
|
82
|
+
# if the user isn't root and sudo isn't used.
|
83
|
+
# :verbose - Boolean value defining whether to flush output to
|
84
|
+
# STDOUT. (default: false)
|
85
|
+
#
|
86
|
+
# Returns an SSH object.
|
87
|
+
# Raises ArgumentError unless at least host and user are defined.
|
88
|
+
def host_login(host)
|
89
|
+
if host[:host].nil? || host[:username].nil?
|
90
|
+
raise ArgumentError, "Need at least host and username defined"
|
91
|
+
end
|
92
|
+
|
93
|
+
host[:flush_buffer] = host[:verbose] || false
|
94
|
+
|
95
|
+
ssh = SSH.new(host)
|
96
|
+
ssh.get_root(host[:root_pass], host[:sudo])
|
97
|
+
|
98
|
+
ssh
|
99
|
+
end
|
100
|
+
|
101
|
+
# Internal: Initiate a connection to a destination host.
|
102
|
+
#
|
103
|
+
# host - Hash containing information for connecting to the host:
|
104
|
+
# :host - String containing the location to which to connect.
|
105
|
+
# :password - String containing the password for the above user.
|
106
|
+
# :verbose - Boolean value defining whether to flush output to
|
107
|
+
# STDOUT. (default: false)
|
108
|
+
#
|
109
|
+
# Returns an SSH object.
|
110
|
+
# Raises ArgumentError unless at least host and user are defined.
|
111
|
+
def destination_login(host)
|
112
|
+
host[:username] = "root"
|
113
|
+
message = "Connecting to destination host (password: #{host[:password]})"
|
114
|
+
r = 0
|
115
|
+
|
116
|
+
destination_host = CLI.spinner(message) do
|
117
|
+
begin
|
118
|
+
host_login(host)
|
119
|
+
rescue Timeout::Error
|
120
|
+
if (r += 1) < 5
|
121
|
+
sleep 300
|
122
|
+
retry
|
123
|
+
end
|
124
|
+
raise
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,357 @@
|
|
1
|
+
require 'fog'
|
2
|
+
require 'cloudflock/target/servers'
|
3
|
+
require 'cloudflock/interface/cli/app/common/servers'
|
4
|
+
|
5
|
+
# Public: The Servers::Migrate app provides the interface to Servers migrations
|
6
|
+
# (primarily targeting Managed/Unmanaged Rackspace First-gen and Open Cloud,
|
7
|
+
# but other migrations are possible) on the CLI.
|
8
|
+
class CloudFlock::Interface::CLI::App::Servers::Migrate
|
9
|
+
include CloudFlock::Interface::CLI::App::Common::Servers
|
10
|
+
CLI = CloudFlock::Interface::CLI::Console
|
11
|
+
|
12
|
+
# Public: Begin Servers migration on the command line
|
13
|
+
#
|
14
|
+
# opts - Hash containing options mappings.
|
15
|
+
def initialize(opts)
|
16
|
+
opencloud = (opts[:function] == :opencloud)
|
17
|
+
|
18
|
+
resume = opts[:resume]
|
19
|
+
source_host_def = define_source(opts[:config])
|
20
|
+
source_host_ssh = CLI.spinner("Logging in to #{source_host_def[:host]}") do
|
21
|
+
host_login(source_host_def)
|
22
|
+
end
|
23
|
+
|
24
|
+
source_profile = CLI.spinner("Checking source host") do
|
25
|
+
profile = Profile.new(source_host_ssh)
|
26
|
+
profile.build
|
27
|
+
profile
|
28
|
+
end
|
29
|
+
|
30
|
+
if opencloud
|
31
|
+
target_platform = Platform::V2
|
32
|
+
else
|
33
|
+
target_platform = Platform::V1
|
34
|
+
end
|
35
|
+
platform = target_platform.new(source_profile[:cpe])
|
36
|
+
build_target = platform.build_recommendation(source_profile)
|
37
|
+
flavor_list = target_platform::FLAVOR_LIST
|
38
|
+
default_target = flavor_list[build_target[:flavor]]
|
39
|
+
|
40
|
+
# Generate and display a brief summary of the server Platform/Profile
|
41
|
+
os_tag = source_profile[:cpe].vendor == "Unknown" ? CLI.red : CLI.blue
|
42
|
+
ram_qty = default_target[:mem]
|
43
|
+
hdd_qty = default_target[:hdd]
|
44
|
+
decision_reason = "#{CLI.bold}#{build_target[:flavor_reason]}#{CLI.reset}"
|
45
|
+
|
46
|
+
puts "OS: #{CLI.bold}#{os_tag}#{platform}#{CLI.reset}"
|
47
|
+
puts "---"
|
48
|
+
puts "Recommended server:"
|
49
|
+
puts "RAM: #{CLI.bold} % 6d MiB#{CLI.reset}" % ram_qty
|
50
|
+
puts "HDD: #{CLI.bold} % 7d GB#{CLI.reset}" % hdd_qty
|
51
|
+
puts "The reason for this decision is: #{decision_reason}"
|
52
|
+
puts "---"
|
53
|
+
unless source_profile.warnings.empty?
|
54
|
+
print CLI.red + CLI.bold
|
55
|
+
source_profile.warnings.each { |warning| puts warning }
|
56
|
+
print CLI.reset
|
57
|
+
puts "---"
|
58
|
+
end
|
59
|
+
|
60
|
+
if resume
|
61
|
+
destination_host_def = define_destination
|
62
|
+
migration_exclusions = determine_exclusions(source_profile[:cpe])
|
63
|
+
platform.managed = CLI.prompt_yn("Managed service level? (Y/N)",
|
64
|
+
default_answer: "Y")
|
65
|
+
platform.rack_connect = CLI.prompt_yn("Rack Connected? (Y/N)",
|
66
|
+
default_answer: "N")
|
67
|
+
else
|
68
|
+
api = {}
|
69
|
+
api[:version] = opencloud ? :v2 : :v1
|
70
|
+
|
71
|
+
proceed = CLI.prompt_yn("Spin up this server? (Y/N)", default_answer: "Y")
|
72
|
+
if proceed
|
73
|
+
api[:flavor] = default_target[:id]
|
74
|
+
else
|
75
|
+
puts CLI.build_grid(flavor_list,
|
76
|
+
{id: "ID", mem: "RAM (MiB)", hdd: "HDD (GB)" })
|
77
|
+
api[:flavor] = CLI.prompt("Flavor ID",
|
78
|
+
default_answer: default_target[:id])
|
79
|
+
api[:flavor] = api[:flavor].to_i
|
80
|
+
end
|
81
|
+
|
82
|
+
migration_exclusions = determine_exclusions(source_profile[:cpe])
|
83
|
+
platform.managed = CLI.prompt_yn("Managed service level? (Y/N)",
|
84
|
+
default_answer: "Y")
|
85
|
+
platform.rack_connect = CLI.prompt_yn("Rack Connected? (Y/N)",
|
86
|
+
default_answer: "N")
|
87
|
+
|
88
|
+
# Warn against Rack Connect
|
89
|
+
if platform.rack_connect
|
90
|
+
puts "#{CLI.bold}#{CLI.red}*** Rack Connect servers might not " +
|
91
|
+
"provision properly when spun up from the API! Resume " +
|
92
|
+
"recommended!#{CLI.reset}"
|
93
|
+
sleep 5
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
# Check to make sure we have a valid flavor ID
|
98
|
+
exit 0 if api[:flavor] == 0 or flavor_list[api[:flavor]-1].nil?
|
99
|
+
|
100
|
+
# Build our API call
|
101
|
+
api[:hostname] = CLI.prompt("New Server Name",
|
102
|
+
default_answer: source_profile[:hostname])
|
103
|
+
|
104
|
+
# OpenCloud only supports US migrations presently
|
105
|
+
if opts[:function] == :opencloud
|
106
|
+
api[:region] = CLI.prompt("Region (dfw, ord)", default_answer: "dfw",
|
107
|
+
valid_answers: ["ord", "dfw"])
|
108
|
+
else
|
109
|
+
api[:region] = :dfw
|
110
|
+
end
|
111
|
+
|
112
|
+
api[:username] = CLI.prompt("RS Cloud Username")
|
113
|
+
api[:api_key] = CLI.prompt("RS Cloud API key")
|
114
|
+
|
115
|
+
# Name cannot have any special characters in it
|
116
|
+
api[:hostname].gsub!(/[^A-Za-z0-9.]/, '-')
|
117
|
+
|
118
|
+
rack_api = Fog::Compute.new(provider: 'rackspace',
|
119
|
+
rackspace_username: api[:username],
|
120
|
+
rackspace_api_key: api[:api_key],
|
121
|
+
rackspace_region: api[:region],
|
122
|
+
version: api[:version])
|
123
|
+
|
124
|
+
# Rescue patch has to be loaded after the connection is created
|
125
|
+
require 'cloudflock/patch/fog'
|
126
|
+
|
127
|
+
# Send API call
|
128
|
+
new_server = CLI.spinner("Spinning up new server: #{api[:hostname]}") do
|
129
|
+
rack_api.servers::create(name: api[:hostname],
|
130
|
+
image_id: platform.image,
|
131
|
+
flavor_id: api[:flavor])
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
# Set the destination host address
|
136
|
+
destination_host_def = {}
|
137
|
+
CLI.spinner("Obtaining information for new instance") do
|
138
|
+
# Obtain the administrative pass for the new host.
|
139
|
+
destination_host_def[:password] = new_server.password
|
140
|
+
server_id = new_server.id
|
141
|
+
|
142
|
+
until new_server.state == 'ACTIVE'
|
143
|
+
sleep 30
|
144
|
+
begin
|
145
|
+
new_server.update
|
146
|
+
rescue NoMethodError
|
147
|
+
new_server.reload
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
if opencloud
|
152
|
+
dest_host = new_server.addresses["public"].select do |e|
|
153
|
+
e["version"] == 4
|
154
|
+
end
|
155
|
+
destination_host_def[:host] = dest_host[0]["addr"]
|
156
|
+
else
|
157
|
+
destination_host_def[:host] = new_server.addresses["public"][0]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# If we're working within the Managed service level, ensure that Chef
|
162
|
+
# has finished successfully
|
163
|
+
if platform.managed
|
164
|
+
r = 0
|
165
|
+
destination_host_ssh = destination_login(destination_host_def)
|
166
|
+
|
167
|
+
begin
|
168
|
+
message =
|
169
|
+
finished = CLI.spinner("Waiting for Chef to finish") do
|
170
|
+
# Sleep 180 seconds before trying
|
171
|
+
sleep 180
|
172
|
+
Migrate.setup_managed(destination_host_ssh)
|
173
|
+
end
|
174
|
+
unless finished
|
175
|
+
panic = "#{CLI.bold}#{CLI.red}*** MGC Cloud Scripts appear to " +
|
176
|
+
"have failed to run in a reasonable amount of time." +
|
177
|
+
"#{CLI.reset}"
|
178
|
+
puts panic
|
179
|
+
exit unless CLI.prompt_yn("Continue? (Y/N)", default_answer: "Y")
|
180
|
+
end
|
181
|
+
destination_host_ssh.logout!
|
182
|
+
rescue
|
183
|
+
panic = "#{CLI.bold}#{CLI.red}*** Unable to communicate with the " +
|
184
|
+
"destination host. Bailing out.#{CLI.reset}"
|
185
|
+
puts panic
|
186
|
+
raise
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
if opts[:function] == :opencloud
|
191
|
+
host = destination_host_def[:host]
|
192
|
+
CLI.spinner("Putting #{host} into rescue mode") do
|
193
|
+
new_server.rescue
|
194
|
+
destination_host_def[:password] = new_server.password
|
195
|
+
new_server.update
|
196
|
+
end
|
197
|
+
else
|
198
|
+
pass_prompt = "Please put #{api[:hostname]} into rescue mode and " +
|
199
|
+
"give password"
|
200
|
+
destination_host_def[:password] = CLI.prompt(pass_prompt)
|
201
|
+
end
|
202
|
+
|
203
|
+
CLI.spinner "Letting rescue mode come up..." do
|
204
|
+
until new_server.state == 'RESCUE'
|
205
|
+
sleep 30
|
206
|
+
begin
|
207
|
+
new_server.update
|
208
|
+
rescue NoMethodError
|
209
|
+
sleep 60
|
210
|
+
new_server.reload
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
Thread.new do
|
216
|
+
continue = false
|
217
|
+
until continue
|
218
|
+
r = 0
|
219
|
+
message = "Checking for SSH on #{destination_host_def[:host]}"
|
220
|
+
ssh_command = "ssh #{SSH::SSH_ARGUMENTS} " +
|
221
|
+
"root@#{destination_host_def[:host]}"
|
222
|
+
continue = CLI.spinner(message) do
|
223
|
+
begin
|
224
|
+
sleep 20
|
225
|
+
ssh_expect = Expectr.new(ssh_command, flush_buffer: false)
|
226
|
+
ssh_expect.expect("password")
|
227
|
+
rescue
|
228
|
+
retry if (r+=1) < 10
|
229
|
+
raise
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end.join
|
234
|
+
end
|
235
|
+
|
236
|
+
destination_host_ssh = destination_login(destination_host_def)
|
237
|
+
|
238
|
+
unless destination_host_def[:pre_steps] == false
|
239
|
+
# Attempt to set up the source host 5 times. If there is a failure,
|
240
|
+
# sleep for 60 seconds before retrying.
|
241
|
+
r = 0
|
242
|
+
begin
|
243
|
+
message = "Setting up source host (attempt #{r + 1}/5)"
|
244
|
+
pubkey = CLI.spinner(message) do
|
245
|
+
begin
|
246
|
+
message.gsub!(/\d\/5/, "#{r + 1}/5")
|
247
|
+
sleep 60 unless r == 0
|
248
|
+
Migrate.setup_source(source_host_ssh, migration_exclusions)
|
249
|
+
rescue
|
250
|
+
retry if (r += 1) < 5
|
251
|
+
raise
|
252
|
+
end
|
253
|
+
end
|
254
|
+
rescue
|
255
|
+
panic = "#{CLI.bold}#{CLI.red}*** Unable to communicate with the " +
|
256
|
+
"source host. Bailing out.#{CLI.reset}"
|
257
|
+
puts panic
|
258
|
+
raise
|
259
|
+
end
|
260
|
+
|
261
|
+
# Attempt to set up the destination host 5 times. If there is a
|
262
|
+
# failure, sleep for 60 seconds before retrying.
|
263
|
+
r = 0
|
264
|
+
begin
|
265
|
+
message = "Setting up destination host (attempt #{r + 1}/5)"
|
266
|
+
CLI.spinner(message) do
|
267
|
+
begin
|
268
|
+
message.gsub!(/\d\/5/, "#{r + 1}/5")
|
269
|
+
sleep 60 unless r == 0
|
270
|
+
Migrate.setup_destination(destination_host_ssh, pubkey)
|
271
|
+
rescue
|
272
|
+
retry if (r += 1) < 5
|
273
|
+
raise
|
274
|
+
end
|
275
|
+
end
|
276
|
+
rescue
|
277
|
+
panic = "#{CLI.bold}#{CLI.red}*** Unable to communicate with the " +
|
278
|
+
"destination host. Bailing out.#{CLI.reset}"
|
279
|
+
puts panic
|
280
|
+
raise
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# Determine if Service Net can be used
|
285
|
+
begin
|
286
|
+
CLI.spinner "Checking for ServiceNet" do
|
287
|
+
target_addr = Migrate.check_servicenet(source_host_ssh,
|
288
|
+
destination_host_ssh)
|
289
|
+
raise if target_addr.nil?
|
290
|
+
destination_host_def[:target_addr] = target_addr
|
291
|
+
end
|
292
|
+
rescue
|
293
|
+
destination_host_def[:target_addr] = destination_host_def[:host]
|
294
|
+
end
|
295
|
+
|
296
|
+
# Define no timeout for the migration rsync
|
297
|
+
destination_host_def[:timeout] = -1
|
298
|
+
|
299
|
+
# Kick off the migration proper
|
300
|
+
if opts[:verbose]
|
301
|
+
Migrate.migrate_server(source_host_ssh, destination_host_def)
|
302
|
+
else
|
303
|
+
CLI.spinner "Performing migration" do
|
304
|
+
Migrate.migrate_server(source_host_ssh, destination_host_def)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
CLI.spinner "Cleaning up destination host" do
|
309
|
+
Migrate.clean_destination(destination_host_ssh, source_profile[:cpe])
|
310
|
+
end
|
311
|
+
|
312
|
+
[destination_host_ssh, source_host_ssh].each do |host|
|
313
|
+
host.logout!
|
314
|
+
end
|
315
|
+
|
316
|
+
puts
|
317
|
+
puts "#{CLI.bold}#{CLI.blue}*** Migration complete#{CLI.reset}\a"
|
318
|
+
end
|
319
|
+
|
320
|
+
# Internal: Ask whether or not to edit the default exclusion list for a given
|
321
|
+
# platform, and facilitate the edit if so.
|
322
|
+
#
|
323
|
+
# cpe - CPE object for the host in question.
|
324
|
+
#
|
325
|
+
# Returns a String containing the exclusions.
|
326
|
+
# Raises ArgumentError if cpe isn't a CPE object.
|
327
|
+
def determine_exclusions(cpe)
|
328
|
+
raise ArgumentError unless cpe.kind_of?(CPE)
|
329
|
+
|
330
|
+
exclusion_string = Migrate.build_default_exclusions(cpe)
|
331
|
+
alter = CLI.prompt_yn("Edit default exclusions list? (Y/N)",
|
332
|
+
default_answer: "N")
|
333
|
+
|
334
|
+
if alter
|
335
|
+
require 'tempfile'
|
336
|
+
|
337
|
+
tmp_file = Tempfile.new("exclude")
|
338
|
+
tmp_file.write(exclusion_string)
|
339
|
+
tmp_file.close
|
340
|
+
|
341
|
+
# Allow for "other" editors
|
342
|
+
if File.exists?("/usr/bin/editor")
|
343
|
+
editor = "/usr/bin/editor"
|
344
|
+
else
|
345
|
+
editor = "vim"
|
346
|
+
end
|
347
|
+
|
348
|
+
system("#{editor} #{tmp_file.path}")
|
349
|
+
tmp_file.open
|
350
|
+
exclusion_string = tmp_file.read
|
351
|
+
tmp_file.close
|
352
|
+
tmp_file.unlink
|
353
|
+
end
|
354
|
+
|
355
|
+
exclusion_string
|
356
|
+
end
|
357
|
+
end
|