fog-softlayer 1.1.2 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 73ebba96e744c0f5dc4a363aa25591e84fb0cb40
4
- data.tar.gz: 74fd09cb62c3b20a9c922da50aec44ca1a9992bc
3
+ metadata.gz: 1638c2a2c405ace844c08b39469d1fcea4d3226e
4
+ data.tar.gz: 5967490b997da6fa1b4c09a9a34d2564db509468
5
5
  SHA512:
6
- metadata.gz: 651ae070cf40d4859b62ad6ccab01113e83c9380522e3ca0d4efa91d6074d4f7d5d4b185a4cd29ac765c46fb66f8aa9e91d5df462ad84d50fc1c138681675008
7
- data.tar.gz: 96f49259a5c1dee89bcc9cc0e27354eb4b6c4547b3e9f265d033592bf43485f930e5b5cbf24b302ee2991bbdec68457babeb42f166ec652c69f7c316bf70007e
6
+ metadata.gz: a4f9a8a37fd5bbd9a8256cf525c2afb7943923a340e37613145dafefbbe7c6f816eb981b59852201695fac027f6c70629cb660f64444694057712cb1bf75ecb6
7
+ data.tar.gz: 780372eb4c8346205d56128526e4d0ab39e3b5c651996749259265769fb76c11820fd9619167afd67c52c488c4f03519d489ebd2d979a4aa08a4c854f43183a6
@@ -1,628 +1,638 @@
1
- #
2
- # Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
3
- # © Copyright IBM Corporation 2014.
4
- #
5
- # LICENSE: MIT (http://opensource.org/licenses/MIT)
6
- #
7
-
8
- require 'fog/compute/models/server'
9
-
10
- module Fog
11
- module Compute
12
- class Softlayer
13
-
14
- class Server < Fog::Compute::Server
15
-
16
- identity :id, :type => :integer
17
- attribute :name, :aliases => 'hostname'
18
- attribute :domain
19
- attribute :fqdn, :aliases => 'fullyQualifiedDomainName'
20
- attribute :cpu, :aliases => ['startCpus', 'processorCoreAmount']
21
- attribute :ram, :aliases => ['maxMemory', 'memory']
22
- attribute :disk, :aliases => ['blockDevices','hardDrives']
23
- attribute :private_ip_address, :aliases => 'primaryBackendIpAddress'
24
- attribute :public_ip_address, :aliases => 'primaryIpAddress'
25
- attribute :flavor_id
26
- attribute :bare_metal, :type => :boolean
27
- attribute :os_code
28
- attribute :image_id
29
- attribute :ephemeral_storage, :aliases => 'localDiskFlag'
30
- attribute :key_pairs, :aliases => 'sshKeys'
31
- attribute :network_components
32
- attribute :fixed_configuration_preset, :aliases => 'fixedConfigurationPreset'
33
-
34
- # Times
35
- attribute :created_at, :aliases => ['createDate', 'provisionDate'], :type => :time
36
- attribute :last_verified_date, :aliases => 'lastVerifiedDate', :type => :time
37
- attribute :metric_poll_date, :aliases => 'metricPollDate', :type => :time
38
- attribute :modify_date, :aliases => 'modifyDate', :type => :time
39
-
40
- # Metadata
41
- attribute :account_id, :aliases => 'accountId', :type => :integer
42
- attribute :datacenter, :aliases => 'datacenter'
43
- attribute :single_tenant, :aliases => 'dedicatedAccountHostOnlyFlag'
44
- attribute :global_identifier, :aliases => 'globalIdentifier'
45
- attribute :hourly_billing_flag, :aliases => 'hourlyBillingFlag'
46
- attribute :tags, :aliases => 'tagReferences'
47
- attribute :private_network_only, :aliases => 'privateNetworkOnlyFlag'
48
- attribute :user_data, :aliases => 'userData'
49
- attribute :uid, :aliases => 'globalIdentifier'
50
- attribute :provision_script, :aliases => 'postInstallScriptUri'
51
-
52
- def initialize(attributes = {})
53
- # Forces every request inject bare_metal parameter
54
- raise Exception if attributes[:collection].nil? and attributes['bare_metal'].nil?
55
- super(attributes)
56
- set_defaults
57
- end
58
-
59
- def add_tags(tags)
60
- requires :id
61
- raise ArgumentError, "Tags argument for #{self.class.name}##{__method__} must be Array." unless tags.is_a?(Array)
62
- tags.each do |tag|
63
- service.tags.new(:resource_id => self.id, :name => tag).save
64
- end
65
- self.reload
66
- true
67
- end
68
-
69
- def bare_metal?
70
- bare_metal
71
- end
72
-
73
- def bare_metal
74
- @bare_metal
75
- end
76
-
77
- def datacenter=(name)
78
- name = name['name'] if name.is_a?(Hash)
79
- attributes[:datacenter] = { :name => name }
80
- end
81
-
82
- def datacenter
83
- attributes[:datacenter][:name] unless attributes[:datacenter].nil?
84
- end
85
-
86
- def delete_tags(tags)
87
- requires :id
88
- raise ArgumentError, "Tags argument for #{self.class.name}##{__method__} must be Array." unless tags.is_a?(Array)
89
- tags.each do |tag|
90
- service.tags.new(:resource_id => self.id, :name => tag).destroy
91
- end
92
- self.reload
93
- true
94
- end
95
-
96
- def destroy
97
- requires :id
98
- request = bare_metal? ? :delete_bare_metal_server : :delete_vm
99
- response = service.send(request, self.id)
100
- response.body
101
- end
102
-
103
- def dns_name
104
- fqdn
105
- end
106
-
107
- def image_id=(uuid)
108
- attributes[:image_id] = {:globalIdentifier => uuid}
109
- end
110
-
111
- def image_id
112
- attributes[:image_id][:globalIdentifier] unless attributes[:image_id].nil?
113
- end
114
-
115
- def name=(set)
116
- attributes[:hostname] = set
117
- end
118
-
119
- def name
120
- attributes[:hostname]
121
- end
122
-
123
- def pre_save
124
- extract_flavor
125
- self.bare_metal = true if attributes[:fixed_configuration_preset] and not bare_metal?
126
- validate_attributes
127
- if self.vlan
128
- attributes[:vlan] = { :networkVlan => { :id => self.vlan.id } }
129
- end
130
- if self.private_vlan
131
- attributes[:private_vlan] = { :networkVlan => { :id => self.private_vlan.id } }
132
- end
133
- if self.key_pairs
134
- attributes[:key_pairs].map! { |key| { :id => key.respond_to?(:id) ? key.id : key } }
135
- end
136
- if self.network_components
137
- self.network_components = self.network_components.map do |component|
138
- component['maxSpeed'] = component.delete('speed') if component['speed']
139
- component['maxSpeed'] = component.delete('max_speed') if component['max_speed']
140
- component
141
- end
142
- end
143
-
144
- if attributes[:fixed_configuration_preset].is_a? String
145
- attributes[:fixedConfigurationPreset] = {:keyName => attributes.delete(:fixed_configuration_preset)}
146
- end
147
-
148
- remap_attributes(attributes, attributes_mapping)
149
- clean_attributes
150
- end
151
-
152
- def private_ip # maintain backward compatibility with <0.3.13
153
- private_ip_address
154
- end
155
-
156
- def public_ip # maintain backward compatibility with <0.3.13
157
- public_ip_address
158
- end
159
-
160
- def os_code
161
- attributes['operatingSystem']['softwareLicense']['softwareDescription']['referenceCode'] if attributes['operatingSystem']
162
- end
163
-
164
- def private_vlan
165
- attributes[:private_vlan] ||= _get_private_vlan
166
- end
167
-
168
- def private_vlan=(value)
169
- unless value.is_a?(Integer) or value.is_a?(Fog::Network::Softlayer::Network)
170
- raise ArgumentError, "vlan argument for #{self.class.name}##{__method__} must be Integer or Fog::Network::Softlayer::Network."
171
- end
172
- value = network_connection.networks.get(value) if value.is_a?(Integer)
173
- attributes[:private_vlan] = value
174
- end
175
-
176
- # reload the OS on a server (method name reload was already taken)
177
- def relaunch!
178
- requires :id
179
- body = [ "FORCE", {}]
180
- body[1][:sshKeyIds] = key_pairs.map {|kp| kp.id} unless key_pairs.empty?
181
- type = bare_metal? ? :hardware_server : :virtual_guest
182
- status = service.request(type, "#{id}/reloadOperatingSystem", :body => body, :http_method => :post).status
183
- wait_for { not ready? } # block until the relaunch has begun
184
- [200, 201].include?(status)
185
- end
186
-
187
- def key_pairs
188
- attributes[:key_pairs]
189
- end
190
-
191
- def key_pairs=(keys)
192
- raise ArgumentError, "Argument #{local_variables.first.to_s} for #{self.class.name}##{__method__} must be Array." unless keys.is_a?(Array)
193
- attributes[:key_pairs] = []
194
- keys.map do |key|
195
- ## This was nice but causing an intolerable number of requests on an account with lots of keys.
196
- ## ToDo: something better...
197
- #key = self.symbolize_keys(key) if key.is_a?(Hash)
198
- #unless key.is_a?(Fog::Compute::Softlayer::KeyPair) or (key.is_a?(Hash) and key[:id])
199
- # raise ArgumentError, "Elements of keys array for #{self.class.name}##{__method__} must be a Hash with key 'id', or Fog::Compute::Softlayer::KeyPair"
200
- #end
201
- #key = service.key_pairs.get(key[:id]) unless key.is_a?(Fog::Compute::Softlayer::KeyPair)
202
- attributes[:key_pairs] << key
203
- end
204
- end
205
-
206
- def vlan
207
- attributes[:vlan] ||= _get_vlan
208
- end
209
-
210
- def vlan=(value)
211
- unless value.is_a?(Integer) or value.is_a?(Fog::Network::Softlayer::Network)
212
- raise ArgumentError, "vlan argument for #{self.class.name}##{__method__} must be Integer or Fog::Network::Softlayer::Network."
213
- end
214
- value = network_connection.networks.get(value) if value.is_a?(Integer)
215
- attributes[:vlan] = value
216
- end
217
-
218
- def ram=(set)
219
- if set.is_a?(Array) and set.first['hardwareComponentModel']
220
- set = 1024 * set.first['hardwareComponentModel']['capacity'].to_i
221
- end
222
- attributes[:ram] = set
223
- end
224
-
225
- # @params value [String]
226
- def user_data=(value)
227
- attributes[:user_data] = [{'value' => value}]
228
- end
229
-
230
- def user_data
231
- attributes[:user_data]
232
- end
233
-
234
- def provision_script=(value)
235
- attributes[:provision_script] = value
236
- end
237
-
238
- def provision_script
239
- attributes[:provision_script]
240
- end
241
-
242
- def network_components
243
- if id
244
- (public_network_components << private_network_components).flatten
245
- else
246
- attributes[:network_components]
247
- end
248
- end
249
-
250
- def public_network_components
251
- if attributes['frontendNetworkComponents']
252
- attributes['frontendNetworkComponents'].map { |n| Fog::Compute::Softlayer::NetworkComponent.new(n) }
253
- else
254
- []
255
- end
256
- end
257
-
258
- def private_network_components
259
- if attributes['backendNetworkComponents']
260
- attributes['backendNetworkComponents'].map { |n| Fog::Compute::Softlayer::NetworkComponent.new(n) }
261
- else
262
- []
263
- end
264
- end
265
-
266
- def ready?
267
- begin
268
- if bare_metal?
269
- state == "on"
270
- else
271
- state == "Running"
272
- end
273
- rescue Excon::Errors::InternalServerError => e
274
- false
275
- end
276
- end
277
-
278
- def reboot(use_hard_reboot = true)
279
- # requires :id # TODO: debug why this breaks the tests on bare metal and uncomment this
280
- if bare_metal?
281
- service.reboot_bare_metal_server(id, use_hard_reboot)
282
- else
283
- service.reboot_vm(id, use_hard_reboot)
284
- end
285
- true
286
- end
287
-
288
- def ssh_password
289
- requires :id
290
- service_path = bare_metal? ? :hardware_server : :virtual_guest
291
- @sshpass ||= service.request(service_path, id, :query => 'objectMask=mask[id,operatingSystem.passwords[password]]').body
292
- @sshpass['operatingSystem']['passwords'][0]['password'] unless @sshpass['operatingSystem'].nil? or @sshpass['operatingSystem']['passwords'].empty?
293
- end
294
-
295
- def snapshot
296
- # TODO: implement
297
- end
298
-
299
- def start
300
- # requires :id # TODO: debug why this breaks the tests on bare metal and uncomment this
301
- if bare_metal?
302
- service.power_on_bare_metal_server(id)
303
- else
304
- service.power_on_vm(id)
305
- end
306
- true
307
- end
308
-
309
- # Hard power off
310
- def stop
311
- # requires :id # TODO: debug why this breaks the tests on bare metal and uncomment this
312
- if bare_metal?
313
- service.power_off_bare_metal_server(id)
314
- else
315
- service.power_off_vm(id, true)
316
- end
317
- true
318
- end
319
-
320
- # Soft power off
321
- def shutdown
322
- # requires :id # TODO: debug why this breaks the tests on bare metal and uncomment this
323
- if bare_metal?
324
- raise Fog::Errors::Error.new('Shutdown not supported on baremetal servers. Use #stop.')
325
- else
326
- service.power_off_vm(id, false)
327
- end
328
- true
329
- end
330
-
331
- def state
332
- if bare_metal?
333
- service.request(:hardware_server, "#{id}/getServerPowerState").body
334
- else
335
- service.request(:virtual_guest, "#{id}/getPowerState").body['name']
336
- end
337
- end
338
-
339
- # Creates server
340
- # * requires attributes: :name, :domain, and :flavor_id OR (:cpu_count && :ram && :disks)
341
- #
342
- # @note You should use servers.create to create servers instead calling this method directly
343
- #
344
- # * State Transitions
345
- # * BUILD -> ACTIVE
346
- # * BUILD -> ERROR (on error)
347
- def save
348
- raise Fog::Errors::Error.new('Resaving an existing object may create a duplicate') if persisted?
349
- copy = self.dup
350
- copy.pre_save
351
-
352
- data = if copy.bare_metal?
353
- service.create_bare_metal_server(copy.attributes).body
354
- else
355
- service.create_vm(copy.attributes).body.first
356
- end
357
-
358
- data.delete("bare_metal")
359
- merge_attributes(data)
360
- true
361
- end
362
-
363
- def tags
364
- attributes[:tags].map { |i| i['tag']['name'] if i['tag'] }.compact if attributes[:tags]
365
- end
366
-
367
- def get_active_tickets
368
- return service.get_bare_metal_active_tickets(id).body if bare_metal?
369
- service.get_virtual_guest_active_tickets(id).body
370
- end
371
-
372
- def get_users
373
- return service.get_bare_metal_users(id).body if bare_metal?
374
- service.get_virtual_guest_users(id).body
375
- end
376
-
377
- def get_upgrade_options
378
- return service.get_bare_metal_upgrade_item_prices(id).body if bare_metal?
379
- service.get_virtual_guest_upgrade_item_prices(id).body
380
- end
381
-
382
- def update(update_attributes)
383
- raise ArgumentError if update_attributes.nil?
384
- product_connection
385
- prices = get_item_prices_id(update_attributes)
386
- order = generate_upgrade_order(prices, update_attributes[:time] || update_attributes[:maintenance_window])
387
- @product_conn.place_order(order).body
388
- end
389
-
390
- def generate_order_template
391
- copy = self.dup
392
- copy.pre_save
393
- return service.generate_bare_metal_order_template(copy.attributes).body if copy.bare_metal?
394
- service.generate_virtual_guest_order_template(copy.attributes).body
395
- end
396
-
397
- def wait_for_id(timeout=14400, delay=30)
398
- # Cannot use self.wait_for because it calls reload which requires
399
- # self.id which is not initially available for bare metal.
400
- filterStr = Fog::JSON.encode({
401
- "hardware" => {
402
- "hostname" => {
403
- "operation" => self.name,
404
- },
405
- "domain" => {
406
- "operation" => self.domain,
407
- },
408
- "globalIdentifier" => {
409
- "operation" => self.uid,
410
- },
411
- }
412
- })
413
-
414
- Fog.wait_for(timeout, delay) do
415
- res = service.request(:account, 'getHardware', :query => {
416
- :objectMask => 'mask[id,fullyQualifiedDomainName,provisionDate,hardwareStatus,lastTransaction[elapsedSeconds,transactionStatus[friendlyName]],operatingSystem[id,passwords[password,username]]]',
417
- :objectFilter => filterStr,
418
- })
419
-
420
- server = res.body.first
421
-
422
- yield server if block_given?
423
-
424
- if server and server["provisionDate"]
425
- attributes[:id] = server['id']
426
- true
427
- else
428
- false
429
- end
430
- end
431
-
432
- self.reload
433
- true
434
- end
435
-
436
- private
437
-
438
- def network_connection
439
- @network_conn ||= Fog::Network.new(
440
- :provider => :softlayer,
441
- :softlayer_username => service.instance_variable_get(:@softlayer_username),
442
- :softlayer_api_key => service.instance_variable_get(:@softlayer_api_key)
443
- )
444
- end
445
-
446
- def product_connection
447
- if Fog.mock?
448
- @product_conn = Fog::Softlayer::Product.new(
449
- :provider => :softlayer,
450
- :softlayer_username => service.instance_variable_get(:@credentials)[:username],
451
- :softlayer_api_key => service.instance_variable_get(:@credentials)[:api_key]
452
- )
453
- end
454
- @product_conn ||= Fog::Softlayer::Product.new(
455
- :provider => :softlayer,
456
- :softlayer_username => service.instance_variable_get(:@softlayer_username),
457
- :softlayer_api_key => service.instance_variable_get(:@softlayer_api_key)
458
- )
459
- end
460
-
461
- def _get_private_vlan
462
- if self.id
463
- vlan_id = if bare_metal?
464
- service.request(:hardware_server, "#{self.id}/get_private_vlan").body['id']
465
- else
466
- service.request(:virtual_guest, self.id, :query => 'objectMask=primaryBackendNetworkComponent.networkVlan').body['primaryBackendNetworkComponent']['networkVlan']['id']
467
- end
468
- network_connection.networks.get(vlan_id)
469
- end
470
- end
471
-
472
- def _get_vlan
473
- if self.id
474
- vlan_id = if bare_metal?
475
- service.request(:hardware_server, "#{self.id}/get_public_vlan").body['id']
476
- else
477
- service.request(:virtual_guest, self.id, :query => 'objectMask=primaryNetworkComponent.networkVlan').body['primaryNetworkComponent']['networkVlan']['id']
478
- end
479
- network_connection.networks.get(vlan_id)
480
- end
481
- end
482
-
483
- ##
484
- # Generate mapping for use with remap_attributes
485
- def attributes_mapping
486
- common = {
487
- :hourly_billing_flag => :hourlyBillingFlag,
488
- :os_code => :operatingSystemReferenceCode,
489
- :vlan => :primaryNetworkComponent,
490
- :private_vlan => :primaryBackendNetworkComponent,
491
- :key_pairs => :sshKeys,
492
- :private_network_only => :privateNetworkOnlyFlag,
493
- :user_data => :userData,
494
- :provision_script => :postInstallScriptUri,
495
- :network_components => :networkComponents,
496
- }
497
-
498
- conditional = if bare_metal?
499
- {
500
- :cpu => :processorCoreAmount,
501
- :ram => :memoryCapacity,
502
- :disk => :hardDrives,
503
- :bare_metal => :bareMetalInstanceFlag,
504
- :fixed_configuration_preset => :fixedConfigurationPreset,
505
- }
506
- else
507
- {
508
- :cpu => :startCpus,
509
- :ram => :maxMemory,
510
- :disk => :blockDevices,
511
- :image_id => :blockDeviceTemplateGroup,
512
- :ephemeral_storage => :localDiskFlag,
513
- }
514
- end
515
- common.merge(conditional)
516
- end
517
-
518
- def bare_metal=(set)
519
- return @bare_metal if set == @bare_metal
520
- raise Exception, "Bare metal flag has already been set" unless @bare_metal.nil?
521
- @bare_metal = case set
522
- when false, 'false', 0, nil, ''
523
- attributes[:bare_metal] = false
524
- else
525
- attributes[:bare_metal] = true
526
- end
527
- end
528
-
529
- ##
530
- # Remove model attributes that aren't expected by the SoftLayer API
531
- def clean_attributes
532
- attributes.delete(:bare_metal)
533
- attributes.delete(:flavor_id)
534
- attributes.delete(:ephemeral_storage)
535
- attributes.delete(:tags) if bare_metal?
536
- end
537
-
538
- ##
539
- # Expand a "flavor" into cpu, ram, and disk attributes
540
- def extract_flavor
541
- if attributes[:flavor_id]
542
- flavor = @service.flavors.get(attributes[:flavor_id])
543
- flavor.nil? and Fog::Errors::Error.new("Unrecognized flavor in #{self.class}##{__method__}")
544
- attributes[:cpu] = flavor.cpu
545
- attributes[:ram] = flavor.ram
546
- attributes[:disk] = flavor.disk unless attributes[:image_id]
547
- if bare_metal?
548
- value = flavor.disk.first['diskImage']['capacity'] < 500 ? 250 : 500
549
- attributes[:disk] = [{'capacity'=>value}]
550
- attributes[:ram] = attributes[:ram] / 1024 if attributes[:ram] > 64
551
- end
552
- end
553
- end
554
-
555
- def validate_attributes
556
- requires :name, :domain, :datacenter
557
- if attributes[:fixed_configuration_preset]
558
- requires :os_code
559
- else
560
- requires :cpu, :ram
561
- requires_one :os_code, :image_id
562
- requires_one :disk, :image_id
563
- end
564
- bare_metal? and image_id and raise ArgumentError, "Bare Metal Cloud does not support booting from Image"
565
- end
566
-
567
- def set_defaults
568
- attributes[:hourly_billing_flag] = true if attributes[:hourly_billing_flag].nil?
569
- attributes[:ephemeral_storage] = false if attributes[:ephemeral_storage].nil?
570
- attributes[:domain] = service.softlayer_default_domain if service.softlayer_default_domain and attributes[:domain].nil?
571
- self.datacenter = service.softlayer_default_datacenter if service.softlayer_default_datacenter and attributes[:datacenter].nil?
572
- end
573
-
574
- def get_item_prices_id_by_value(item_price_array, category, value)
575
- item_prices = item_price_array.select { |item_price| item_price["categories"].find { |category_hash| category_hash["categoryCode"] == category } }
576
- item_price = item_prices.find { |item_price| item_price['item']['capacity'] == value.to_s }
577
- item_price.nil? ? "" : item_price["id"]
578
- end
579
-
580
- def get_item_prices_id(update_attributes)
581
- item_price_array = get_upgrade_options
582
- update_attributes.delete(:time)
583
- update_attributes.delete(:maintenance_window)
584
- update_attributes.map { |key, value| { :id => get_item_prices_id_by_value(item_price_array, key.to_s, value) } }
585
- end
586
-
587
- def bm_upgrade_order_template(value)
588
- {
589
- :complexType => 'SoftLayer_Container_Product_Order_Hardware_Server_Upgrade',
590
- :hardware => [
591
- {
592
- :id => id
593
- }
594
- ],
595
- :properties => [
596
- {
597
- :name => 'MAINTENANCE_WINDOW_ID',
598
- :value => value
599
- }
600
- ]
601
- }
602
- end
603
-
604
- def vm_upgrade_order_template(time)
605
- {
606
- :complexType => 'SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade',
607
- :virtualGuests => [
608
- {
609
- :id => id
610
- }
611
- ],
612
- :properties => [
613
- {
614
- :name => 'MAINTENANCE_WINDOW',
615
- :value => (time.nil? || time.empty?) ? Time.now.iso8601 : time.iso8601
616
- }
617
- ]
618
- }
619
- end
620
-
621
- def generate_upgrade_order(prices, value)
622
- return bm_upgrade_order_template(value).merge({ :prices => prices }) if bare_metal?
623
- vm_upgrade_order_template(value).merge({ :prices => prices })
624
- end
625
- end
626
- end
627
- end
628
- end
1
+ #
2
+ # Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
3
+ # © Copyright IBM Corporation 2014.
4
+ #
5
+ # LICENSE: MIT (http://opensource.org/licenses/MIT)
6
+ #
7
+
8
+ require 'fog/compute/models/server'
9
+
10
+ module Fog
11
+ module Compute
12
+ class Softlayer
13
+
14
+ class Server < Fog::Compute::Server
15
+
16
+ identity :id, :type => :integer
17
+ attribute :name, :aliases => 'hostname'
18
+ attribute :domain
19
+ attribute :fqdn, :aliases => 'fullyQualifiedDomainName'
20
+ attribute :cpu, :aliases => ['startCpus', 'processorCoreAmount']
21
+ attribute :ram, :aliases => ['maxMemory', 'memory']
22
+ attribute :disk, :aliases => ['blockDevices','hardDrives']
23
+ attribute :private_ip_address, :aliases => 'primaryBackendIpAddress'
24
+ attribute :public_ip_address, :aliases => 'primaryIpAddress'
25
+ attribute :flavor_id
26
+ attribute :bare_metal, :type => :boolean
27
+ attribute :os_code
28
+ attribute :image_id
29
+ attribute :ephemeral_storage, :aliases => 'localDiskFlag'
30
+ attribute :key_pairs, :aliases => 'sshKeys'
31
+ attribute :network_components
32
+ attribute :fixed_configuration_preset, :aliases => 'fixedConfigurationPreset'
33
+
34
+ # Times
35
+ attribute :created_at, :aliases => ['createDate', 'provisionDate'], :type => :time
36
+ attribute :last_verified_date, :aliases => 'lastVerifiedDate', :type => :time
37
+ attribute :metric_poll_date, :aliases => 'metricPollDate', :type => :time
38
+ attribute :modify_date, :aliases => 'modifyDate', :type => :time
39
+
40
+ # Metadata
41
+ attribute :account_id, :aliases => 'accountId', :type => :integer
42
+ attribute :datacenter, :aliases => 'datacenter'
43
+ attribute :single_tenant, :aliases => 'dedicatedAccountHostOnlyFlag'
44
+ attribute :global_identifier, :aliases => 'globalIdentifier'
45
+ attribute :hourly_billing_flag, :aliases => 'hourlyBillingFlag'
46
+ attribute :tags, :aliases => 'tagReferences'
47
+ attribute :private_network_only, :aliases => 'privateNetworkOnlyFlag'
48
+ attribute :user_data, :aliases => 'userData'
49
+ attribute :uid, :aliases => 'globalIdentifier'
50
+ attribute :provision_script, :aliases => 'postInstallScriptUri'
51
+
52
+ def initialize(attributes = {})
53
+ # Forces every request inject bare_metal parameter
54
+ raise Exception if attributes[:collection].nil? and attributes['bare_metal'].nil?
55
+ super(attributes)
56
+ set_defaults
57
+ end
58
+
59
+ def add_tags(tags)
60
+ requires :id
61
+ raise ArgumentError, "Tags argument for #{self.class.name}##{__method__} must be Array." unless tags.is_a?(Array)
62
+ tags.each do |tag|
63
+ service.tags.new(:resource_id => self.id, :name => tag).save
64
+ end
65
+ self.reload
66
+ true
67
+ end
68
+
69
+ def bare_metal?
70
+ bare_metal
71
+ end
72
+
73
+ def bare_metal
74
+ @bare_metal
75
+ end
76
+
77
+ def datacenter=(name)
78
+ name = name['name'] if name.is_a?(Hash)
79
+ attributes[:datacenter] = { :name => name }
80
+ end
81
+
82
+ def datacenter
83
+ attributes[:datacenter][:name] unless attributes[:datacenter].nil?
84
+ end
85
+
86
+ def delete_tags(tags)
87
+ requires :id
88
+ raise ArgumentError, "Tags argument for #{self.class.name}##{__method__} must be Array." unless tags.is_a?(Array)
89
+ tags.each do |tag|
90
+ service.tags.new(:resource_id => self.id, :name => tag).destroy
91
+ end
92
+ self.reload
93
+ true
94
+ end
95
+
96
+ def destroy
97
+ requires :id
98
+ request = bare_metal? ? :delete_bare_metal_server : :delete_vm
99
+ response = service.send(request, self.id)
100
+ response.body
101
+ end
102
+
103
+ def dns_name
104
+ fqdn
105
+ end
106
+
107
+ def image_id=(uuid)
108
+ attributes[:image_id] = {:globalIdentifier => uuid}
109
+ end
110
+
111
+ def image_id
112
+ attributes[:image_id][:globalIdentifier] unless attributes[:image_id].nil?
113
+ end
114
+
115
+ def name=(set)
116
+ attributes[:hostname] = set
117
+ end
118
+
119
+ def name
120
+ attributes[:hostname]
121
+ end
122
+
123
+ def pre_save
124
+ extract_flavor
125
+ self.bare_metal = true if attributes[:fixed_configuration_preset] and not bare_metal?
126
+ validate_attributes
127
+ if self.vlan
128
+ attributes[:vlan] = { :networkVlan => { :id => self.vlan.id } }
129
+ end
130
+ if self.private_vlan
131
+ attributes[:private_vlan] = { :networkVlan => { :id => self.private_vlan.id } }
132
+ end
133
+ if self.key_pairs
134
+ attributes[:key_pairs].map! { |key| { :id => key.respond_to?(:id) ? key.id : key } }
135
+ end
136
+ if self.network_components
137
+ self.network_components = self.network_components.map do |component|
138
+ component['maxSpeed'] = component.delete('speed') if component['speed']
139
+ component['maxSpeed'] = component.delete('max_speed') if component['max_speed']
140
+ component
141
+ end
142
+ end
143
+
144
+ if attributes[:fixed_configuration_preset].is_a? String
145
+ attributes[:fixedConfigurationPreset] = {:keyName => attributes.delete(:fixed_configuration_preset)}
146
+ end
147
+
148
+ remap_attributes(attributes, attributes_mapping)
149
+ clean_attributes
150
+ end
151
+
152
+ def private_ip # maintain backward compatibility with <0.3.13
153
+ private_ip_address
154
+ end
155
+
156
+ def public_ip # maintain backward compatibility with <0.3.13
157
+ public_ip_address
158
+ end
159
+
160
+ def os_code
161
+ attributes['operatingSystem']['softwareLicense']['softwareDescription']['referenceCode'] if attributes['operatingSystem']
162
+ end
163
+
164
+ def private_vlan
165
+ attributes[:private_vlan] ||= _get_private_vlan
166
+ end
167
+
168
+ def private_vlan=(value)
169
+ unless value.is_a?(Integer) or value.is_a?(Fog::Network::Softlayer::Network)
170
+ raise ArgumentError, "vlan argument for #{self.class.name}##{__method__} must be Integer or Fog::Network::Softlayer::Network."
171
+ end
172
+ value = network_connection.networks.get(value) if value.is_a?(Integer)
173
+ attributes[:private_vlan] = value
174
+ end
175
+
176
+ # reload the OS on a server (method name reload was already taken)
177
+ def relaunch!
178
+ requires :id
179
+ body = [ "FORCE", {}]
180
+ body[1][:sshKeyIds] = key_pairs.map {|kp| kp.id} unless key_pairs.empty?
181
+ type = bare_metal? ? :hardware_server : :virtual_guest
182
+ status = service.request(type, "#{id}/reloadOperatingSystem", :body => body, :http_method => :post).status
183
+ wait_for { not ready? } # block until the relaunch has begun
184
+ [200, 201].include?(status)
185
+ end
186
+
187
+ def key_pairs
188
+ attributes[:key_pairs]
189
+ end
190
+
191
+ def key_pairs=(keys)
192
+ raise ArgumentError, "Argument #{local_variables.first.to_s} for #{self.class.name}##{__method__} must be Array." unless keys.is_a?(Array)
193
+ attributes[:key_pairs] = []
194
+ keys.map do |key|
195
+ ## This was nice but causing an intolerable number of requests on an account with lots of keys.
196
+ ## ToDo: something better...
197
+ #key = self.symbolize_keys(key) if key.is_a?(Hash)
198
+ #unless key.is_a?(Fog::Compute::Softlayer::KeyPair) or (key.is_a?(Hash) and key[:id])
199
+ # raise ArgumentError, "Elements of keys array for #{self.class.name}##{__method__} must be a Hash with key 'id', or Fog::Compute::Softlayer::KeyPair"
200
+ #end
201
+ #key = service.key_pairs.get(key[:id]) unless key.is_a?(Fog::Compute::Softlayer::KeyPair)
202
+ attributes[:key_pairs] << key
203
+ end
204
+ end
205
+
206
+ def vlan
207
+ attributes[:vlan] ||= _get_vlan
208
+ end
209
+
210
+ def vlan=(value)
211
+ unless value.is_a?(Integer) or value.is_a?(Fog::Network::Softlayer::Network)
212
+ raise ArgumentError, "vlan argument for #{self.class.name}##{__method__} must be Integer or Fog::Network::Softlayer::Network."
213
+ end
214
+ value = network_connection.networks.get(value) if value.is_a?(Integer)
215
+ attributes[:vlan] = value
216
+ end
217
+
218
+ def ram=(set)
219
+ if set.is_a?(Array) and set.first['hardwareComponentModel']
220
+ set = 1024 * set.first['hardwareComponentModel']['capacity'].to_i
221
+ end
222
+ attributes[:ram] = set
223
+ end
224
+
225
+ # @params value [String]
226
+ def user_data=(value)
227
+ attributes[:user_data] = [{'value' => value}]
228
+ end
229
+
230
+ def user_data
231
+ attributes[:user_data]
232
+ end
233
+
234
+ def provision_script=(value)
235
+ attributes[:provision_script] = value
236
+ end
237
+
238
+ def provision_script
239
+ attributes[:provision_script]
240
+ end
241
+
242
+ def network_components
243
+ if id
244
+ (public_network_components << private_network_components).flatten
245
+ else
246
+ attributes[:network_components]
247
+ end
248
+ end
249
+
250
+ def public_network_components
251
+ if attributes['frontendNetworkComponents']
252
+ attributes['frontendNetworkComponents'].map { |n| Fog::Compute::Softlayer::NetworkComponent.new(n) }
253
+ else
254
+ []
255
+ end
256
+ end
257
+
258
+ def private_network_components
259
+ if attributes['backendNetworkComponents']
260
+ attributes['backendNetworkComponents'].map { |n| Fog::Compute::Softlayer::NetworkComponent.new(n) }
261
+ else
262
+ []
263
+ end
264
+ end
265
+
266
+ def active_transaction
267
+ if bare_metal?
268
+ service.request(:hardware_server, "#{id}/getActiveTransaction").body
269
+ else
270
+ service.request(:virtual_guest, "#{id}/getActiveTransaction").body
271
+ end
272
+ end
273
+
274
+ def ready?
275
+ begin
276
+ if active_transaction
277
+ false
278
+ elsif bare_metal?
279
+ state == "on"
280
+ else
281
+ state == "Running"
282
+ end
283
+ rescue Excon::Errors::InternalServerError => e
284
+ false
285
+ end
286
+ end
287
+
288
+ def reboot(use_hard_reboot = true)
289
+ # requires :id # TODO: debug why this breaks the tests on bare metal and uncomment this
290
+ if bare_metal?
291
+ service.reboot_bare_metal_server(id, use_hard_reboot)
292
+ else
293
+ service.reboot_vm(id, use_hard_reboot)
294
+ end
295
+ true
296
+ end
297
+
298
+ def ssh_password
299
+ requires :id
300
+ service_path = bare_metal? ? :hardware_server : :virtual_guest
301
+ @sshpass ||= service.request(service_path, id, :query => 'objectMask=mask[id,operatingSystem.passwords[password]]').body
302
+ @sshpass['operatingSystem']['passwords'][0]['password'] unless @sshpass['operatingSystem'].nil? or @sshpass['operatingSystem']['passwords'].empty?
303
+ end
304
+
305
+ def snapshot
306
+ # TODO: implement
307
+ end
308
+
309
+ def start
310
+ # requires :id # TODO: debug why this breaks the tests on bare metal and uncomment this
311
+ if bare_metal?
312
+ service.power_on_bare_metal_server(id)
313
+ else
314
+ service.power_on_vm(id)
315
+ end
316
+ true
317
+ end
318
+
319
+ # Hard power off
320
+ def stop
321
+ # requires :id # TODO: debug why this breaks the tests on bare metal and uncomment this
322
+ if bare_metal?
323
+ service.power_off_bare_metal_server(id)
324
+ else
325
+ service.power_off_vm(id, true)
326
+ end
327
+ true
328
+ end
329
+
330
+ # Soft power off
331
+ def shutdown
332
+ # requires :id # TODO: debug why this breaks the tests on bare metal and uncomment this
333
+ if bare_metal?
334
+ raise Fog::Errors::Error.new('Shutdown not supported on baremetal servers. Use #stop.')
335
+ else
336
+ service.power_off_vm(id, false)
337
+ end
338
+ true
339
+ end
340
+
341
+ def state
342
+ if bare_metal?
343
+ service.request(:hardware_server, "#{id}/getServerPowerState").body
344
+ else
345
+ service.request(:virtual_guest, "#{id}/getPowerState").body['name']
346
+ end
347
+ end
348
+
349
+ # Creates server
350
+ # * requires attributes: :name, :domain, and :flavor_id OR (:cpu_count && :ram && :disks)
351
+ #
352
+ # @note You should use servers.create to create servers instead calling this method directly
353
+ #
354
+ # * State Transitions
355
+ # * BUILD -> ACTIVE
356
+ # * BUILD -> ERROR (on error)
357
+ def save
358
+ raise Fog::Errors::Error.new('Resaving an existing object may create a duplicate') if persisted?
359
+ copy = self.dup
360
+ copy.pre_save
361
+
362
+ data = if copy.bare_metal?
363
+ service.create_bare_metal_server(copy.attributes).body
364
+ else
365
+ service.create_vm(copy.attributes).body.first
366
+ end
367
+
368
+ data.delete("bare_metal")
369
+ merge_attributes(data)
370
+ true
371
+ end
372
+
373
+ def tags
374
+ attributes[:tags].map { |i| i['tag']['name'] if i['tag'] }.compact if attributes[:tags]
375
+ end
376
+
377
+ def get_active_tickets
378
+ return service.get_bare_metal_active_tickets(id).body if bare_metal?
379
+ service.get_virtual_guest_active_tickets(id).body
380
+ end
381
+
382
+ def get_users
383
+ return service.get_bare_metal_users(id).body if bare_metal?
384
+ service.get_virtual_guest_users(id).body
385
+ end
386
+
387
+ def get_upgrade_options
388
+ return service.get_bare_metal_upgrade_item_prices(id).body if bare_metal?
389
+ service.get_virtual_guest_upgrade_item_prices(id).body
390
+ end
391
+
392
+ def update(update_attributes)
393
+ raise ArgumentError if update_attributes.nil?
394
+ product_connection
395
+ prices = get_item_prices_id(update_attributes)
396
+ order = generate_upgrade_order(prices, update_attributes[:time] || update_attributes[:maintenance_window])
397
+ @product_conn.place_order(order).body
398
+ end
399
+
400
+ def generate_order_template
401
+ copy = self.dup
402
+ copy.pre_save
403
+ return service.generate_bare_metal_order_template(copy.attributes).body if copy.bare_metal?
404
+ service.generate_virtual_guest_order_template(copy.attributes).body
405
+ end
406
+
407
+ def wait_for_id(timeout=14400, delay=30)
408
+ # Cannot use self.wait_for because it calls reload which requires
409
+ # self.id which is not initially available for bare metal.
410
+ filterStr = Fog::JSON.encode({
411
+ "hardware" => {
412
+ "hostname" => {
413
+ "operation" => self.name,
414
+ },
415
+ "domain" => {
416
+ "operation" => self.domain,
417
+ },
418
+ "globalIdentifier" => {
419
+ "operation" => self.uid,
420
+ },
421
+ }
422
+ })
423
+
424
+ Fog.wait_for(timeout, delay) do
425
+ res = service.request(:account, 'getHardware', :query => {
426
+ :objectMask => 'mask[id,fullyQualifiedDomainName,provisionDate,hardwareStatus,lastTransaction[elapsedSeconds,transactionStatus[friendlyName]],operatingSystem[id,passwords[password,username]]]',
427
+ :objectFilter => filterStr,
428
+ })
429
+
430
+ server = res.body.first
431
+
432
+ yield server if block_given?
433
+
434
+ if server and server["provisionDate"]
435
+ attributes[:id] = server['id']
436
+ true
437
+ else
438
+ false
439
+ end
440
+ end
441
+
442
+ self.reload
443
+ true
444
+ end
445
+
446
+ private
447
+
448
+ def network_connection
449
+ @network_conn ||= Fog::Network.new(
450
+ :provider => :softlayer,
451
+ :softlayer_username => service.instance_variable_get(:@softlayer_username),
452
+ :softlayer_api_key => service.instance_variable_get(:@softlayer_api_key)
453
+ )
454
+ end
455
+
456
+ def product_connection
457
+ if Fog.mock?
458
+ @product_conn = Fog::Softlayer::Product.new(
459
+ :provider => :softlayer,
460
+ :softlayer_username => service.instance_variable_get(:@credentials)[:username],
461
+ :softlayer_api_key => service.instance_variable_get(:@credentials)[:api_key]
462
+ )
463
+ end
464
+ @product_conn ||= Fog::Softlayer::Product.new(
465
+ :provider => :softlayer,
466
+ :softlayer_username => service.instance_variable_get(:@softlayer_username),
467
+ :softlayer_api_key => service.instance_variable_get(:@softlayer_api_key)
468
+ )
469
+ end
470
+
471
+ def _get_private_vlan
472
+ if self.id
473
+ vlan_id = if bare_metal?
474
+ service.request(:hardware_server, "#{self.id}/get_private_vlan").body['id']
475
+ else
476
+ service.request(:virtual_guest, self.id, :query => 'objectMask=primaryBackendNetworkComponent.networkVlan').body['primaryBackendNetworkComponent']['networkVlan']['id']
477
+ end
478
+ network_connection.networks.get(vlan_id)
479
+ end
480
+ end
481
+
482
+ def _get_vlan
483
+ if self.id
484
+ vlan_id = if bare_metal?
485
+ service.request(:hardware_server, "#{self.id}/get_public_vlan").body['id']
486
+ else
487
+ service.request(:virtual_guest, self.id, :query => 'objectMask=primaryNetworkComponent.networkVlan').body['primaryNetworkComponent']['networkVlan']['id']
488
+ end
489
+ network_connection.networks.get(vlan_id)
490
+ end
491
+ end
492
+
493
+ ##
494
+ # Generate mapping for use with remap_attributes
495
+ def attributes_mapping
496
+ common = {
497
+ :hourly_billing_flag => :hourlyBillingFlag,
498
+ :os_code => :operatingSystemReferenceCode,
499
+ :vlan => :primaryNetworkComponent,
500
+ :private_vlan => :primaryBackendNetworkComponent,
501
+ :key_pairs => :sshKeys,
502
+ :private_network_only => :privateNetworkOnlyFlag,
503
+ :user_data => :userData,
504
+ :provision_script => :postInstallScriptUri,
505
+ :network_components => :networkComponents,
506
+ }
507
+
508
+ conditional = if bare_metal?
509
+ {
510
+ :cpu => :processorCoreAmount,
511
+ :ram => :memoryCapacity,
512
+ :disk => :hardDrives,
513
+ :bare_metal => :bareMetalInstanceFlag,
514
+ :fixed_configuration_preset => :fixedConfigurationPreset,
515
+ }
516
+ else
517
+ {
518
+ :cpu => :startCpus,
519
+ :ram => :maxMemory,
520
+ :disk => :blockDevices,
521
+ :image_id => :blockDeviceTemplateGroup,
522
+ :ephemeral_storage => :localDiskFlag,
523
+ }
524
+ end
525
+ common.merge(conditional)
526
+ end
527
+
528
+ def bare_metal=(set)
529
+ return @bare_metal if set == @bare_metal
530
+ raise Exception, "Bare metal flag has already been set" unless @bare_metal.nil?
531
+ @bare_metal = case set
532
+ when false, 'false', 0, nil, ''
533
+ attributes[:bare_metal] = false
534
+ else
535
+ attributes[:bare_metal] = true
536
+ end
537
+ end
538
+
539
+ ##
540
+ # Remove model attributes that aren't expected by the SoftLayer API
541
+ def clean_attributes
542
+ attributes.delete(:bare_metal)
543
+ attributes.delete(:flavor_id)
544
+ attributes.delete(:ephemeral_storage)
545
+ attributes.delete(:tags) if bare_metal?
546
+ end
547
+
548
+ ##
549
+ # Expand a "flavor" into cpu, ram, and disk attributes
550
+ def extract_flavor
551
+ if attributes[:flavor_id]
552
+ flavor = @service.flavors.get(attributes[:flavor_id])
553
+ flavor.nil? and Fog::Errors::Error.new("Unrecognized flavor in #{self.class}##{__method__}")
554
+ attributes[:cpu] = flavor.cpu
555
+ attributes[:ram] = flavor.ram
556
+ attributes[:disk] = flavor.disk unless attributes[:image_id]
557
+ if bare_metal?
558
+ value = flavor.disk.first['diskImage']['capacity'] < 500 ? 250 : 500
559
+ attributes[:disk] = [{'capacity'=>value}]
560
+ attributes[:ram] = attributes[:ram] / 1024 if attributes[:ram] > 64
561
+ end
562
+ end
563
+ end
564
+
565
+ def validate_attributes
566
+ requires :name, :domain, :datacenter
567
+ if attributes[:fixed_configuration_preset]
568
+ requires :os_code
569
+ else
570
+ requires :cpu, :ram
571
+ requires_one :os_code, :image_id
572
+ requires_one :disk, :image_id
573
+ end
574
+ bare_metal? and image_id and raise ArgumentError, "Bare Metal Cloud does not support booting from Image"
575
+ end
576
+
577
+ def set_defaults
578
+ attributes[:hourly_billing_flag] = true if attributes[:hourly_billing_flag].nil?
579
+ attributes[:ephemeral_storage] = false if attributes[:ephemeral_storage].nil?
580
+ attributes[:domain] = service.softlayer_default_domain if service.softlayer_default_domain and attributes[:domain].nil?
581
+ self.datacenter = service.softlayer_default_datacenter if service.softlayer_default_datacenter and attributes[:datacenter].nil?
582
+ end
583
+
584
+ def get_item_prices_id_by_value(item_price_array, category, value)
585
+ item_prices = item_price_array.select { |item_price| item_price["categories"].find { |category_hash| category_hash["categoryCode"] == category } }
586
+ item_price = item_prices.find { |item_price| item_price['item']['capacity'] == value.to_s }
587
+ item_price.nil? ? "" : item_price["id"]
588
+ end
589
+
590
+ def get_item_prices_id(update_attributes)
591
+ item_price_array = get_upgrade_options
592
+ update_attributes.delete(:time)
593
+ update_attributes.delete(:maintenance_window)
594
+ update_attributes.map { |key, value| { :id => get_item_prices_id_by_value(item_price_array, key.to_s, value) } }
595
+ end
596
+
597
+ def bm_upgrade_order_template(value)
598
+ {
599
+ :complexType => 'SoftLayer_Container_Product_Order_Hardware_Server_Upgrade',
600
+ :hardware => [
601
+ {
602
+ :id => id
603
+ }
604
+ ],
605
+ :properties => [
606
+ {
607
+ :name => 'MAINTENANCE_WINDOW_ID',
608
+ :value => value
609
+ }
610
+ ]
611
+ }
612
+ end
613
+
614
+ def vm_upgrade_order_template(time)
615
+ {
616
+ :complexType => 'SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade',
617
+ :virtualGuests => [
618
+ {
619
+ :id => id
620
+ }
621
+ ],
622
+ :properties => [
623
+ {
624
+ :name => 'MAINTENANCE_WINDOW',
625
+ :value => (time.nil? || time.empty?) ? Time.now.iso8601 : time.iso8601
626
+ }
627
+ ]
628
+ }
629
+ end
630
+
631
+ def generate_upgrade_order(prices, value)
632
+ return bm_upgrade_order_template(value).merge({ :prices => prices }) if bare_metal?
633
+ vm_upgrade_order_template(value).merge({ :prices => prices })
634
+ end
635
+ end
636
+ end
637
+ end
638
+ end