Class: WZCloud::Nova

Inherits:
Object
  • Object
show all
Defined in:
lib/wzcloud/nova.rb

Defined Under Namespace

Classes: ErrorHandler, InstanceError

Constant Summary collapse

KEYPAIR_PREFIX =
'os-keypairs'.freeze
ERROR_STATUS =
'ERROR'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(access, transport, custom_headers = {}) ⇒ Nova

Returns a new instance of Nova.



14
15
16
17
18
19
20
21
22
# File 'lib/wzcloud/nova.rb', line 14

def initialize(access, transport, custom_headers = {})
  @access = access
  @transport = transport
  @base_url = @access.endpoints('nova')
  @transport.middleware.unshift(ErrorHandler)
  @default_headers = {'X-Auth-Token' => @access.token,
                      'Content-Type' => 'application/json',
                      'Accept' => 'application/json'}.merge(custom_headers)
end

Instance Attribute Details

#default_headersObject (readonly)

Returns the value of attribute default_headers.



12
13
14
# File 'lib/wzcloud/nova.rb', line 12

def default_headers
  @default_headers
end

#transportObject (readonly)

Returns the value of attribute transport.



12
13
14
# File 'lib/wzcloud/nova.rb', line 12

def transport
  @transport
end

Instance Method Details

#attach_interface(server_id, network_id) ⇒ Object



177
178
179
180
181
182
183
184
185
# File 'lib/wzcloud/nova.rb', line 177

def attach_interface(server_id, network_id)
  wait_for_servers

  make_request(
    :post,
    "servers/#{server_id}/os-interface",
    interfaceAttachment: {net_id: network_id}
  ).body[:interfaceAttachment]
end

#attach_volume(server_id, volume_id) ⇒ Object



330
331
332
333
334
335
336
337
# File 'lib/wzcloud/nova.rb', line 330

def attach_volume(server_id, volume_id)
  data = {volumeAttachment: {volumeId: volume_id}}
  make_request(
    :post,
    "servers/#{server_id}/os-volume_attachments",
    data
  ).body[:volumeAttachment]
end

#clear_db(servers = get_servers) ⇒ Object



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/wzcloud/nova.rb', line 397

def clear_db(servers = get_servers)
  servers.each do |server|
    begin
      delete_server(server[:id])
    rescue Transport::RequestError => e
      # that happens sometimes
      raise e unless e.status == 404
    end
  end

  get_snapshots.each do |snap|
    begin
      delete_image(snap[:id])
    rescue Transport::RequestError => e
      # that happens too
      raise e unless e.status == 404
    end
  end

  # If we don't wait until all instances are terminated,
  # the next cleanup action is likely to remove the current tenant,
  # and then the remaining instances won't get deleted automatically.
  while servers.any?
    servers.each do |server|
      begin
        get_server(server[:id])
      rescue Transport::NotFoundError
        servers.delete(server)
      end
    end

    sleep 0.1
  end

  get_keypairs.each do |keypair|
    delete_keypair keypair[:keypair][:name]
  end
end

#confirm_resize_server(server_uuid) ⇒ Object

Raises:



113
114
115
116
117
# File 'lib/wzcloud/nova.rb', line 113

def confirm_resize_server(server_uuid)
  server = get_server(server_uuid)
  raise InstanceError if server[:status] != 'VERIFY_RESIZE'
  make_request :post, "servers/#{server_uuid}/action", confirmResize: nil
end

#create_keypair(name, public_key = nil) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/wzcloud/nova.rb', line 203

def create_keypair(name, public_key = nil)
  body = {keypair: {name: name}}
  body[:keypair][:public_key] = public_key if public_key
  begin
    make_request(:post, KEYPAIR_PREFIX, body).body[:keypair]
  rescue Transport::BadRequestError => e
    # Can't reproduce message
    if e.message['malformed or otherwise incorrect'] || e.message['Keypair data is invalid:']
      raise ArgumentError, 'Invalid public key'
    end
    raise e
  end
end

#create_server(name, flavor_ref, image_ref, network_id = nil, av_zone = nil, key_name = nil, password = nil, server_group = nil, count = 1, return_reservation_id = false) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/wzcloud/nova.rb', line 71

