ncc-api 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.
- 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
|