vagrant-openstack-provider 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|