def create_server(name, flavor_ref, image_ref, network_id = nil,
                  av_zone = nil, key_name = nil, password = nil,
                  server_group = nil, count = 1, return_reservation_id = false)
  options = {server: {name: name, flavorRef: flavor_ref, imageRef: image_ref}}

  options['os:scheduler_hints'] = {group: server_group} if server_group
  options[:server][:availability_zone] = av_zone if av_zone
  options[:server][:key_name] = key_name if key_name
  options[:server][:networks] = Array(network_id).map { |nid| {uuid: nid} } if network_id
  options[:server][:min_count] = count if count.to_i > 1
  options[:server][:return_reservation_id] = 'True' if return_reservation_id
  unless password.to_s.empty?
    user_data = generate_user_data(password)
    options[:server][:user_data] = Base64.encode64(user_data)
  end

  result = make_request(:post, 'servers', options)

  return_reservation_id ? result.body : result.body[:server]
end

#create_server_group(name, policy) ⇒ Object



149
150
151
152
# File 'lib/wzcloud/nova.rb', line 149

def create_server_group(name, policy)
  options = {name: name, policies: [policy]}
  make_request(:post, 'os-server-groups', server_group: options).body[:server_group]
end

#create_snapshot(instance_id, options = {}) ⇒ Object



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/wzcloud/nova.rb', line 246

def create_snapshot(instance_id, options = {})
  name = options[:name] || "#{instance_id}-snap-#{Time.new.to_i}"
   = options[:metadata] || {}
  begin
    r = make_request(
      :post,
      "servers/#{instance_id}/action",
      createImage: {name: name, metadata: }
    )
    {id: r.headers['Location'][%r{\/([-a-f0-9]+)\z}, 1]}
  rescue Transport::RequestError => e
    case e
    when Transport::NotFoundError
      raise ArgumentError, "Instance #{instance_id} not found"
    when Transport::ConflictError
      raise ArgumentError, "Instance #{instance_id} is in conflicting state"
    else
      raise e
    end
  end
end

#delete_image(id) ⇒ Object



268
269
270
# File 'lib/wzcloud/nova.rb', line 268

def delete_image(id)
  make_request(:delete, "images/#{id}")
end

#delete_keypair(name) ⇒ Object



217
218
219
# File 'lib/wzcloud/nova.rb', line 217

def delete_keypair(name)
  make_request :delete, "#{KEYPAIR_PREFIX}/#{name}"
end

#delete_server(uuid) ⇒ Object



102
103
104
# File 'lib/wzcloud/nova.rb', line 102

def delete_server(uuid)
  make_request(:delete, "servers/#{uuid}")
end

#delete_server_group(server_group_uuid) ⇒ Object



158
159
160
# File 'lib/wzcloud/nova.rb', line 158

def delete_server_group(server_group_uuid)
  make_request(:delete, "os-server-groups/#{server_group_uuid}")
end

#detach_interface(server_id, network_id, neutron) ⇒ Object



187
188
189
190
191
192
# File 'lib/wzcloud/nova.rb', line 187

def detach_interface(server_id, network_id, neutron)
  ifaces = make_request(:get, "servers/#{server_id}/os-interface").body[:interfaceAttachments]
  iface  = ifaces.find { |i| i[:net_id] == network_id }
  raise "Interface for network #{network_id} not found on #{server_id}" unless iface
  neutron.delete_port(iface[:port_id])
end

#detach_volume(server_id, attachment_id) ⇒ Object



351
352
353
# File 'lib/wzcloud/nova.rb', line 351

def detach_volume(server_id, attachment_id)
  make_request(:delete, "servers/#{server_id}/os-volume_attachments/#{attachment_id}")
end

#fetch_internal_and_external_ips(server_details) ⇒ Object



283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/wzcloud/nova.rb', line 283

def fetch_internal_and_external_ips(server_details)
  internal_ip = nil
  external_ip = nil
  server_details[:addresses].each do |addr_name, ips|
    next unless addr_name['network-for']
    ips.each do |ip|
      case ip[:"OS-EXT-IPS:type"]
      when 'fixed' then internal_ip = ip[:addr]
      when 'floating' then external_ip = ip[:addr]
      end
    end
  end
  {internal_ip: internal_ip, external_ip: external_ip}
end

#fetch_local_ip(server_details) ⇒ Object



272
273
274
275
276
277
278
279
280
281
# File 'lib/wzcloud/nova.rb', line 272

def fetch_local_ip(server_details)
  local_ip = nil
  server_details[:addresses].each do |addr_name, ips|
    next unless addr_name['local-network']
    ips.each do |ip|
      local_ip = ip[:addr] if ip[:"OS-EXT-IPS:type"] == 'fixed'
    end
  end
  local_ip
end

