fog-softlayer 1.1.2 → 1.1.3

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 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