vagrant-openstack-provider 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +13 -5
- data/.rubocop.yml +28 -0
- data/Appraisals +3 -3
- data/CHANGELOG.md +4 -0
- data/Gemfile +3 -0
- data/Rakefile +6 -2
- data/Vagrantfile +4 -15
- data/gemfiles/latest_stable.gemfile +2 -0
- data/gemfiles/oldest_current.gemfile +2 -0
- data/gemfiles/previous_release.gemfile +2 -0
- data/lib/vagrant-openstack-provider.rb +18 -13
- data/lib/vagrant-openstack-provider/action.rb +112 -46
- data/lib/vagrant-openstack-provider/action/connect_openstack.rb +9 -10
- data/lib/vagrant-openstack-provider/action/create_server.rb +86 -57
- data/lib/vagrant-openstack-provider/action/delete_server.rb +5 -6
- data/lib/vagrant-openstack-provider/action/{is_created.rb → message.rb} +4 -3
- data/lib/vagrant-openstack-provider/action/read_ssh_info.rb +7 -27
- data/lib/vagrant-openstack-provider/action/read_state.rb +7 -9
- data/lib/vagrant-openstack-provider/action/resume.rb +20 -0
- data/lib/vagrant-openstack-provider/action/start_server.rb +22 -0
- data/lib/vagrant-openstack-provider/action/stop_server.rb +22 -0
- data/lib/vagrant-openstack-provider/action/suspend.rb +20 -0
- data/lib/vagrant-openstack-provider/action/sync_folders.rb +27 -38
- data/lib/vagrant-openstack-provider/action/wait_stop.rb +29 -0
- data/lib/vagrant-openstack-provider/client/keystone.rb +76 -0
- data/lib/vagrant-openstack-provider/client/neutron.rb +32 -0
- data/lib/vagrant-openstack-provider/client/nova.rb +166 -0
- data/lib/vagrant-openstack-provider/client/openstack.rb +41 -0
- data/lib/vagrant-openstack-provider/client/utils.rb +38 -0
- data/lib/vagrant-openstack-provider/config.rb +38 -110
- data/lib/vagrant-openstack-provider/errors.rb +7 -3
- data/lib/vagrant-openstack-provider/plugin.rb +8 -8
- data/lib/vagrant-openstack-provider/provider.rb +6 -6
- data/lib/vagrant-openstack-provider/version.rb +1 -1
- data/locales/en.yml +83 -5
- data/numergyrc +22 -0
- data/spec/vagrant-openstack-provider/action/create_server_spec.rb +89 -0
- data/spec/vagrant-openstack-provider/client/keystone_spec.rb +140 -0
- data/spec/vagrant-openstack-provider/client/neutron_spec.rb +53 -0
- data/spec/vagrant-openstack-provider/client/nova_spec.rb +373 -0
- data/spec/vagrant-openstack-provider/client/utils_spec.rb +125 -0
- data/spec/vagrant-openstack-provider/config_spec.rb +117 -0
- data/spec/vagrant-openstack-provider/provider_spec.rb +13 -0
- data/spec/vagrant-openstack-provider/spec_helper.rb +23 -0
- data/vagrant-openstack-provider.gemspec +13 -14
- metadata +40 -30
- data/features/provision.feature +0 -35
- data/features/steps/sdk_steps.rb +0 -13
- data/features/steps/server_steps.rb +0 -25
- data/features/support/env.rb +0 -37
- data/features/support/fog_mock.rb +0 -19
- data/features/vagrant-openstack-provider.feature +0 -70
- data/lib/vagrant-openstack-provider/action/message_already_created.rb +0 -16
- data/lib/vagrant-openstack-provider/action/message_not_created.rb +0 -16
- data/lib/vagrant-openstack-provider/openstack_client.rb +0 -98
- data/spec/vagrant-openstack/config_spec.rb +0 -184
- data/stackrc +0 -31
@@ -0,0 +1,20 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module Openstack
|
3
|
+
module Action
|
4
|
+
class Suspend
|
5
|
+
def initialize(app, _env)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
if env[:machine].id
|
11
|
+
env[:ui].info I18n.t('vagrant.actions.vm.suspend.suspending')
|
12
|
+
env[:openstack_client].nova.suspend_server(env, env[:machine].id)
|
13
|
+
end
|
14
|
+
|
15
|
+
@app.call(env)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,46 +1,44 @@
|
|
1
|
-
require
|
1
|
+
require 'log4r'
|
2
2
|
require 'rbconfig'
|
3
|
-
require
|
3
|
+
require 'vagrant/util/subprocess'
|
4
4
|
|
5
5
|
module VagrantPlugins
|
6
6
|
module Openstack
|
7
7
|
module Action
|
8
|
-
|
9
8
|
class SyncFolders
|
10
|
-
def initialize(app,
|
9
|
+
def initialize(app, _env)
|
11
10
|
@app = app
|
12
11
|
end
|
13
12
|
|
14
13
|
def call(env)
|
15
14
|
sync_method = env[:machine].provider_config.sync_method
|
16
|
-
if sync_method ==
|
15
|
+
if sync_method == 'rsync'
|
17
16
|
RsyncFolders.new(@app, env).call(env)
|
18
|
-
elsif sync_method ==
|
17
|
+
elsif sync_method == 'none'
|
19
18
|
NoSyncFolders.new(@app, env).call(env)
|
20
19
|
else
|
21
|
-
|
20
|
+
fail Errors::SyncMethodError, sync_method_value: sync_method
|
22
21
|
end
|
23
22
|
end
|
24
|
-
|
25
23
|
end
|
26
24
|
|
27
25
|
class NoSyncFolders
|
28
|
-
def initialize(app,
|
26
|
+
def initialize(app, _env)
|
29
27
|
@app = app
|
30
28
|
end
|
31
29
|
|
32
30
|
def call(env)
|
33
31
|
@app.call(env)
|
34
|
-
env[:ui].info(
|
32
|
+
env[:ui].info('Sync folders are disabled')
|
35
33
|
end
|
36
34
|
end
|
37
35
|
|
38
36
|
# This middleware uses `rsync` to sync the folders over to the
|
39
37
|
# remote instance.
|
40
38
|
class RsyncFolders
|
41
|
-
def initialize(app,
|
39
|
+
def initialize(app, _env)
|
42
40
|
@app = app
|
43
|
-
@logger = Log4r::Logger.new(
|
41
|
+
@logger = Log4r::Logger.new('vagrant_openstack::action::sync_folders')
|
44
42
|
@host_os = RbConfig::CONFIG['host_os']
|
45
43
|
end
|
46
44
|
|
@@ -52,22 +50,20 @@ module VagrantPlugins
|
|
52
50
|
config = env[:machine].provider_config
|
53
51
|
rsync_includes = config.rsync_includes.to_a
|
54
52
|
|
55
|
-
env[:machine].config.vm.synced_folders.each do |
|
53
|
+
env[:machine].config.vm.synced_folders.each do |_, data|
|
56
54
|
hostpath = File.expand_path(data[:hostpath], env[:root_path])
|
57
55
|
guestpath = data[:guestpath]
|
58
56
|
|
59
57
|
# Make sure there is a trailing slash on the host path to
|
60
58
|
# avoid creating an additional directory with rsync
|
61
59
|
hostpath = "#{hostpath}/" if hostpath !~ /\/$/
|
62
|
-
|
60
|
+
|
63
61
|
# If on Windows, modify the path to work with cygwin rsync
|
64
62
|
if @host_os =~ /mswin|mingw|cygwin/
|
65
|
-
hostpath = hostpath.sub(/^([A-Za-z]):\//, "/cygdrive/#{
|
63
|
+
hostpath = hostpath.sub(/^([A-Za-z]):\//, "/cygdrive/#{Regexp.last_match[1].downcase}/")
|
66
64
|
end
|
67
65
|
|
68
|
-
env[:ui].info(I18n.t(
|
69
|
-
:hostpath => hostpath,
|
70
|
-
:guestpath => guestpath))
|
66
|
+
env[:ui].info(I18n.t('vagrant_openstack.rsync_folder', hostpath: hostpath, guestpath: guestpath))
|
71
67
|
|
72
68
|
# Create the guest path
|
73
69
|
env[:machine].communicate.sudo("mkdir -p '#{guestpath}'")
|
@@ -75,46 +71,39 @@ module VagrantPlugins
|
|
75
71
|
"chown -R #{ssh_info[:username]} '#{guestpath}'")
|
76
72
|
|
77
73
|
# Generate rsync include commands
|
78
|
-
includes = rsync_includes.each_with_object([])
|
79
|
-
incls <<
|
74
|
+
includes = rsync_includes.each_with_object([]) do |incl, incls|
|
75
|
+
incls << '--include'
|
80
76
|
incls << incl
|
81
|
-
|
77
|
+
end
|
82
78
|
|
83
79
|
# Rsync over to the guest path using the SSH info. add
|
84
80
|
# .hg/ to exclude list as that isn't covered in
|
85
81
|
# --cvs-exclude
|
86
82
|
command = [
|
87
|
-
|
88
|
-
|
89
|
-
|
83
|
+
'rsync', '--verbose', '--archive', '-z',
|
84
|
+
'--cvs-exclude',
|
85
|
+
'--exclude', '.hg/',
|
90
86
|
*includes,
|
91
|
-
|
87
|
+
'-e', "ssh -p #{ssh_info[:port]} -o StrictHostKeyChecking=no #{ssh_key_options(ssh_info)}",
|
92
88
|
hostpath,
|
93
89
|
"#{ssh_info[:username]}@#{ssh_info[:host]}:#{guestpath}"]
|
94
90
|
command.compact!
|
95
91
|
|
96
92
|
# during rsync, ignore files specified in .hgignore and
|
97
93
|
# .gitignore traditional .gitignore or .hgignore files
|
98
|
-
ignore_files = [
|
94
|
+
ignore_files = ['.hgignore', '.gitignore']
|
99
95
|
ignore_files.each do |ignore_file|
|
100
|
-
abs_ignore_file = env[:root_path].to_s +
|
101
|
-
if File.exist?(abs_ignore_file)
|
102
|
-
command = command + ["--exclude-from", abs_ignore_file]
|
103
|
-
end
|
96
|
+
abs_ignore_file = env[:root_path].to_s + '/' + ignore_file
|
97
|
+
command += ['--exclude-from', abs_ignore_file] if File.exist?(abs_ignore_file)
|
104
98
|
end
|
105
99
|
|
106
|
-
|
107
|
-
|
108
|
-
raise Errors::RsyncError,
|
109
|
-
:guestpath => guestpath,
|
110
|
-
:hostpath => hostpath,
|
111
|
-
:stderr => r.stderr
|
112
|
-
end
|
100
|
+
next if Vagrant::Util::Subprocess.execute(*command).exit_code == 0
|
101
|
+
ail Errors::RsyncError, guestpath: guestpath, hostpath: hostpath, stderr: r.stderr
|
113
102
|
end
|
114
103
|
end
|
115
104
|
|
116
105
|
private
|
117
|
-
|
106
|
+
|
118
107
|
def ssh_key_options(ssh_info)
|
119
108
|
# Ensure that `private_key_path` is an Array (for Vagrant < 1.4)
|
120
109
|
Array(ssh_info[:private_key_path]).map { |path| "-i '#{path}' " }.join
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'log4r'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module VagrantPlugins
|
5
|
+
module Openstack
|
6
|
+
module Action
|
7
|
+
class WaitForServerToStop
|
8
|
+
def initialize(app, _env)
|
9
|
+
@app = app
|
10
|
+
@logger = Log4r::Logger.new('vagrant_openstack::action::stop_server')
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
if env[:machine].id
|
15
|
+
env[:ui].info(I18n.t('vagrant_openstack.waiting_stop'))
|
16
|
+
client = env[:openstack_client].nova
|
17
|
+
timeout(200) do
|
18
|
+
while client.get_server_details(env, env[:machine].id)['status'] != 'SHUTOFF'
|
19
|
+
sleep 3
|
20
|
+
@logger.debug('Waiting for server to stop')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
@app.call(env)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'log4r'
|
2
|
+
require 'restclient'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module VagrantPlugins
|
6
|
+
module Openstack
|
7
|
+
class KeystoneClient
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@logger = Log4r::Logger.new('vagrant_openstack::keystone')
|
12
|
+
@session = VagrantPlugins::Openstack.session
|
13
|
+
end
|
14
|
+
|
15
|
+
def authenticate(env)
|
16
|
+
@logger.debug('Authenticating on Keystone')
|
17
|
+
config = env[:machine].provider_config
|
18
|
+
env[:ui].info(I18n.t('vagrant_openstack.client.authentication', project: config.tenant_name, user: config.username))
|
19
|
+
|
20
|
+
authentication = RestClient.post(
|
21
|
+
config.openstack_auth_url,
|
22
|
+
{
|
23
|
+
auth:
|
24
|
+
{
|
25
|
+
tenantName: config.tenant_name,
|
26
|
+
passwordCredentials:
|
27
|
+
{
|
28
|
+
username: config.username,
|
29
|
+
password: config.password
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}.to_json,
|
33
|
+
content_type: :json,
|
34
|
+
accept: :json)
|
35
|
+
|
36
|
+
access = JSON.parse(authentication)['access']
|
37
|
+
|
38
|
+
read_endpoint_catalog(env, access['serviceCatalog'])
|
39
|
+
override_endpoint_catalog_with_user_config(env)
|
40
|
+
print_endpoint_catalog(env)
|
41
|
+
|
42
|
+
response_token = access['token']
|
43
|
+
@session.token = response_token['id']
|
44
|
+
@session.project_id = response_token['tenant']['id']
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def read_endpoint_catalog(env, catalog)
|
50
|
+
env[:ui].info(I18n.t('vagrant_openstack.client.looking_for_available_endpoints'))
|
51
|
+
|
52
|
+
catalog.each do |service|
|
53
|
+
se = service['endpoints']
|
54
|
+
if se.size > 1
|
55
|
+
env[:ui].warn I18n.t('vagrant_openstack.client.multiple_endpoint', size: se.size, type: service['type'])
|
56
|
+
env[:ui].warn " => #{service['endpoints'][0]['publicURL']}"
|
57
|
+
end
|
58
|
+
url = se[0]['publicURL'].strip
|
59
|
+
@session.endpoints[service['type'].to_sym] = url unless url.empty?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def override_endpoint_catalog_with_user_config(env)
|
64
|
+
config = env[:machine].provider_config
|
65
|
+
@session.endpoints[:compute] = config.openstack_compute_url unless config.openstack_compute_url.nil?
|
66
|
+
@session.endpoints[:network] = config.openstack_network_url unless config.openstack_network_url.nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
def print_endpoint_catalog(env)
|
70
|
+
@session.endpoints.each do |key, value|
|
71
|
+
env[:ui].info(" -- #{key.to_s.ljust 15}: #{value}")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'log4r'
|
2
|
+
require 'restclient'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require 'vagrant-openstack-provider/client/utils'
|
6
|
+
|
7
|
+
module VagrantPlugins
|
8
|
+
module Openstack
|
9
|
+
class NeutronClient
|
10
|
+
include Singleton
|
11
|
+
include VagrantPlugins::Openstack::Utils
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@logger = Log4r::Logger.new('vagrant_openstack::neutron')
|
15
|
+
@session = VagrantPlugins::Openstack.session
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_private_networks(env)
|
19
|
+
authenticated(env) do
|
20
|
+
networks_json = RestClient.get("#{@session.endpoints[:network]}/networks",
|
21
|
+
'X-Auth-Token' => @session.token,
|
22
|
+
:accept => :json) { |res| handle_response(res) }
|
23
|
+
networks = []
|
24
|
+
JSON.parse(networks_json)['networks'].each do |n|
|
25
|
+
networks << { id: n['id'], name: n['name'] } if n['tenant_id'].eql? @session.project_id
|
26
|
+
end
|
27
|
+
networks
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'log4r'
|
2
|
+
require 'restclient'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require 'vagrant-openstack-provider/client/utils'
|
6
|
+
|
7
|
+
module VagrantPlugins
|
8
|
+
module Openstack
|
9
|
+
class NovaClient
|
10
|
+
include Singleton
|
11
|
+
include VagrantPlugins::Openstack::Utils
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@logger = Log4r::Logger.new('vagrant_openstack::nova')
|
15
|
+
@session = VagrantPlugins::Openstack.session
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_all_flavors(env)
|
19
|
+
authenticated(env) do
|
20
|
+
flavors_json = RestClient.get("#{@session.endpoints[:compute]}/flavors",
|
21
|
+
'X-Auth-Token' => @session.token,
|
22
|
+
:accept => :json) { |res| handle_response(res) }
|
23
|
+
|
24
|
+
return JSON.parse(flavors_json)['flavors'].map { |fl| Item.new(fl['id'], fl['name']) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_all_images(env)
|
29
|
+
authenticated(env) do
|
30
|
+
images_json = RestClient.get(
|
31
|
+
"#{@session.endpoints[:compute]}/images",
|
32
|
+
'X-Auth-Token' => @session.token, :accept => :json) { |res| handle_response(res) }
|
33
|
+
|
34
|
+
JSON.parse(images_json)['images'].map { |im| Item.new(im['id'], im['name']) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_server(env, name, image_ref, flavor_ref, networks, keypair)
|
39
|
+
authenticated(env) do
|
40
|
+
server = {}.tap do |s|
|
41
|
+
s['name'] = name
|
42
|
+
s['imageRef'] = image_ref
|
43
|
+
s['flavorRef'] = flavor_ref
|
44
|
+
s['key_name'] = keypair
|
45
|
+
unless networks.nil? || networks.empty?
|
46
|
+
s['networks'] = []
|
47
|
+
networks.each do |uuid|
|
48
|
+
s['networks'] << { uuid: uuid }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
server = RestClient.post(
|
54
|
+
"#{@session.endpoints[:compute]}/servers", { server: server }.to_json,
|
55
|
+
'X-Auth-Token' => @session.token,
|
56
|
+
:accept => :json,
|
57
|
+
:content_type => :json) { |res| handle_response(res) }
|
58
|
+
|
59
|
+
JSON.parse(server)['server']['id']
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def delete_server(env, server_id)
|
64
|
+
authenticated(env) do
|
65
|
+
RestClient.delete(
|
66
|
+
"#{@session.endpoints[:compute]}/servers/#{server_id}",
|
67
|
+
'X-Auth-Token' => @session.token,
|
68
|
+
:accept => :json) { |res| handle_response(res) }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def suspend_server(env, server_id)
|
73
|
+
authenticated(env) do
|
74
|
+
RestClient.post(
|
75
|
+
"#{@session.endpoints[:compute]}/servers/#{server_id}/action", '{ "suspend": null }',
|
76
|
+
'X-Auth-Token' => @session.token,
|
77
|
+
:accept => :json,
|
78
|
+
:content_type => :json) { |res| handle_response(res) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def resume_server(env, server_id)
|
83
|
+
# TODO(julienvey) check status before (if pause->unpause, if suspend->resume...)
|
84
|
+
authenticated(env) do
|
85
|
+
RestClient.post(
|
86
|
+
"#{@session.endpoints[:compute]}/servers/#{server_id}/action", '{ "resume": null }',
|
87
|
+
'X-Auth-Token' => @session.token,
|
88
|
+
:accept => :json,
|
89
|
+
:content_type => :json) { |res| handle_response(res) }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def stop_server(env, server_id)
|
94
|
+
authenticated(env) do
|
95
|
+
RestClient.post(
|
96
|
+
"#{@session.endpoints[:compute]}/servers/#{server_id}/action", '{ "os-stop": null }',
|
97
|
+
'X-Auth-Token' => @session.token,
|
98
|
+
:accept => :json,
|
99
|
+
:content_type => :json) { |res| handle_response(res) }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def start_server(env, server_id)
|
104
|
+
authenticated(env) do
|
105
|
+
RestClient.post(
|
106
|
+
"#{@session.endpoints[:compute]}/servers/#{server_id}/action", '{ "os-start": null }',
|
107
|
+
'X-Auth-Token' => @session.token,
|
108
|
+
:accept => :json,
|
109
|
+
:content_type => :json) { |res| handle_response(res) }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def get_server_details(env, server_id)
|
114
|
+
authenticated(env) do
|
115
|
+
server_details = RestClient.get(
|
116
|
+
"#{@session.endpoints[:compute]}/servers/#{server_id}",
|
117
|
+
'X-Auth-Token' => @session.token,
|
118
|
+
:accept => :json) { |res| handle_response(res) }
|
119
|
+
|
120
|
+
return JSON.parse(server_details)['server']
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def add_floating_ip(env, server_id, floating_ip)
|
125
|
+
authenticated(env) do
|
126
|
+
check_floating_ip(env, floating_ip)
|
127
|
+
RestClient.post(
|
128
|
+
"#{@session.endpoints[:compute]}/servers/#{server_id}/action",
|
129
|
+
{
|
130
|
+
addFloatingIp:
|
131
|
+
{
|
132
|
+
address: floating_ip
|
133
|
+
}
|
134
|
+
}.to_json,
|
135
|
+
'X-Auth-Token' => @session.token,
|
136
|
+
:accept => :json,
|
137
|
+
:content_type => :json) { |res| handle_response(res) }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def check_floating_ip(_env, floating_ip)
|
144
|
+
ip_details = RestClient.get(
|
145
|
+
"#{@session.endpoints[:compute]}/os-floating-ips",
|
146
|
+
'X-Auth-Token' => @session.token,
|
147
|
+
:accept => :json) { |res| handle_response(res) }
|
148
|
+
|
149
|
+
JSON.parse(ip_details)['floating_ips'].each do |ip|
|
150
|
+
next unless ip['ip'] == floating_ip
|
151
|
+
return if ip['instance_id'].nil?
|
152
|
+
fail "Floating IP #{floating_ip} already assigned to another server"
|
153
|
+
end
|
154
|
+
fail "Floating IP #{floating_ip} not available for this tenant"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class Item
|
159
|
+
attr_accessor :id, :name
|
160
|
+
def initialize(id, name)
|
161
|
+
@id = id
|
162
|
+
@name = name
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|