#get_action_detail(server_uuid, request_id) ⇒ Object



172
173
174
175
# File 'lib/wzcloud/nova.rb', line 172

def get_action_detail(server_uuid, request_id)
  make_request(:get, "/servers/#{server_uuid}/os-instance-actions/#{request_id}")
    .body[:instanceAction]
end

#get_actions(server_uuid) ⇒ Object



167
168
169
170
# File 'lib/wzcloud/nova.rb', line 167

def get_actions(server_uuid)
  make_request(:get, "/servers/#{server_uuid}/os-instance-actions")
    .body[:instanceActions]
end

#get_av_zonesObject



227
228
229
230
# File 'lib/wzcloud/nova.rb', line 227

def get_av_zones
  info = make_request(:get, 'os-availability-zone').body[:availabilityZoneInfo]
  info.delete_if { |zone| !zone[:zoneState][:available] }.map { |zone| zone[:zoneName] }
end

#get_console_output(instance_id) ⇒ Object



316
317
318
319
320
# File 'lib/wzcloud/nova.rb', line 316

def get_console_output(instance_id)
  make_request(:post, "servers/#{instance_id}/action",
               :"os-getConsoleOutput" => {length: nil})
    .body[:output]
end

#get_flavor(id) ⇒ Object



31
32
33
# File 'lib/wzcloud/nova.rb', line 31

def get_flavor(id)
  make_request(:get, "flavors/#{id}").body[:flavor]
end

#get_flavor_access(flavor_id, options = {}) ⇒ Object



45
46
47
48
49
# File 'lib/wzcloud/nova.rb', line 45

def get_flavor_access(flavor_id, options = {})
  make_request(:get, "flavors/#{flavor_id}/os-flavor-access", nil, options).body[:flavor_access]
rescue WZCloud::Transport::NotFoundError => _e
  []
end

#get_flavor_extra_specs(flavor_id) ⇒ Object



41
42
43
# File 'lib/wzcloud/nova.rb', line 41

def get_flavor_extra_specs(flavor_id)
  make_request(:get, "flavors/#{flavor_id}/os-extra_specs", nil, {}).body[:extra_specs]
end

#get_flavors(is_public = true) ⇒ Object



24
25
26
27
28
29
# File 'lib/wzcloud/nova.rb', line 24

def get_flavors(is_public = true)
  params = {}
  params[:is_public] = false unless is_public
  make_request(:get, 'flavors', nil, params)
    .body[:flavors].map(&OpenStruct.method(:new))
end

#get_flavors_detail(is_public = true) ⇒ Object



35
36
37
38
39
# File 'lib/wzcloud/nova.rb', line 35

def get_flavors_detail(is_public = true)
  params = {}
  params[:is_public] = false unless is_public
  sort_flavors(make_request(:get, 'flavors/detail', nil, params).body[:flavors])
end

#get_hostsObject



221
222
223
224
225
# File 'lib/wzcloud/nova.rb', line 221

def get_hosts
  hosts = make_request(:get, 'os-hosts').body[:hosts]
  # Filtering via query parameter doesn't work.
  hosts.select { |host| host[:service] == 'compute' }
end

#get_imagesObject



51
52
53
# File 'lib/wzcloud/nova.rb', line 51

def get_images
  make_request(:get, 'images').body[:images].map(&OpenStruct.method(:new))
end

#get_images_detail(options = {}) ⇒ Object



55
56
57
# File 'lib/wzcloud/nova.rb', line 55

def get_images_detail(options = {})
  make_request(:get, 'images/detail', nil, options).body[:images]
end

#get_keypair_by_fingerprint(fp) ⇒ Object



198
199
200
201
# File 'lib/wzcloud/nova.rb', line 198

def get_keypair_by_fingerprint(fp)
  get_keypairs.find { |kp| kp[:keypair][:fingerprint] == fp } ||
    raise(ArgumentError, "Keypair with fingerprint #{fp} not found")
end

#get_keypairsObject



194
195
196
# File 'lib/wzcloud/nova.rb', line 194

def get_keypairs
  make_request(:get, KEYPAIR_PREFIX).body[:keypairs]
end

#get_server(id) ⇒ Object



63
64
65
# File 'lib/wzcloud/nova.rb', line 63

def get_server(id)
  make_request(:get, "servers/#{id}").body[:server]
end

#get_server_group(server_group_uuid) ⇒ Object



154
155
156
# File 'lib/wzcloud/nova.rb', line 154

