fog-softlayer 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,628 +1,628 @@
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.id } }
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.id } }
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