forj 0.0.48 → 1.0.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,403 @@
1
+ # encoding: UTF-8
2
+
3
+ # (c) Copyright 2014 Hewlett-Packard Development Company, L.P.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ class SSLErrorMgt
18
+
19
+ def initialize(iMaxRetry = 5)
20
+ @iRetry = 0
21
+ @iMaxRetry = iMaxRetry
22
+ end
23
+
24
+ def ErrorDetected(message,backtrace, e)
25
+ if message.match('SSLv2/v3 read server hello A: unknown protocol')
26
+ if @iRetry <@iMaxRetry
27
+ sleep(2)
28
+ @iRetry+=1
29
+ print "%s/%s try... 'unknown protocol' SSL Error\r" % [@iRetry, @iMaxRetry] if $FORJ_LOGGER.level == 0
30
+ return false
31
+ else
32
+ Logging.error('Too many retry. %s' % message)
33
+ return true
34
+ end
35
+ elsif e.is_a?(Excon::Errors::InternalServerError)
36
+ if @iRetry <@iMaxRetry
37
+ sleep(2)
38
+ @iRetry+=1
39
+ print "%s/%s try... %s\n" % [@iRetry, @iMaxRetry, ANSI.red(e.class)] if $FORJ_LOGGER.level == 0
40
+ return false
41
+ else
42
+ Logging.error('Too many retry. %s' % message)
43
+ return true
44
+ end
45
+ else
46
+ Logging.error("Exception %s: %s\n%s" % [e.class, message,backtrace.join("\n")])
47
+ return true
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+
54
+ class CloudProcess < BaseProcess
55
+ def connect(sCloudObj, hParams)
56
+ oSSLError = SSLErrorMgt.new # Retry object
57
+ Logging.debug("%s:%s Connecting to '%s' - Project '%s'" % [self.class, sCloudObj, config.get(:provider), hParams[:tenant]])
58
+ begin
59
+ controler.connect(sCloudObj)
60
+ rescue => e
61
+ if not oSSLError.ErrorDetected(e.message,e.backtrace, e)
62
+ retry
63
+ end
64
+ Logging.error('%s:%s: Unable to connect.\n%s' % [self.class, sCloudObj, e.message ])
65
+ nil
66
+ end
67
+ end
68
+ end
69
+
70
+ # ---------------------------------------------------------------------------
71
+ # Keypair management
72
+ # ---------------------------------------------------------------------------
73
+ class CloudProcess
74
+ def forj_get_or_create_keypair(sCloudObj, hParams)
75
+ sKeypair_name = hParams[:keypair_name]
76
+ # setup has configured and copied the appropriate key to forj keypairs.
77
+
78
+ hKeys = keypair_detect(sKeypair_name, File.expand_path(hParams[:keypair_path]))
79
+ if hKeys[:private_key_exist? ]
80
+ hParams[:private_key_file] = File.join(hKeys[:keypair_path], hKeys[:private_key_name])
81
+ Logging.info("Openssh private key file '%s' exists." % hParams[:private_key_file])
82
+ end
83
+ if hKeys[:public_key_exist? ]
84
+ hParams[:public_key_file] = File.join(hKeys[:keypair_path], hKeys[:public_key_name])
85
+ else
86
+ Logging.fatal("Public key file is not found. Please run 'forj setup %s'" % config[:account_name])
87
+ end
88
+
89
+ Logging.state("Searching for keypair '%s'" % [sKeypair_name] )
90
+
91
+ keypairs = forj_query_keypair(sCloudObj, {:name => sKeypair_name}, hParams)
92
+ if keypairs.length > 0
93
+ keypair = keypairs[0]
94
+ # Check the public key with the one found here, locally.
95
+ if not keypair[:public_key].nil? and keypair[:public_key] != ""
96
+ begin
97
+ local_pub_key = File.read(hParams[:public_key_file])
98
+ rescue => e
99
+ Logging.error("Unable to read '%s'.\n%s",[hParams[:public_key_file], e.message] )
100
+ keypair[:coherent] = false
101
+ else
102
+ if local_pub_key.split(' ')[1].strip == keypair[:public_key].split(' ')[1].strip
103
+ Logging.info("keypair '%s' local files are coherent with keypair in your cloud service. You will be able to connect to your box over SSH." % sKeypair_name)
104
+ keypair[:coherent] = true
105
+ else
106
+ keypair[:coherent] = false
107
+ Logging.warning("Your local keypair file '%s' are incoherent with public key '%s' found in your cloud. You won't be able to access your box with this keypair.\nPublic key found in the cloud:\n%s" % [hParams[:public_key_file], sKeypair_name, keypair[:public_key]])
108
+ end
109
+ end
110
+ else
111
+ keypair[:coherent] = false
112
+ Logging.warning("Unable to verify keypair coherence between your cloud and your local SSH keys. The cloud controller did not provided ':public_key'")
113
+ end
114
+ else
115
+ keypair = create_keypair(sCloudObj,hParams)
116
+ if not hKeys[:private_key_exist? ]
117
+ keypair[:coherent] = false
118
+ else
119
+ keypair[:coherent] = true
120
+ end
121
+ end
122
+ # Adding information about key files.
123
+ keypair[:private_key_file] = hParams[:private_key_file]
124
+ keypair[:public_key_file] = hParams[:public_key_file]
125
+ keypair
126
+ end
127
+
128
+ def forj_query_keypair(sCloudObj, sQuery, hParams)
129
+ key_name = hParams[:keypair_name]
130
+ oSSLError = SSLErrorMgt.new
131
+ begin
132
+ oList = controler.query(sCloudObj, sQuery)
133
+ query_single(sCloudObj, oList, sQuery, key_name)
134
+ rescue => e
135
+ if not oSSLError.ErrorDetected(e.message, e.backtrace, e)
136
+ retry
137
+ end
138
+ end
139
+
140
+ end
141
+
142
+ def keypair_create(sCloudObj,hParams)
143
+ key_name = hParams[:keypair_name]
144
+ Logging.debug("Importing keypair '%s'" % [key_name])
145
+ oSSLError=SSLErrorMgt.new
146
+ begin
147
+ controler.create(sCloudObj)
148
+ rescue StandardError => e
149
+ if not oSSLError.ErrorDetected(e.message,e.backtrace, e)
150
+ retry
151
+ end
152
+ Logging.error "error importing keypair '%s'" % [key_name]
153
+ end
154
+ end
155
+
156
+ def keypair_detect(keypair_name, key_fullpath)
157
+ # Build key data information structure.
158
+ # Take care of priv with or without .pem and pubkey with pub.
159
+
160
+ key_basename = File.basename(key_fullpath)
161
+ key_path = File.expand_path(File.dirname(key_fullpath))
162
+
163
+ mObj = key_basename.match(/^(.*?)(\.pem|\.pub)?$/)
164
+ key_basename = mObj[1]
165
+
166
+ private_key_ext = nil
167
+ private_key_ext = "" if File.exists?(File.join(key_path, key_basename))
168
+ private_key_ext = '.pem' if File.exists?(File.join(key_path, key_basename + '.pem'))
169
+ if private_key_ext
170
+ private_key_exist = true
171
+ private_key_name = key_basename + private_key_ext
172
+ else
173
+ private_key_exist = false
174
+ private_key_name = key_basename
175
+ end
176
+
177
+ public_key_exist = File.exists?(File.join(key_path, key_basename + '.pub'))
178
+ public_key_name = key_basename + '.pub'
179
+
180
+
181
+ {:keypair_name => keypair_name,
182
+ :keypair_path => key_path, :key_basename => key_basename,
183
+ :private_key_name => private_key_name, :private_key_exist? => private_key_exist,
184
+ :public_key_name => public_key_name, :public_key_exist? => public_key_exist,
185
+ }
186
+ end
187
+
188
+ end
189
+
190
+ # ---------------------------------------------------------------------------
191
+ # flavor management
192
+ # ---------------------------------------------------------------------------
193
+ class CloudProcess
194
+ # Depending on clouds/rights, we can create flavor or not.
195
+ # Usually, flavor records already exists, and the controller may map them
196
+ # CloudProcess predefines some values. Consult CloudProcess.rb for details
197
+ def forj_get_or_create_flavor(sCloudObj, hParams)
198
+ sFlavor_name = hParams[:flavor_name]
199
+ Logging.state("Searching for flavor '%s'" % [sFlavor_name] )
200
+
201
+ flavors = query_flavor(sCloudObj, {:name => sFlavor_name}, hParams)
202
+ if flavors.length == 0
203
+ if not hParams[:create]
204
+ Logging.error("Unable to create %s '%s'. Creation is not supported." % [sCloudObj, sFlavor_name])
205
+ ForjLib::Data.new.set(nil, sCloudObj)
206
+ else
207
+ create_flavor(sCloudObj,hParams)
208
+ end
209
+ else
210
+ flavors[0]
211
+ end
212
+ end
213
+
214
+ # Should return 1 or 0 flavor.
215
+ def query_flavor(sCloudObj, sQuery, hParams)
216
+ sFlavor_name = hParams[:flavor_name]
217
+ oList = forj_query_flavor(sCloudObj, sQuery, hParams)
218
+ query_single(sCloudObj, oList, sQuery, sFlavor_name)
219
+ end
220
+
221
+ # Should return 1 or 0 flavor.
222
+ def forj_query_flavor(sCloudObj, sQuery, hParams)
223
+ sFlavor_name = hParams[:flavor_name]
224
+ oSSLError = SSLErrorMgt.new
225
+ begin
226
+ oList = controler.query(sCloudObj, sQuery)
227
+ rescue => e
228
+ if not oSSLError.ErrorDetected(e.message,e.backtrace, e)
229
+ retry
230
+ end
231
+ end
232
+ oList
233
+ end
234
+ end
235
+
236
+ # ---------------------------------------------------------------------------
237
+ # Image management
238
+ # ---------------------------------------------------------------------------
239
+ class CloudProcess < BaseProcess
240
+ def forj_get_or_create_image(sCloudObj, hParams)
241
+ sImage_name = hParams[:image_name]
242
+ Logging.state("Searching for image '%s'" % [sImage_name] )
243
+
244
+ search_the_image(sCloudObj, {:name => sImage_name}, hParams)
245
+ # No creation possible.
246
+ end
247
+
248
+ def search_the_image(sCloudObj, sQuery, hParams)
249
+ image_name = hParams[:image_name]
250
+ images = forj_query_image(sCloudObj, sQuery, hParams)
251
+ case images.length()
252
+ when 0
253
+ Logging.info("No image '%s' found" % [ image_name ] )
254
+ nil
255
+ else
256
+ Logging.info("Found image '%s'." % [ image_name ])
257
+ images[0]
258
+ end
259
+ end
260
+
261
+ def forj_query_image(sCloudObj, sQuery, hParams)
262
+ oSSLError = SSLErrorMgt.new
263
+ begin
264
+ controler.query(sCloudObj, sQuery)
265
+ rescue => e
266
+ if not oSSLError.ErrorDetected(e.message,e.backtrace, e)
267
+ retry
268
+ end
269
+ end
270
+ end
271
+ end
272
+
273
+ # ---------------------------------------------------------------------------
274
+ # Server management
275
+ # ---------------------------------------------------------------------------
276
+ class CloudProcess < BaseProcess
277
+ # Process Handler functions
278
+ def forj_get_or_create_server(sCloudObj, hParams)
279
+ sServer_name = hParams[:server_name]
280
+ Logging.state("Searching for server '%s'" % [sServer_name] )
281
+ servers = forj_query_server(sCloudObj, {:name => sServer_name}, hParams)
282
+ if servers.length > 0
283
+ # Get server details
284
+ forj_get_server(sCloudObj, servers[0][:attrs][:id], hParams)
285
+ else
286
+ create_server(sCloudObj, hParams)
287
+ end
288
+ end
289
+
290
+ def forj_query_server(sCloudObj, sQuery, hParams)
291
+ server_name = "Undefined"
292
+ server_name = sQuery[:name] if sQuery.key?(:name)
293
+ oSSLError = SSLErrorMgt.new
294
+ begin
295
+ oList = controler.query(sCloudObj, sQuery)
296
+ query_single(sCloudObj, oList, sQuery, server_name)
297
+ rescue => e
298
+ if not oSSLError.ErrorDetected(e.message,e.backtrace, e)
299
+ retry
300
+ end
301
+ end
302
+ end
303
+
304
+ def forj_get_server(sCloudObj, sId, hParams)
305
+ oSSLError = SSLErrorMgt.new
306
+ begin
307
+ controler.get(sCloudObj, sId)
308
+ rescue => e
309
+ if not oSSLError.ErrorDetected(e.message,e.backtrace, e)
310
+ retry
311
+ end
312
+ end
313
+ end
314
+
315
+ # Internal Process function
316
+ def create_server(sCloudObj, hParams)
317
+ name = hParams[:server_name]
318
+ begin
319
+ Logging.info("boot: meta-data provided.") if hParams[:meta_data]
320
+ Logging.info("boot: user-data provided.") if hParams[:user_data]
321
+ Logging.state('creating server %s' % [name])
322
+ server = controler.create(sCloudObj)
323
+ Logging.info("%s '%s' created." % [sCloudObj, name])
324
+ rescue => e
325
+ Logging.fatal(1, "Unable to create server '%s'" % name, e)
326
+ end
327
+ server
328
+ end
329
+
330
+ def forj_get_server_log(sCloudObj, sId, hParams)
331
+ oSSLError = SSLErrorMgt.new
332
+ begin
333
+ controler.get(sCloudObj, sId)
334
+ rescue => e
335
+ if not oSSLError.ErrorDetected(e.message,e.backtrace, e)
336
+ retry
337
+ end
338
+ end
339
+ end
340
+ end
341
+ # ---------------------------------------------------------------------------
342
+ # Addresses management
343
+ # ---------------------------------------------------------------------------
344
+ class CloudProcess < BaseProcess
345
+ # Process Handler functions
346
+ def forj_get_or_assign_public_address(sCloudObj, hParams)
347
+ # Function which to assign a public IP address to a server.
348
+ sServer_name = hParams[:server, :name]
349
+
350
+ Logging.state("Searching public IP for server '%s'" % [sServer_name] )
351
+ addresses = controler.query(sCloudObj, {:server_id => hParams[:server, :id]})
352
+ if addresses.length == 0
353
+ assign_address(sCloudObj, hParams)
354
+ else
355
+ addresses[0]
356
+ end
357
+ end
358
+
359
+ def forj_query_public_address(sCloudObj, sQuery, hParams)
360
+ server_name = hParams[:server, :name]
361
+ oSSLError = SSLErrorMgt.new
362
+ begin
363
+ sInfo = {
364
+ :notfound => "No %s for '%s' found",
365
+ :checkmatch => "Found 1 %s. checking exact match for server '%s'.",
366
+ :nomatch => "No %s for '%s' match",
367
+ :found => "Found %s '%s' for #{server_name}.",
368
+ :more => "Found several %s. Searching for '%s'.",
369
+ :items => :public_ip
370
+ }
371
+ oList = controler.query(sCloudObj, sQuery)
372
+ query_single(sCloudObj, oList, sQuery, server_name, sInfo)
373
+ rescue => e
374
+ if not oSSLError.ErrorDetected(e.message,e.backtrace, e)
375
+ retry
376
+ end
377
+ end
378
+ end
379
+
380
+ def forj_get_public_address(sCloudObj, sId, hParams)
381
+ oSSLError = SSLErrorMgt.new
382
+ begin
383
+ controler.get(sCloudObj, sId)
384
+ rescue => e
385
+ if not oSSLError.ErrorDetected(e.message,e.backtrace, e)
386
+ retry
387
+ end
388
+ end
389
+ end
390
+
391
+ # Internal Process function
392
+ def assign_address(sCloudObj, hParams)
393
+ name = hParams[:server, :name]
394
+ begin
395
+ Logging.state('Getting public IP for server %s' % [name])
396
+ ip_address = controler.create(sCloudObj)
397
+ Logging.info("Public IP '%s' for server '%s' assigned." % [ip_address[:public_ip], name])
398
+ rescue => e
399
+ Logging.fatal(1, "Unable to assign a public IP to server '%s'" % name, e)
400
+ end
401
+ ip_address
402
+ end
403
+ end