def get_server_group(server_group_uuid)
  make_request(:get, "os-server-groups/#{server_group_uuid}").body[:server_group]
end

#get_server_groups(options = {}) ⇒ Object



145
146
147
# File 'lib/wzcloud/nova.rb', line 145

def get_server_groups(options = {})
  make_request(:get, 'os-server-groups', options).body[:server_groups]
end

#get_servers(options = {}) ⇒ Object



67
68
69
# File 'lib/wzcloud/nova.rb', line 67

def get_servers(options = {})
  make_request(:get, 'servers', nil, options).body[:servers]
end

#get_servers_detail(options = {}) ⇒ Object



59
60
61
# File 'lib/wzcloud/nova.rb', line 59

def get_servers_detail(options = {})
  make_request(:get, 'servers/detail', nil, options).body[:servers]
end

#get_snapshots(instance_id = nil) ⇒ Object



232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/wzcloud/nova.rb', line 232

def get_snapshots(instance_id = nil)
  params = if instance_id
             {server: "#{@base_url}/servers/#{instance_id}"}
           else
             {}
           end
  images = get_images_detail(params)
  if instance_id
    images
  else
    images.select { |i| i[:server] }
  end
end

#get_volumes(server_id) ⇒ Object



326
327
328
# File 'lib/wzcloud/nova.rb', line 326

def get_volumes(server_id)
  make_request(:get, "servers/#{server_id}/os-volume_attachments").body[:volumeAttachments]
end

#lock(server_uuid) ⇒ Object



355
356
357
# File 'lib/wzcloud/nova.rb', line 355

def lock(server_uuid)
  make_request :post, "servers/#{server_uuid}/action", lock: nil
end

#make_request(method, url_path, body = nil, params = nil) ⇒ Object



436
437
438
439
440
441
# File 'lib/wzcloud/nova.rb', line 436

def make_request(method, url_path, body = nil, params = nil)
  json_body = MultiJson.dump(body) if body

  @transport.http_call("#{@base_url}/#{url_path}", method,
                       default_headers, json_body, params)
end

#migrate_server(instance_id) ⇒ Object



322
323
324
# File 'lib/wzcloud/nova.rb', line 322

def migrate_server(instance_id)
  make_request(:post, "servers/#{instance_id}/action", migrate: nil)
end

#quotas(tenant_id) ⇒ Object



298
299
300
# File 'lib/wzcloud/nova.rb', line 298

def quotas(tenant_id)
  make_request(:get, "os-quota-sets/#{tenant_id}").body[:quota_set]
end

#reboot_server(server_uuid, type = 'SOFT') ⇒ Object



135
136
137
138
# File 'lib/wzcloud/nova.rb', line 135

def reboot_server(server_uuid, type = 'SOFT')
  data = {reboot: {type: type}}
  make_request :post, "servers/#{server_uuid}/action", data
end

#rebuild_server(server_uuid, image_ref, name = nil) ⇒ Object



96
97
98
99
100
# File 'lib/wzcloud/nova.rb', line 96

def rebuild_server(server_uuid, image_ref, name = nil)
  opts = {imageRef: image_ref}
  opts[:name] = name if name
  make_request(:post, "servers/#{server_uuid}/action", rebuild: opts).body[:server]
end

#refresh_network(server_id) ⇒ Object

Requires admin privileges by default, but that can be changed via compute_extension:os-server-external-events:create policy.



309
310
311
312
313
314
# File 'lib/wzcloud/nova.rb', line 309

def refresh_network(server_id)
  data = {events: [{name: 'network-changed', server_uuid: server_id}]}
  make_request(:post, 'os-server-external-events', data)
rescue WZCloud::Transport::NotFoundError
  # Network not available yet? Nothing to reset, then.
end

#rename_server(server_uuid, new_name) ⇒ Object



92
93
94
# File 'lib/wzcloud/nova.rb', line 92

def rename_server(server_uuid, new_name)
  make_request(:put, "servers/#{server_uuid}", server: {name: new_name}).body[:server]
end

#rescue_server(server_uuid, admin_password = nil, rescue_image_ref = nil) ⇒ Object



339
340
341
342
343
344
# File 'lib/wzcloud/nova.rb', line 339

def rescue_server(server_uuid, admin_password = nil, rescue_image_ref = nil)
  data = {rescue: {}}
  data[:rescue][:adminPass] = admin_password if admin_password
  data[:rescue][:rescue_image_ref] = rescue_image_ref if rescue_image_ref
  make_request(:post, "servers/#{server_uuid}/action", data).body
