ncc-api 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/ncc-api +62 -0
- data/lib/ncc/config.rb +207 -0
- data/lib/ncc/connection/aws.rb +138 -0
- data/lib/ncc/connection/openstack.rb +171 -0
- data/lib/ncc/connection.rb +590 -0
- data/lib/ncc/error.rb +41 -0
- data/lib/ncc/instance.rb +213 -0
- data/lib/ncc-api.rb +218 -0
- data/lib/ncc.rb +102 -0
- metadata +96 -0
@@ -0,0 +1,590 @@
|
|
1
|
+
#!ruby
|
2
|
+
# /* Copyright 2013 Proofpoint, Inc. All rights reserved.
|
3
|
+
# Copyright 2014 Evernote Corporation. All rights reserved.
|
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
|
+
|
18
|
+
require 'rubygems'
|
19
|
+
require 'fog'
|
20
|
+
require 'uuidtools'
|
21
|
+
|
22
|
+
class NCC
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
class NCC::Connection
|
27
|
+
|
28
|
+
attr_reader :fog
|
29
|
+
|
30
|
+
def self.connect(ncc, cloud=:default, opt={})
|
31
|
+
cfg = ncc.config
|
32
|
+
provider = cfg[:clouds][cloud]['provider']
|
33
|
+
case provider
|
34
|
+
when 'aws'
|
35
|
+
require 'ncc/connection/aws'
|
36
|
+
NCC::Connection::AWS.new(ncc, cloud, opt)
|
37
|
+
when 'openstack'
|
38
|
+
require 'ncc/connection/openstack'
|
39
|
+
NCC::Connection::OpenStack.new(ncc, cloud, opt)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(ncc, cloud, opt={})
|
44
|
+
@ncc = ncc
|
45
|
+
@cache = { }
|
46
|
+
@cfg = ncc.config
|
47
|
+
@cfg_mtime = @cfg.mtime
|
48
|
+
@cloud = cloud
|
49
|
+
@logger = opt[:logger] if opt.has_key? :logger
|
50
|
+
@cache_timeout = opt[:cache_timeout] || 600
|
51
|
+
@auth_retry_limit = opt[:auth_retry_limit] || 1
|
52
|
+
do_connect
|
53
|
+
end
|
54
|
+
|
55
|
+
def current?
|
56
|
+
provider == @cfg[:clouds][@cloud]['provider'] and
|
57
|
+
@cfg_mtime == @cfg[:clouds][@cloud].mtime
|
58
|
+
end
|
59
|
+
|
60
|
+
def maybe_invalidate(type)
|
61
|
+
if @cache.has_key? type
|
62
|
+
@cache.delete(type) if
|
63
|
+
(Time.now - @cache[type][:timestamp]) > @cache_timeout
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def me
|
68
|
+
[self.class, provider, @cloud].join(' ')
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_s
|
72
|
+
'#<' + me + '>'
|
73
|
+
end
|
74
|
+
|
75
|
+
def warn(msg)
|
76
|
+
if @logger and @logger.respond_to? :warn
|
77
|
+
@logger.warn "#{self} #{msg}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def debug(msg=nil)
|
82
|
+
msg ||= yield if block_given?
|
83
|
+
if @logger and @logger.respond_to? :debug
|
84
|
+
@logger.debug "#{self} #{msg}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def notice(msg)
|
89
|
+
if @logger and @logger.respond_to? :notice
|
90
|
+
@logger.notice "#{self} #{msg}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def info(msg)
|
95
|
+
if @logger and @logger.respond_to? :info
|
96
|
+
@logger.info "#{self} #{msg}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def connection_params
|
101
|
+
[]
|
102
|
+
end
|
103
|
+
|
104
|
+
def do_connect
|
105
|
+
cloud_config = @cfg[:clouds][@cloud]
|
106
|
+
pnames = connection_params + ['provider']
|
107
|
+
params = Hash[cloud_config.to_hash(*pnames).map do |k, v|
|
108
|
+
[k.intern, v]
|
109
|
+
end ]
|
110
|
+
@fog = Fog::Compute.new(params)
|
111
|
+
end
|
112
|
+
|
113
|
+
def do_fog
|
114
|
+
do_connect unless @fog
|
115
|
+
remaining_tries = @auth_retry_limit
|
116
|
+
begin
|
117
|
+
yield @fog
|
118
|
+
rescue Excon::Errors::Unauthorized => err
|
119
|
+
# might be an expired auth token, retry
|
120
|
+
if remaining_tries > 0
|
121
|
+
do_connect
|
122
|
+
remaining_tries -= 1
|
123
|
+
retry
|
124
|
+
else
|
125
|
+
@fog = nil
|
126
|
+
raise NCC::Error::Cloud, "Error communicating with #{provider} " +
|
127
|
+
"cloud #{@cloud} (#{e.class}): #{e.message}"
|
128
|
+
end
|
129
|
+
rescue NCC::Error::Cloud => err
|
130
|
+
@fog = nil
|
131
|
+
raise err
|
132
|
+
rescue NCC::Error => err
|
133
|
+
raise err
|
134
|
+
rescue StandardError => err
|
135
|
+
@fog = nil
|
136
|
+
raise NCC::Error::Cloud, "Error communicating with #{provider} " +
|
137
|
+
"cloud #{@cloud} [#{err.class}]: #{err.message}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Warning. use_only_mapped is ignored for types with
|
142
|
+
# alternate id fields
|
143
|
+
def use_only_mapped(type)
|
144
|
+
false
|
145
|
+
end
|
146
|
+
|
147
|
+
def fields(obj, *flist)
|
148
|
+
Hash[flist.map { |f| [f, obj.send(f)] }]
|
149
|
+
end
|
150
|
+
|
151
|
+
def ids(a)
|
152
|
+
Hash[a.map { |e| [e['id'], e] }]
|
153
|
+
end
|
154
|
+
|
155
|
+
def provider
|
156
|
+
generic
|
157
|
+
end
|
158
|
+
|
159
|
+
def keyname_for(type)
|
160
|
+
case type
|
161
|
+
when :size
|
162
|
+
'sizes'
|
163
|
+
when :image
|
164
|
+
'images'
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Something like "images" => { "$abstract_id" => x }
|
169
|
+
# can have an x where it's the provider id, or an x
|
170
|
+
# which is a hash. If the hash has an id key, than that
|
171
|
+
# is used as a mapped id, if it doesn't, it's ignored
|
172
|
+
# (for the purposes of id mapping) -jbrinkley/20130410
|
173
|
+
def id_map_hash(h)
|
174
|
+
Hash[
|
175
|
+
h.map do |k, v|
|
176
|
+
if v.respond_to? :has_key?
|
177
|
+
if v.has_key? 'id'
|
178
|
+
[k, v['id']]
|
179
|
+
end
|
180
|
+
else
|
181
|
+
[k, v]
|
182
|
+
end
|
183
|
+
end.reject { |p| p.nil? }
|
184
|
+
]
|
185
|
+
end
|
186
|
+
|
187
|
+
def id_map(type)
|
188
|
+
debug "making id_map for #{type.inspect}"
|
189
|
+
keyname = keyname_for(type)
|
190
|
+
r = { }
|
191
|
+
debug "keyname = #{keyname} (for #{type.inspect})"
|
192
|
+
debug "provider map: #{@cfg[:providers][provider].to_hash.inspect}"
|
193
|
+
r.update(id_map_hash(@cfg[:providers][provider][keyname].to_hash)) if
|
194
|
+
@cfg[:providers][provider].has_key? keyname
|
195
|
+
#debug "cloud map: #{@cfg[:clouds][@cloud][keyname].to_hash}"
|
196
|
+
r.update(id_map_hash(@cfg[:clouds][@cloud][keyname].to_hash)) if
|
197
|
+
@cfg[:clouds][@cloud].has_key? keyname
|
198
|
+
debug " >> #{r.inspect}"
|
199
|
+
r
|
200
|
+
end
|
201
|
+
|
202
|
+
def provider_id_map(type)
|
203
|
+
debug "making provider_id_map for #{type.inspect}"
|
204
|
+
keyname = keyname_for(type)
|
205
|
+
r = { }
|
206
|
+
r.update(id_map_hash(@cfg[:providers][provider][keyname].
|
207
|
+
to_hash).invert) if
|
208
|
+
@cfg[:providers][provider].has_key? keyname
|
209
|
+
r.update(id_map_hash(@cfg[:clouds][@cloud][keyname].
|
210
|
+
to_hash).invert) if
|
211
|
+
@cfg[:clouds][@cloud].has_key? keyname
|
212
|
+
debug " >>> #{r.inspect}"
|
213
|
+
r
|
214
|
+
end
|
215
|
+
|
216
|
+
def map_to_id(type, provider_id)
|
217
|
+
debug "provider_id_map = #{provider_id_map(type).inspect}"
|
218
|
+
provider_id_map(type)[provider_id] || provider_id
|
219
|
+
end
|
220
|
+
|
221
|
+
def map_to_provider_id(type, abstract_id)
|
222
|
+
id_map(type)[abstract_id] || abstract_id
|
223
|
+
end
|
224
|
+
|
225
|
+
# raw meaning not using "our" notion of id_field
|
226
|
+
def map_from_raw_provider_id(type, raw_provider_id)
|
227
|
+
# Using collection here for cache
|
228
|
+
provider_object = collection(type).find do |item|
|
229
|
+
item.id == raw_provider_id
|
230
|
+
end
|
231
|
+
id_of(type, provider_object)
|
232
|
+
end
|
233
|
+
|
234
|
+
def map_to_status(provider_status)
|
235
|
+
provider_status
|
236
|
+
end
|
237
|
+
|
238
|
+
def map_to_provider_status(abstract_status)
|
239
|
+
abstract_status
|
240
|
+
end
|
241
|
+
|
242
|
+
def sizes(size_id=nil)
|
243
|
+
debug "sizes(#{size_id.inspect})"
|
244
|
+
if size_id.nil?
|
245
|
+
collection(:size).find_all do |flavor|
|
246
|
+
@cfg[:sizes].has_key? id_of(:size, flavor)
|
247
|
+
end.map { |f| object_of(:size, f) }
|
248
|
+
else
|
249
|
+
object_of(:size,
|
250
|
+
get_provider_object(:size => size_id))
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def images(image_id=nil)
|
255
|
+
if image_id.nil?
|
256
|
+
collection(:image).find_all do |provider_image|
|
257
|
+
@cfg[:images].has_key? id_of(:image, provider_image)
|
258
|
+
end.map { |i| object_of(:image, i) }
|
259
|
+
else
|
260
|
+
object_of(:image,
|
261
|
+
get_provider_object(:image => image_id))
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def id_field(abstract_object_type)
|
266
|
+
methodname = abstract_object_type.to_s + '_id_field'
|
267
|
+
if self.respond_to? methodname
|
268
|
+
self.send methodname
|
269
|
+
else
|
270
|
+
:id
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def id_of(abstract_object_type, obj)
|
275
|
+
map_to_id(abstract_object_type,
|
276
|
+
translated_id_of(abstract_object_type, obj))
|
277
|
+
end
|
278
|
+
|
279
|
+
def translated_id_of(abstract_object_type, obj)
|
280
|
+
id_fielder = id_field abstract_object_type
|
281
|
+
if id_fielder.kind_of? Proc
|
282
|
+
id_fielder.call obj
|
283
|
+
elsif obj.nil?
|
284
|
+
nil
|
285
|
+
else
|
286
|
+
obj.send id_fielder
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def collection(type, key=nil)
|
291
|
+
maybe_invalidate type
|
292
|
+
@cache[type] ||= {
|
293
|
+
:timestamp => Time.now,
|
294
|
+
:data => Hash[get_provider_objects(type).map { |o| [o.id, o] }]
|
295
|
+
}
|
296
|
+
if key.nil?
|
297
|
+
@cache[type][:data].values
|
298
|
+
else
|
299
|
+
@cache[type][:data][key]
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def get_provider_objects(type)
|
304
|
+
enum = case type
|
305
|
+
when :size
|
306
|
+
:flavors
|
307
|
+
when :image
|
308
|
+
:images
|
309
|
+
end
|
310
|
+
if use_only_mapped(type) and id_field(type) == :id
|
311
|
+
id_map(type).values.map do |id|
|
312
|
+
do_fog { |fog| fog.send(enum).get(id) }
|
313
|
+
end
|
314
|
+
else
|
315
|
+
do_fog { |fog| fog.send(enum) }
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def get_provider_object(provider_object_spec)
|
320
|
+
abstract_object_type, abstract_object_id = provider_object_spec.first
|
321
|
+
if id_field(abstract_object_type) != :id
|
322
|
+
collection(abstract_object_type).find do |provider_object|
|
323
|
+
id_of(abstract_object_type, provider_object) ==
|
324
|
+
abstract_object_id
|
325
|
+
end
|
326
|
+
else
|
327
|
+
provider_object_id = map_to_provider_id(abstract_object_type,
|
328
|
+
abstract_object_id)
|
329
|
+
collection(abstract_object_type, provider_object_id)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def translate(abstract_object_type, provider_object)
|
334
|
+
debug "translate(#{abstract_object_type.inspect}, #{provider_object})"
|
335
|
+
methodname = 'translate_' + abstract_object_type.to_s
|
336
|
+
if self.respond_to? methodname
|
337
|
+
self.send(methodname, provider_object)
|
338
|
+
else
|
339
|
+
generic_translate(abstract_object_type, provider_object)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def merge_configured(abstract_object, data)
|
344
|
+
if data.respond_to? :each_pair
|
345
|
+
data.each_pair { |k, v| abstract_object[k] ||= v }
|
346
|
+
else
|
347
|
+
abstract_object['provider_id'] ||= data
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def object_of(abstract_object_type, provider_object)
|
352
|
+
debug "object_of(#{abstract_object_type.inspect}, #{provider_object})"
|
353
|
+
abstract_object = translate(abstract_object_type, provider_object)
|
354
|
+
keyname = keyname_for abstract_object_type
|
355
|
+
id = abstract_object['id']
|
356
|
+
[@cfg[:clouds][@cloud], @cfg[:providers][provider]].each do |cfg|
|
357
|
+
if cfg.has_key? keyname and cfg[keyname].has_key? id
|
358
|
+
debug "merging cloud/provider data: cfg[#{keyname}][#{id}]"
|
359
|
+
merge_configured(abstract_object, cfg[keyname][id])
|
360
|
+
end
|
361
|
+
end
|
362
|
+
if @cfg.has_key? keyname.intern and @cfg[keyname.intern].has_key? id
|
363
|
+
debug "merging abstract data: " +
|
364
|
+
"@cfg[#{keyname.intern.inspect}][#{id}]"
|
365
|
+
merge_configured(abstract_object, @cfg[keyname.intern][id].to_hash)
|
366
|
+
end
|
367
|
+
debug "object_of returns: #{abstract_object.inspect}"
|
368
|
+
abstract_object
|
369
|
+
end
|
370
|
+
|
371
|
+
def generic_translate(abstract_object_type, provider_object)
|
372
|
+
abstract_object = {
|
373
|
+
'id' => id_of(abstract_object_type, provider_object),
|
374
|
+
'provider_id' => provider_object.id
|
375
|
+
}
|
376
|
+
[:name, :description].each do |field|
|
377
|
+
abstract_object[field.to_s] = provider_object.send(field) if
|
378
|
+
provider_object.respond_to? field
|
379
|
+
end
|
380
|
+
abstract_object
|
381
|
+
end
|
382
|
+
|
383
|
+
def instance_for(server)
|
384
|
+
instance = NCC::Instance.new(@cfg, :logger => @logger)
|
385
|
+
instance.set_without_validation(:id => server.id)
|
386
|
+
instance.set_without_validation(:name => instance_name(server))
|
387
|
+
instance.set_without_validation(:size => instance_size(server))
|
388
|
+
instance.set_without_validation(:image => instance_image(server))
|
389
|
+
instance.set_without_validation(:ip_address =>
|
390
|
+
instance_ip_address(server))
|
391
|
+
instance.set_without_validation(:host => instance_host(server))
|
392
|
+
instance.set_without_validation(:status => instance_status(server))
|
393
|
+
instance
|
394
|
+
end
|
395
|
+
|
396
|
+
def instance_name(server)
|
397
|
+
server.name
|
398
|
+
end
|
399
|
+
|
400
|
+
def instance_size(server)
|
401
|
+
map_to_id(:size, server.size)
|
402
|
+
end
|
403
|
+
|
404
|
+
def instance_image(server)
|
405
|
+
map_to_id(:image, server.image)
|
406
|
+
end
|
407
|
+
|
408
|
+
def instance_status(server)
|
409
|
+
map_to_status(server.status)
|
410
|
+
end
|
411
|
+
|
412
|
+
def instance_host(server)
|
413
|
+
nil
|
414
|
+
end
|
415
|
+
|
416
|
+
def instance_ip_address(server)
|
417
|
+
server.ip_address
|
418
|
+
end
|
419
|
+
|
420
|
+
def instances(instance_id=nil)
|
421
|
+
debug "instances(#{instance_id.inspect})"
|
422
|
+
if instance_id.nil?
|
423
|
+
do_fog { |fog| fog.servers }.map { |server| instance_for server }
|
424
|
+
else
|
425
|
+
server = do_fog { |fog| fog.servers.get instance_id }
|
426
|
+
if server.nil?
|
427
|
+
instance_not_found instance_id
|
428
|
+
end
|
429
|
+
instance_for server
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
def provider_request(instance)
|
434
|
+
keyintern(
|
435
|
+
provider_request_of(instance.
|
436
|
+
with_defaults(@cfg[:clouds][@cloud]['defaults'],
|
437
|
+
@cfg[:providers][provider]['defaults']))
|
438
|
+
)
|
439
|
+
end
|
440
|
+
|
441
|
+
def provider_request_of(instance)
|
442
|
+
generic_provider_request(instance)
|
443
|
+
end
|
444
|
+
|
445
|
+
def generic_provider_request(instance)
|
446
|
+
{
|
447
|
+
:name => instance.name,
|
448
|
+
:flavor => map_to_provider_id(:size, instance.size),
|
449
|
+
:size => map_to_provider_id(:image, instance.image),
|
450
|
+
}.merge(instance.extra provider)
|
451
|
+
end
|
452
|
+
|
453
|
+
def keyintern(h)
|
454
|
+
Hash[h.map do |k, v|
|
455
|
+
[k.to_sym,
|
456
|
+
((v.is_a? Hash and (k.to_sym != :tags)) ?
|
457
|
+
keyintern(v) : v)]
|
458
|
+
end]
|
459
|
+
end
|
460
|
+
|
461
|
+
def inventory_request(instance)
|
462
|
+
i = instance.with_defaults(@cfg[:clouds][@cloud]['defaults'],
|
463
|
+
@cfg[:providers][provider]['defaults'])
|
464
|
+
{
|
465
|
+
'fqdn' => i.name,
|
466
|
+
'size' => i.size,
|
467
|
+
'image' => i.image,
|
468
|
+
'serial_number' => i.id,
|
469
|
+
'roles' => i.role.sort.join(','),
|
470
|
+
'ip_address' => i.ip_address,
|
471
|
+
'host_fqdn' => host_fqdn(i.host),
|
472
|
+
'environment_name' => i.environment
|
473
|
+
}.merge(instance.extra 'inventory')
|
474
|
+
end
|
475
|
+
|
476
|
+
def host_fqdn(host)
|
477
|
+
if @cfg[:clouds][@cloud].has_key? 'host_domain'
|
478
|
+
host + '.' + @cfg[:clouds][@cloud]['host_domain']
|
479
|
+
else
|
480
|
+
host
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
def modify_instance_request(instance)
|
485
|
+
instance
|
486
|
+
end
|
487
|
+
|
488
|
+
def create_instance(instance_spec, wait_for_ip=60)
|
489
|
+
if ! instance_spec.kind_of? NCC::Instance
|
490
|
+
instance_spec = NCC::Instance.new(@cfg, instance_spec)
|
491
|
+
end
|
492
|
+
req_id = ['ncc',
|
493
|
+
@cloud,
|
494
|
+
UUIDTools::UUID.random_create.to_s].join('-')
|
495
|
+
begin
|
496
|
+
info "#{@cloud} requesting name from inventory using #{req_id}"
|
497
|
+
fqdn = @ncc.inventory.get_or_assign_system_name(req_id)['fqdn']
|
498
|
+
rescue StandardError => e
|
499
|
+
raise NCC::Error::Cloud, "Error [#{e.class}] " +
|
500
|
+
"communicating with inventory to " +
|
501
|
+
"assign name using (ncc_req_id=#{req_id}): #{e.message}"
|
502
|
+
end
|
503
|
+
instance_spec.name = fqdn
|
504
|
+
begin
|
505
|
+
req = provider_request(instance_spec)
|
506
|
+
t0 = Time.now
|
507
|
+
info "#{@cloud} sending request: #{req.inspect}"
|
508
|
+
server = do_fog { |fog| fog.servers.create(req) }
|
509
|
+
# The reason we wait for an IP is so we can add it to
|
510
|
+
# inventory, which then calculates the datacenter
|
511
|
+
if wait_for_ip > 0 and instance_ip_address(server).nil?
|
512
|
+
info "#{@cloud} waiting for ip on #{server.id}"
|
513
|
+
this = self
|
514
|
+
server.wait_for(wait_for_ip) { this.instance_ip_address(server) }
|
515
|
+
end
|
516
|
+
rescue StandardError => err
|
517
|
+
inv_update = { 'fqdn' => fqdn, 'status' => 'decommissioned' }
|
518
|
+
if ! server.nil? and server.id
|
519
|
+
inv_update['serial_number'] = server.id
|
520
|
+
end
|
521
|
+
@ncc.inventory.update('system', inv_update, fqdn)
|
522
|
+
server_id = (server.nil? ? 'nil' : server.id)
|
523
|
+
communication_error "[#{err.class}] (ncc_req_id=#{req_id} " +
|
524
|
+
"instance_id=#{server_id}): #{err.message}"
|
525
|
+
end
|
526
|
+
elapsed = Time.now - t0
|
527
|
+
info "Created instance instance_id=#{server.id} at #{provider} cloud #{@cloud} in #{elapsed}s"
|
528
|
+
instance = instance_for server
|
529
|
+
instance_spec.id = instance.id
|
530
|
+
instance_spec.ip_address = instance.ip_address
|
531
|
+
instance_spec.host = instance.host
|
532
|
+
inv_req = inventory_request(instance_spec)
|
533
|
+
inv_req['cloud'] = @cloud
|
534
|
+
inv_req['status'] = 'building'
|
535
|
+
begin
|
536
|
+
info "#{@cloud} updating inventory #{fqdn}/#{req_id} -> #{inv_req.inspect}"
|
537
|
+
@ncc.inventory.update('system', inv_req, fqdn)
|
538
|
+
rescue StandardError => e
|
539
|
+
raise NCC::Error::Cloud, "Error [#{e.class}] updating inventory " +
|
540
|
+
"system #{fqdn} " +
|
541
|
+
"(cloud=#{@cloud} ncc_req_id=#{req_id} " +
|
542
|
+
"instance_id=#{server.id}): " + e.message
|
543
|
+
end
|
544
|
+
instance
|
545
|
+
end
|
546
|
+
|
547
|
+
def instance_not_found(instance_id)
|
548
|
+
raise NCC::Error::NotFound, "Instance #{instance_id.inspect} not " +
|
549
|
+
"found in #{provider} cloud #{@cloud}"
|
550
|
+
end
|
551
|
+
|
552
|
+
def communication_error(message)
|
553
|
+
message = "Error communicating with #{provider} cloud #{@cloud}: " + message unless
|
554
|
+
/Error .*communicating with/.match(message)
|
555
|
+
raise NCC::Error::Cloud, message
|
556
|
+
end
|
557
|
+
|
558
|
+
def delete(instance_id)
|
559
|
+
server = do_fog { |fog| fog.servers.get(instance_id) }
|
560
|
+
if server.nil?
|
561
|
+
instance_not_found instance_id
|
562
|
+
else
|
563
|
+
begin
|
564
|
+
instance = instance_for server
|
565
|
+
server.destroy
|
566
|
+
inv_update = { 'fqdn' => instance.name,
|
567
|
+
'status' => 'decommissioned' }
|
568
|
+
@ncc.inventory.update('system', inv_update, instance.name)
|
569
|
+
rescue StandardError => e
|
570
|
+
communication_error "deleting fqdn=#{instance.name} " +
|
571
|
+
"instance_id=#{server.id}: #{e.message}"
|
572
|
+
end
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
def console_log(instance_id)
|
577
|
+
raise NCC::Error::NotFound, "Cloud #{@cloud} provider " +
|
578
|
+
"#{provider} does not support console logs"
|
579
|
+
end
|
580
|
+
|
581
|
+
def reboot(instance_id)
|
582
|
+
server = do_fog { |fog| fog.servers.get(instance_id) }
|
583
|
+
if server.nil?
|
584
|
+
instance_not_found instance_id
|
585
|
+
else
|
586
|
+
server.reboot
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
end
|
data/lib/ncc/error.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!ruby
|
2
|
+
# /* Copyright 2013 Proofpoint, Inc. All rights reserved.
|
3
|
+
# Copyright 2014 Evernote Corporation. All rights reserved.
|
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
|
+
|
18
|
+
|
19
|
+
class NCC
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
class NCC::Error < StandardError
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class NCC::Error::NotFound < NCC::Error
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class NCC::Error::Cloud < NCC::Error
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
class NCC::Error::Internal < NCC::Error
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class NCC::Error::Client < NCC::Error
|
40
|
+
|
41
|
+
end
|