end

#reset_server(server_uuid, type = 'error') ⇒ Object



140
141
142
143
# File 'lib/wzcloud/nova.rb', line 140

def reset_server(server_uuid, type = 'error')
  data = {'os-resetState' => {state: type}}
  make_request :post, "servers/#{server_uuid}/action", data
end

#resize_server(server_uuid, flavor_ref) ⇒ Object

Raises:



106
107
108
109
110
111
# File 'lib/wzcloud/nova.rb', line 106

def resize_server(server_uuid, flavor_ref)
  server = get_server(server_uuid)
  raise InstanceError if server[:status] != 'ACTIVE'
  data = {resize: {flavorRef: flavor_ref}}
  make_request :post, "servers/#{server_uuid}/action", data
end

#revert_resize_server(server_uuid) ⇒ Object

Raises:



119
120
121
122
123
# File 'lib/wzcloud/nova.rb', line 119

def revert_resize_server(server_uuid)
  server = get_server(server_uuid)
  raise InstanceError if server[:status] != 'VERIFY_RESIZE' # XXX Maybe delete all this checks?
  make_request :post, "servers/#{server_uuid}/action", revertResize: nil
end

#show_attachment(server_id, attachment_id) ⇒ Object



363
364
365
366
367
368
# File 'lib/wzcloud/nova.rb', line 363

def show_attachment(server_id, attachment_id)
  make_request(
    :get,
    "servers/#{server_id}/os-volume_attachments/#{attachment_id}"
  ).body[:volumeAttachment]
end

#start_server(server_uuid) ⇒ Object



125
126
127
# File 'lib/wzcloud/nova.rb', line 125

def start_server(server_uuid)
  make_request :post, "servers/#{server_uuid}/action", 'os-start' => nil
end

#stop_server(server_uuid) ⇒ Object



129
130
131
132
133
# File 'lib/wzcloud/nova.rb', line 129

def stop_server(server_uuid)
  make_request :post, "servers/#{server_uuid}/action", 'os-stop' => nil
rescue Transport::ConflictError => e
  raise unless e.message['vm_state stopped']
end

#unlock(server_uuid) ⇒ Object



359
360
361
# File 'lib/wzcloud/nova.rb', line 359

def unlock(server_uuid)
  make_request :post, "servers/#{server_uuid}/action", unlock: nil
end

#unrescue_server(server_uuid) ⇒ Object



346
347
348
349
# File 'lib/wzcloud/nova.rb', line 346

def unrescue_server(server_uuid)
  data = {unrescue: nil}
  make_request(:post, "servers/#{server_uuid}/action", data).body
end

#update_quotas(tenant_id, new_quotas) ⇒ Object



302
303
304
305
# File 'lib/wzcloud/nova.rb', line 302

def update_quotas(tenant_id, new_quotas)
  data = {quota_set: new_quotas}
  make_request(:put, "os-quota-sets/#{tenant_id}", data).body[:quota_set]
end

#vnc_url(server_uuid) ⇒ Object



162
163
164
165
# File 'lib/wzcloud/nova.rb', line 162

def vnc_url(server_uuid)
  data = {:'os-getVNCConsole' => {type: 'novnc'}}
  make_request(:post, "servers/#{server_uuid}/action", data).body
end

#wait_for_serversObject



370
371
372
373
374
375
376
377
# File 'lib/wzcloud/nova.rb', line 370

def wait_for_servers
  loop do
    servers = get_servers_detail
    break if !servers.any? { |serv| %w(BUILD RESIZE).include? serv[:status] } &&
             !servers.any? { |serv| ['deleting'].include? serv[:"OS-EXT-STS:task_state"] }
    sleep 1
  end
end

#wait_for_snapshotsObject



389
390
391
392
393
394
395
# File 'lib/wzcloud/nova.rb', line 389

def wait_for_snapshots
  loop do
    images = get_images_detail
    break unless images.any? { |image| ['SAVING'].include? image[:status] }
    sleep 1
  end
end

#wait_status(server, status, seconds = 120) ⇒ Object

Raises:



379
380
381
382
383
384
385
386
387
# File 'lib/wzcloud/nova.rb', line 379

def wait_status(server, status, seconds = 120)
  server_status = get_server(server[:id])[:status]
  seconds.times do
    server_status = get_server(server[:id])[:status]
    break if server_status == status || server_status == ERROR_STATUS
    sleep 1
  end
  raise InstanceError, server_status if server_status != status
end