vagrant-softlayer 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ ## 0.2.0 (December 5, 2013)
2
+
3
+ NEW FEATURES:
4
+
5
+ - Automatic joining of local load balancers.
6
+
7
+ ## 0.1.0 (November 18, 2013)
8
+
9
+ - Initial release.
data/LICENSE CHANGED
@@ -1,20 +1,20 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2013 audiolize
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy of
6
- this software and associated documentation files (the "Software"), to deal in
7
- the Software without restriction, including without limitation the rights to
8
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
- the Software, and to permit persons to whom the Software is furnished to do so,
10
- subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 audiolize
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Vagrant SoftLayer Provider
2
2
 
3
+ [![Code Climate](https://codeclimate.com/github/audiolize/vagrant-softlayer.png)](https://codeclimate.com/github/audiolize/vagrant-softlayer)
4
+
3
5
  This is a [Vagrant](http://www.vagrantup.com) 1.3+ plugin that adds a [SoftLayer](http://www.softlayer.com)
4
6
  provider to Vagrant, allowing Vagrant to control and provision SoftLayer CCI instances.
5
7
 
@@ -102,6 +104,10 @@ Parameter | Description | Default | Required
102
104
  ------------ | ------------------------------------- | ------- | --------
103
105
  `manage_dns` | Add/remove A record on create/destroy | false | no
104
106
 
107
+ ### Join Local Load Balancers
108
+
109
+ See [Join load balancers](https://github.com/audiolize/vagrant-softlayer/wiki/Join-load-balancers).
110
+
105
111
  ### Instance Configuration
106
112
 
107
113
  Parameter | Description | Default | Required
@@ -231,3 +237,7 @@ Use bundler to execute Vagrant:
231
237
  ```
232
238
  $ bundle exec vagrant up --provider=softlayer
233
239
  ```
240
+
241
+
242
+ [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/audiolize/vagrant-softlayer/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
243
+
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
- require "bundler/gem_tasks"
2
- require 'rspec/core/rake_task'
3
-
4
- RSpec::Core::RakeTask.new
5
-
6
- task :default => "spec"
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => "spec"
@@ -10,12 +10,12 @@ module VagrantPlugins
10
10
  end
11
11
 
12
12
  def call(env)
13
- @app.call(env)
14
-
15
13
  env[:ui].info I18n.t("vagrant_softlayer.vm.destroying")
16
14
 
17
15
  sl_warden { env[:sl_machine].deleteObject }
18
16
  env[:machine].id = nil
17
+
18
+ @app.call(env)
19
19
  end
20
20
  end
21
21
  end
@@ -0,0 +1,95 @@
1
+ module VagrantPlugins
2
+ module SoftLayer
3
+ module Action
4
+ # Look for defined load balancers and perform join operations.
5
+ class JoinLoadBalancer
6
+ include Util::LoadBalancer
7
+ include Util::Network
8
+ include Util::Warden
9
+
10
+ def initialize(app, env)
11
+ @app = app
12
+ @logger = Log4r::Logger.new("vagrant_softlayer::action::join_load_balancer")
13
+ end
14
+
15
+ def call(env)
16
+ @env = env
17
+
18
+ if enabled?
19
+ setup
20
+ prepare
21
+ join!
22
+ rebalance!
23
+ end
24
+
25
+ @app.call(@env)
26
+ end
27
+
28
+ def append_service_group(cfg, idx)
29
+ {}.tap do |virtual_server|
30
+ virtual_server["allocation"] = 1
31
+ virtual_server["port"] = cfg[:port]
32
+ virtual_server["serviceGroups"] = [
33
+ {
34
+ "routingMethodId" => (@enums["Routing_Method"][cfg[:method]] || 10),
35
+ "routingTypeId" => (@enums["Routing_Type"][cfg[:type]] || 3),
36
+ "services" => []
37
+ }
38
+ ]
39
+ @load_balancers[idx]["virtualServers"] << virtual_server
40
+ end
41
+ end
42
+
43
+ def join!
44
+ @pending = []
45
+
46
+ until @queue.empty?
47
+ job = @queue.pop
48
+ merge(job[:cfg], job[:idx])
49
+ end
50
+
51
+ # Perform the API calls for join.
52
+ @load_balancers.each_with_index do |lb, idx|
53
+ next unless @pending[idx]
54
+ @logger.debug("Updating VIP #{lb['id']} with: #{lb['virtualServers']}")
55
+ vip_id = @services["VirtualIpAddress"].object_with_id(lb["id"])
56
+ sl_warden { vip_id.editObject("virtualServers" => lb["virtualServers"]) }
57
+ end
58
+ end
59
+
60
+ def merge(cfg, idx)
61
+ # Get the service group. Create it if not found.
62
+ sg = @load_balancers[idx]["virtualServers"].find(lambda { append_service_group(cfg, idx) }) { |g| g["port"] == cfg[:port] }
63
+ # Get the IP address ID of the current machine.
64
+ ip_id = ip_address_id(@env)
65
+ unless sg["serviceGroups"].first["services"].index { |s| s["ipAddressId"] == ip_id }
66
+ @logger.debug("Merging service: #{cfg[:service]}")
67
+ # Add the service to the group.
68
+ sg["serviceGroups"].first["services"] << {
69
+ "enabled" => 1,
70
+ "ipAddressId" => ip_id,
71
+ "groupReferences" => [ { "weight" => cfg[:service].weight } ],
72
+ "healthChecks" => [ { "healthCheckTypeId" => (@enums["Health_Check_Type"][cfg[:service].health_check] || 21) } ],
73
+ "port" => cfg[:service].destination_port
74
+ }
75
+ # Mark the load balancer object as pending update
76
+ @pending[idx] = true
77
+ end
78
+ end
79
+
80
+ def prepare
81
+ @env[:ui].info I18n.t("vagrant_softlayer.vm.joining_load_balancers")
82
+
83
+ # For each definition, check if the load balancer exists and enqueue
84
+ # the join operation.
85
+ @queue = []
86
+ @env[:machine].provider_config.load_balancers.each do |cfg|
87
+ idx = @load_balancers.index { |lb| lb["ipAddress"]["ipAddress"] == cfg[:vip] }
88
+ raise Errors::SLLoadBalancerNotFound unless idx
89
+ @queue << { :cfg => cfg, :idx => idx }
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,44 @@
1
+ module VagrantPlugins
2
+ module SoftLayer
3
+ module Action
4
+ # Cleanup orphaned virtual servers from load balancers.
5
+ class LoadBalancerCleanup
6
+ include Util::LoadBalancer
7
+ include Util::Warden
8
+
9
+ def initialize(app, env)
10
+ @app = app
11
+ @logger = Log4r::Logger.new("vagrant_softlayer::action::load_balancer_cleanup")
12
+ end
13
+
14
+ def call(env)
15
+ @env = env
16
+
17
+ if enabled?
18
+ setup
19
+ cleanup!
20
+ rebalance!
21
+ end
22
+
23
+ @app.call(@env)
24
+ end
25
+
26
+ def cleanup!
27
+ @env[:ui].info I18n.t("vagrant_softlayer.vm.load_balancer_cleanup")
28
+
29
+ # Keep it safe here. We delete a virtual server only if
30
+ # no services exist on any service group. In the future,
31
+ # we will find a way to selectively delete empty service groups.
32
+ @load_balancers.each do |load_balancer|
33
+ load_balancer["virtualServers"].each do |vs|
34
+ if vs["serviceGroups"].inject(0) { |sum, sg| sum + sg["services"].count } == 0
35
+ @logger.debug("Found empty virtual server (ID #{vs['id']}). Deleting.")
36
+ sl_warden { @services["VirtualServer"].object_with_id(vs["id"]).deleteObject }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -16,11 +16,30 @@ module VagrantPlugins
16
16
  def call(env)
17
17
  @env = env
18
18
 
19
- @env[:dns_zone] = setup_dns
19
+ update_dns
20
20
 
21
21
  @app.call(@env)
22
+ end
22
23
 
23
- update_dns
24
+ def add_record
25
+ template = {
26
+ "data" => ip_address(@env),
27
+ "domainId" => @dns_zone["id"],
28
+ "host" => hostname(@env),
29
+ "ttl" => 86400,
30
+ "type" => "a"
31
+ }
32
+ @env[:ui].info I18n.t("vagrant_softlayer.vm.creating_dns_record")
33
+ @logger.debug("Creating DNS A record for #{template['host']}.#{@dns_zone[:name]} (IP address #{template['data']}).")
34
+ resource = sl_warden { @resource.createObject(template) }
35
+ self.dns_id = resource["id"]
36
+ end
37
+
38
+ def delete_record
39
+ @env[:ui].info I18n.t("vagrant_softlayer.vm.deleting_dns_record")
40
+ @logger.debug("Deleting stored DNS A record (ID #{self.dns_id}).")
41
+ warn_msg = lambda { @env[:ui].warn I18n.t("vagrant_softlayer.errors.dns_record_not_found") }
42
+ sl_warden(warn_msg) { @resource.object_with_id(self.dns_id).deleteObject }
24
43
  end
25
44
 
26
45
  def dns_id
@@ -45,49 +64,28 @@ module VagrantPlugins
45
64
  end
46
65
  end
47
66
 
48
- def setup_dns
67
+ def update_dns
49
68
  unless @env[:machine].provider_config.manage_dns
50
69
  @logger.debug("Not managing DNS. Going ahead.")
51
70
  return
52
71
  end
53
72
 
54
- dns_zone = ::SoftLayer::Service.new("SoftLayer_Dns_Domain", @env[:sl_credentials])
55
-
73
+ # Lookup the DNS zone
74
+ zone = ::SoftLayer::Service.new("SoftLayer_Dns_Domain", @env[:sl_credentials])
56
75
  domain = @env[:machine].provider_config.domain
57
- @logger.debug("Looking for #{domain} zone into the SoftLayer zone list.")
58
- dns_zone_obj = sl_warden { dns_zone.getByDomainName(domain).first }
59
- raise Errors::SLDNSZoneNotFound, :zone => domain unless dns_zone_obj
60
- @logger.debug("Found DNS zone: #{dns_zone_obj.inspect}")
61
- return dns_zone_obj
62
- end
63
-
64
- def update_dns
65
- unless @env[:machine].provider_config.manage_dns
66
- @logger.debug("Not managing DNS. Going ahead.")
67
- return
68
- end
69
76
 
70
- dns_resource = ::SoftLayer::Service.new("SoftLayer_Dns_Domain_ResourceRecord", @env[:sl_credentials])
77
+ @logger.debug("Looking for #{domain} zone into the SoftLayer zone list.")
78
+ @dns_zone = sl_warden { zone.getByDomainName(domain).first }
79
+ raise Errors::SLDNSZoneNotFound, :zone => domain unless @dns_zone
80
+ @logger.debug("Found DNS zone: #{@dns_zone.inspect}")
71
81
 
72
- case @env[:action_name]
73
- when :machine_action_up
74
- hostname = @env[:machine].provider_config.hostname || @env[:machine].config.vm.hostname
75
- @env[:sl_machine] = @env[:sl_connection].object_with_id(@env[:machine].id.to_i)
76
- res_template = {
77
- "data" => ip_address(@env),
78
- "domainId" => @env[:dns_zone]["id"],
79
- "host" => hostname,
80
- "ttl" => 86400,
81
- "type" => "a"
82
- }
83
- @env[:ui].info I18n.t("vagrant_softlayer.vm.creating_dns_record")
84
- @logger.debug("Creating DNS A record for #{hostname}.#{@env[:dns_zone][:name]} (IP address #{res_template['data']}).")
85
- new_rr = sl_warden { dns_resource.createObject(res_template) }
86
- self.dns_id = new_rr["id"]
87
- when :machine_action_destroy
88
- @env[:ui].info I18n.t("vagrant_softlayer.vm.deleting_dns_record")
89
- @logger.debug("Deleting stored DNS A record (ID #{self.dns_id}).")
90
- sl_warden { dns_resource.object_with_id(self.dns_id).deleteObject }
82
+ # Add or remove the resource record
83
+ @resource = ::SoftLayer::Service.new("SoftLayer_Dns_Domain_ResourceRecord", @env[:sl_credentials])
84
+ case @env[:machine_action]
85
+ when :up
86
+ add_record unless self.dns_id
87
+ when :destroy
88
+ delete_record
91
89
  end
92
90
  end
93
91
  end
@@ -1,4 +1,5 @@
1
1
  require "pathname"
2
+ require "vagrant-softlayer/util/load_balancer"
2
3
  require "vagrant-softlayer/util/network"
3
4
  require "vagrant-softlayer/util/warden"
4
5
 
@@ -21,8 +22,10 @@ module VagrantPlugins
21
22
  end
22
23
  end
23
24
  b2.use SetupSoftLayer
24
- b2.use DestroyInstance
25
25
  b2.use UpdateDNS
26
+ b2.use DestroyInstance
27
+ b2.use LoadBalancerCleanup
28
+ b2.use ProvisionerCleanup
26
29
  else
27
30
  b2.use Message, :warn, "vagrant_softlayer.vm.not_destroying"
28
31
  end
@@ -157,9 +160,10 @@ module VagrantPlugins
157
160
  b1.use SetupSoftLayer
158
161
  b1.use Provision
159
162
  b1.use SyncFolders
160
- b1.use UpdateDNS
161
163
  b1.use CreateInstance
162
164
  b1.use WaitForProvision
165
+ b1.use UpdateDNS
166
+ b1.use JoinLoadBalancer
163
167
  b1.use WaitForCommunicator
164
168
  else
165
169
  b1.use Call, Is, :halted do |env2, b2|
@@ -168,6 +172,8 @@ module VagrantPlugins
168
172
  b2.use Provision
169
173
  b2.use SyncFolders
170
174
  b2.use StartInstance
175
+ b2.use UpdateDNS
176
+ b2.use JoinLoadBalancer
171
177
  b2.use WaitForCommunicator
172
178
  else
173
179
  b2.use Message, :warn, "vagrant_softlayer.vm.already_running"
@@ -181,20 +187,22 @@ module VagrantPlugins
181
187
  # The autoload farm
182
188
  action_root = Pathname.new(File.expand_path("../action", __FILE__))
183
189
 
184
- autoload :CreateInstance, action_root.join("create_instance")
185
- autoload :DestroyInstance, action_root.join("destroy_instance")
186
- autoload :Is, action_root.join("is")
187
- autoload :Message, action_root.join("message")
188
- autoload :ReadSSHInfo, action_root.join("read_ssh_info")
189
- autoload :ReadState, action_root.join("read_state")
190
- autoload :RebuildInstance, action_root.join("rebuild_instance")
191
- autoload :SetupSoftLayer, action_root.join("setup_softlayer")
192
- autoload :StartInstance, action_root.join("start_instance")
193
- autoload :StopInstance, action_root.join("stop_instance")
194
- autoload :SyncFolders, action_root.join("sync_folders")
195
- autoload :UpdateDNS, action_root.join("update_dns")
196
- autoload :WaitForProvision, action_root.join("wait_for_provision")
197
- autoload :WaitForRebuild, action_root.join("wait_for_rebuild")
190
+ autoload :CreateInstance, action_root.join("create_instance")
191
+ autoload :DestroyInstance, action_root.join("destroy_instance")
192
+ autoload :Is, action_root.join("is")
193
+ autoload :JoinLoadBalancer, action_root.join("join_load_balancer")
194
+ autoload :LoadBalancerCleanup, action_root.join("load_balancer_cleanup")
195
+ autoload :Message, action_root.join("message")
196
+ autoload :ReadSSHInfo, action_root.join("read_ssh_info")
197
+ autoload :ReadState, action_root.join("read_state")
198
+ autoload :RebuildInstance, action_root.join("rebuild_instance")
199
+ autoload :SetupSoftLayer, action_root.join("setup_softlayer")
200
+ autoload :StartInstance, action_root.join("start_instance")
201
+ autoload :StopInstance, action_root.join("stop_instance")
202
+ autoload :SyncFolders, action_root.join("sync_folders")
203
+ autoload :UpdateDNS, action_root.join("update_dns")
204
+ autoload :WaitForProvision, action_root.join("wait_for_provision")
205
+ autoload :WaitForRebuild, action_root.join("wait_for_rebuild")
198
206
  end
199
207
  end
200
208
  end
@@ -1,3 +1,5 @@
1
+ require "ostruct"
2
+
1
3
  module VagrantPlugins
2
4
  module SoftLayer
3
5
  class Config < Vagrant.plugin("2", :config)
@@ -58,6 +60,9 @@ module VagrantPlugins
58
60
  # The ID of the public VLAN.
59
61
  attr_accessor :vlan_public
60
62
 
63
+ # The load balancers service groups to join.
64
+ attr_reader :load_balancers
65
+
61
66
  # Automatically update DNS on create and destroy.
62
67
  attr_accessor :manage_dns
63
68
 
@@ -83,7 +88,41 @@ module VagrantPlugins
83
88
  @vlan_private = UNSET_VALUE
84
89
  @vlan_public = UNSET_VALUE
85
90
 
86
- @manage_dns = UNSET_VALUE
91
+ @load_balancers = []
92
+ @manage_dns = UNSET_VALUE
93
+ end
94
+
95
+ # Set the load balancer service group to join.
96
+ #
97
+ # Available options:
98
+ #
99
+ # :method => Routing method. Default to round robin.
100
+ # :port => Load balancer virtual port.
101
+ # :type => Routing type. Default to TCP.
102
+ # :vip => Load balancer virtual IP address.
103
+ #
104
+ # An optional block will accept parameters for the
105
+ # balanced service. Available parameters:
106
+ #
107
+ # :destination_port => TCP port on the node.
108
+ # :health_check => Service health check. Default to ping.
109
+ # :weight => Service weight. Default to 1.
110
+ #
111
+ def join_load_balancer(opts = {}, &block)
112
+ # Defaults
113
+ opts[:method] ||= "ROUND ROBIN"
114
+ opts[:type] ||= "TCP"
115
+ opts[:service] = OpenStruct.new(:destination_port => nil, :health_check => "PING", :weight => 1)
116
+
117
+ yield opts[:service] if block_given?
118
+
119
+ # Convert all options that belongs to
120
+ # an enumeration in uppercase.
121
+ opts[:method].upcase!
122
+ opts[:type].upcase!
123
+ opts[:service].health_check.upcase!
124
+
125
+ @load_balancers << opts
87
126
  end
88
127
 
89
128
  def finalize!
@@ -148,7 +187,7 @@ module VagrantPlugins
148
187
  @manage_dns = false if @manage_dns == UNSET_VALUE
149
188
  end
150
189
 
151
- # Aliases for ssh_key for beautiful semantic
190
+ # Aliases for ssh_key for beautiful semantic.
152
191
  def ssh_keys=(value)
153
192
  @ssh_key = value
154
193
  end
@@ -171,6 +210,18 @@ module VagrantPlugins
171
210
  errors << I18n.t("vagrant_softlayer.config.hostname_required")
172
211
  end
173
212
 
213
+ # Fail if a load balancer has been specified without vip, port or destination port.
214
+ unless @load_balancers.empty?
215
+ @load_balancers.each do |lb|
216
+ errors << I18n.t("vagrant_softlayer.config.lb_port_vip_required") unless (lb[:vip] && lb[:port] && lb[:service].destination_port)
217
+ end
218
+ end
219
+
220
+ # Fail if two or more load balancers has been specified with same vip and port.
221
+ if @load_balancers.map { |lb| { :port => lb[:port], :vip => lb[:vip] } }.uniq!
222
+ errors << I18n.t("vagrant_softlayer.config.lb_duplicate")
223
+ end
224
+
174
225
  { "SoftLayer" => errors }
175
226
  end
176
227
  end
@@ -17,6 +17,10 @@ module VagrantPlugins
17
17
  error_key(:dns_zone_not_found)
18
18
  end
19
19
 
20
+ class SLLoadBalancerNotFound < VagrantSoftLayerError
21
+ error_key(:load_balancer_not_found)
22
+ end
23
+
20
24
  class SLSshKeyNotFound < VagrantSoftLayerError
21
25
  error_key(:ssh_key_not_found)
22
26
  end
@@ -0,0 +1,103 @@
1
+ module VagrantPlugins
2
+ module SoftLayer
3
+ module Util
4
+ # This mixin contains utility methods for load balancer management.
5
+ module LoadBalancer
6
+ # Whether load balancer management is enabled or not.
7
+ def enabled?
8
+ if @env[:machine].provider_config.load_balancers.empty?
9
+ @logger.debug("No load balancer has been defined. Going ahead.")
10
+ return false
11
+ end
12
+
13
+ # Currently we don't do load balancing for private machines.
14
+ if @env[:machine].provider_config.private_only
15
+ @logger.info("Load balancing doesn't work for private machines. Going ahead.")
16
+ return false
17
+ end
18
+ true
19
+ end
20
+
21
+ # Get existing stuff.
22
+ def read_load_balancers
23
+ mask = [
24
+ "id",
25
+ "ipAddress.ipAddress",
26
+ "virtualServers.serviceGroups.services.groupReferences",
27
+ "virtualServers.serviceGroups.services.healthChecks"
28
+ ]
29
+ @logger.debug("Looking for existing load balancers.")
30
+ @load_balancers = sl_warden { @services["Account"].object_mask(mask).getAdcLoadBalancers }
31
+ @logger.debug("Got load balancer configuration:")
32
+ @logger.debug("-- #{@load_balancers}")
33
+ end
34
+
35
+ # For each load balancer, check if total connections
36
+ # are less than 100% and, if so, rebalance the allocations.
37
+ def rebalance!
38
+ read_load_balancers
39
+
40
+ @load_balancers.each do |load_balancer|
41
+ next if load_balancer["virtualServers"].empty?
42
+ next if 100 == load_balancer["virtualServers"].inject(0) { |sum, vs| sum += vs["allocation"] }
43
+
44
+ # Create allocation slots.
45
+ count = load_balancer["virtualServers"].count
46
+ allocation = [100 / count] * count
47
+ (100 % count).times { |i| allocation[i] += 1 }
48
+
49
+ # Rebalance allocations.
50
+ load_balancer["virtualServers"].each do |vs|
51
+ vs["allocation"] = allocation.pop
52
+ end
53
+
54
+ # Update the VIP object.
55
+ @logger.debug("Rebalancing VIP #{load_balancer['id']}")
56
+ @logger.debug("-- #{load_balancer}")
57
+ @services["VirtualIpAddress"].object_with_id(load_balancer["id"]).editObject("virtualServers" => load_balancer["virtualServers"])
58
+ end
59
+ end
60
+
61
+ # Initial setup.
62
+ def setup
63
+ # A plethora of service objects is required for managing
64
+ # load balancers. We instanciate'em all here.
65
+ @services = {
66
+ "Account" => ::SoftLayer::Service.new("SoftLayer_Account", @env[:sl_credentials])
67
+ }
68
+ [
69
+ "Health_Check_Type",
70
+ "Routing_Method",
71
+ "Routing_Type",
72
+ "Service",
73
+ "Service_Group",
74
+ "VirtualIpAddress",
75
+ "VirtualServer"
76
+ ].each do |service|
77
+ @services[service] = ::SoftLayer::Service.new(
78
+ "SoftLayer_Network_Application_Delivery_Controller_LoadBalancer_#{service}",
79
+ @env[:sl_credentials]
80
+ )
81
+ end
82
+
83
+ # We create enumerations for the various configurables.
84
+ @enums = {}
85
+ [
86
+ "Health_Check_Type",
87
+ "Routing_Method",
88
+ "Routing_Type"
89
+ ].each do |service|
90
+ {}.tap do |enum|
91
+ sl_warden { @services[service].getAllObjects }.each do |record|
92
+ enum[record["name"].upcase] = record["id"]
93
+ end
94
+ @enums[service] = enum
95
+ end
96
+ end
97
+
98
+ read_load_balancers
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -2,13 +2,44 @@ module VagrantPlugins
2
2
  module SoftLayer
3
3
  module Util
4
4
  module Network
5
- # Gets instance's IP address.
5
+ # Gets hostname of the instance starting from the environment.
6
+ def hostname(env)
7
+ env[:machine].provider_config.hostname || env[:machine].config.vm.hostname
8
+ end
9
+
10
+ # Gets IP address of the instance starting from the environment.
6
11
  #
7
12
  # Returns the private IP address if the instance has been
8
13
  # defined as private only, the public IP address otherwise.
9
14
  def ip_address(env)
10
- service = env[:machine].provider_config.private_only ? :getPrimaryBackendIpAddress : :getPrimaryIpAddress
11
- return sl_warden { env[:sl_machine].send(service) }
15
+ ip_address_record(env)[:address]
16
+ end
17
+
18
+ # Gets IP address ID of the instance starting from the environment.
19
+ #
20
+ # Returns the private IP address ID if the instance has been
21
+ # defined as private only, the public IP address ID otherwise.
22
+ def ip_address_id(env)
23
+ ip_address_record(env)[:id]
24
+ end
25
+
26
+ # Gets IP address record of the instance starting from the environment.
27
+ #
28
+ # Returns an hash with the following structure:
29
+ #
30
+ # :address
31
+ # :id
32
+ #
33
+ # Returns the private IP address record if the instance has been
34
+ # defined as private only, the public IP address record otherwise.
35
+ def ip_address_record(env)
36
+ data_type = env[:machine].provider_config.private_only ? "primaryBackendNetworkComponent" : "primaryNetworkComponent"
37
+ mask = { data_type => { "primaryIpAddressRecord" => ["id", "ipAddress"] } }
38
+ record = sl_warden { env[:sl_machine].object_mask(mask).getObject }
39
+ return {
40
+ :address => record[data_type]["primaryIpAddressRecord"]["ipAddress"],
41
+ :id => record[data_type]["primaryIpAddressRecord"]["id"]
42
+ }
12
43
  end
13
44
 
14
45
  # Returns SSH keys starting from the configuration parameter.
@@ -19,7 +19,7 @@ module VagrantPlugins
19
19
  rescue ::OpenSSL::SSL::SSLError
20
20
  raise Errors::SLCertificateError
21
21
  rescue SocketError, ::SoftLayer::SoftLayerAPIException => e
22
- if e.class == ::SoftLayer::SoftLayerAPIException && e.message.start_with?("Unable to find object with id")
22
+ if e.class == ::SoftLayer::SoftLayerAPIException && (e.message.start_with?("Unable to find object") || e.message.start_with?("Object does not exist"))
23
23
  out = rescue_proc.call if rescue_proc
24
24
  if retry_interval > 0
25
25
  sleep retry_interval
@@ -1,5 +1,5 @@
1
1
  module VagrantPlugins
2
2
  module SoftLayer
3
- VERSION = "0.1.0"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
data/locales/en.yml CHANGED
@@ -26,6 +26,20 @@ en:
26
26
  config.vm.provider :softlayer do |sl|
27
27
  sl.hostname = "vagrant"
28
28
  end
29
+ lb_duplicate: |-
30
+ A load balancer service group has been specified multiple
31
+ times. Only one directive for any { :vip, :port } combination
32
+ is allowed.
33
+ lb_port_vip_required: |-
34
+ For joining a load balancer, you need to specify at least
35
+ virtual IP address, virtual server port and destination
36
+ port in the provider section of the Vagrantfile:
37
+
38
+ config.vm.provider :softlayer do |sl|
39
+ sl.join_load_balancer :port => 443, :vip => "1.1.1.1" do |service|
40
+ service.destination_port = 443
41
+ end
42
+ end
29
43
  ssh_key_required: |-
30
44
  At least an SSH key for the instance is required.
31
45
  Please specify it, either using name or id, in the
@@ -67,9 +81,15 @@ en:
67
81
  Windows example:
68
82
 
69
83
  set SSL_CERT_FILE=C:\HashiCorp\Vagrant\embedded\cacert.pem
84
+ dns_record_not_found: |-
85
+ The DNS record you're trying to delete has not been found.
70
86
  dns_zone_not_found: |-
71
87
  The DNS zone you're trying to manage (%{zone}) has not been
72
88
  found in the zone list.
89
+ load_balancer_not_found: |-
90
+ The load balancer you're trying to join has not been found.
91
+
92
+ Please check the configuration parameter for virtual IP address.
73
93
  ssh_key_not_found: |-
74
94
  The SSH key you're trying to set (%{key}) does not exists in the
75
95
  SoftLayer account's keychain.
@@ -116,6 +136,10 @@ en:
116
136
  Deleting DNS record for the instance...
117
137
  destroying: |-
118
138
  Destroying the SoftLayer instance...
139
+ joining_load_balancers: |-
140
+ Joining load balancer(s)...
141
+ load_balancer_cleanup: |-
142
+ Running cleanup tasks for load balancers...
119
143
  not_destroying: |-
120
144
  The SoftLayer instance will not be destroyed, since the confirmation
121
145
  was declined.
@@ -32,7 +32,8 @@ describe VagrantPlugins::SoftLayer::Config do
32
32
  its("vlan_private") { should be_nil }
33
33
  its("vlan_public") { should be_nil }
34
34
 
35
- its("manage_dns") { should be_false }
35
+ its("load_balancers") { should eq [] }
36
+ its("manage_dns") { should be_false }
36
37
  end
37
38
 
38
39
  describe "overriding defaults" do
@@ -69,6 +70,34 @@ describe VagrantPlugins::SoftLayer::Config do
69
70
  end
70
71
  end
71
72
 
73
+ describe "joining load balancer" do
74
+ it "should set weight to 1 by default" do
75
+ config.join_load_balancer :port => 443, :vip => "1.1.1.1"
76
+ config.finalize!
77
+ expect(config.load_balancers.first[:service].weight).to eq(1)
78
+ end
79
+
80
+ it "should set passed options" do
81
+ config.join_load_balancer :foo => "bar", :port => 443, :vip => "1.1.1.1"
82
+ config.finalize!
83
+ expect(config.load_balancers.first[:foo]).to eq("bar")
84
+ end
85
+
86
+ it "should set service parameters" do
87
+ config.join_load_balancer :port => 443, :vip => "1.1.1.1" do |srv|
88
+ srv.destination_port = 443
89
+ srv.health_check = "DNS"
90
+ srv.notes = "Some notes"
91
+ srv.weight = 9
92
+ end
93
+ config.finalize!
94
+ expect(config.load_balancers.first[:service].destination_port).to eq(443)
95
+ expect(config.load_balancers.first[:service].health_check).to eq("DNS")
96
+ expect(config.load_balancers.first[:service].notes).to eq("Some notes")
97
+ expect(config.load_balancers.first[:service].weight).to eq(9)
98
+ end
99
+ end
100
+
72
101
  describe "using SL_ environment variables" do
73
102
  before :each do
74
103
  ENV.stub(:[]).with("SL_API_KEY").and_return("env_api_key")
@@ -143,6 +172,7 @@ describe VagrantPlugins::SoftLayer::Config do
143
172
 
144
173
  it "should validate if hostname is not given but config.vm.hostname is set" do
145
174
  config.hostname = nil
175
+ config.finalize!
146
176
  machine.stub_chain(:config, :vm, :hostname).and_return("vagrant")
147
177
  expect(config.validate(machine)["SoftLayer"]).to have(:no).item
148
178
  end
@@ -152,5 +182,46 @@ describe VagrantPlugins::SoftLayer::Config do
152
182
  config.finalize!
153
183
  expect(config.validate(machine)["SoftLayer"]).to have(1).item
154
184
  end
185
+
186
+ it "should fail if a load balancer is specified without vip" do
187
+ config.join_load_balancer :port => 443 do |srv|
188
+ srv.destination_port = 443
189
+ end
190
+ config.finalize!
191
+ expect(config.validate(machine)["SoftLayer"]).to have(1).item
192
+ end
193
+
194
+ it "should fail if a load balancer is specified without port" do
195
+ config.join_load_balancer :vip => "1.1.1.1" do |srv|
196
+ srv.destination_port = 443
197
+ end
198
+ config.finalize!
199
+ expect(config.validate(machine)["SoftLayer"]).to have(1).item
200
+ end
201
+
202
+ it "should fail if a load balancer is specified without destination port" do
203
+ config.join_load_balancer :port => 443, :vip => "1.1.1.1"
204
+ config.finalize!
205
+ expect(config.validate(machine)["SoftLayer"]).to have(1).item
206
+ end
207
+
208
+ it "should fail if two load balancers han been defined with same vip and port" do
209
+ config.join_load_balancer :port => 443, :vip => "1.1.1.1" do |srv|
210
+ srv.destination_port = 443
211
+ end
212
+ config.join_load_balancer :port => 443, :vip => "1.1.1.1" do |srv|
213
+ srv.destination_port = 8443
214
+ end
215
+ config.finalize!
216
+ expect(config.validate(machine)["SoftLayer"]).to have(1).item
217
+ end
218
+
219
+ it "should validate if a load balancer if specified with vip, port and destination port" do
220
+ config.join_load_balancer :port => 443, :vip => "1.1.1.1" do |srv|
221
+ srv.destination_port = 443
222
+ end
223
+ config.finalize!
224
+ expect(config.validate(machine)["SoftLayer"]).to have(:no).item
225
+ end
155
226
  end
156
227
  end
@@ -1,53 +1,53 @@
1
- $:.unshift File.expand_path("../lib", __FILE__)
2
- require "vagrant-softlayer/version"
3
-
4
- Gem::Specification.new do |spec|
5
- spec.name = "vagrant-softlayer"
6
- spec.version = VagrantPlugins::SoftLayer::VERSION
7
- spec.authors = "Audiolize GmbH"
8
- spec.email = ""
9
- spec.description = "Enables Vagrant to manages SoftLayer CCI."
10
- spec.summary = "Enables Vagrant to manages SoftLayer CCI."
11
-
12
- # The following block of code determines the files that should be included
13
- # in the gem. It does this by reading all the files in the directory where
14
- # this gemspec is, and parsing out the ignored files from the gitignore.
15
- # Note that the entire gitignore(5) syntax is not supported, specifically
16
- # the "!" syntax, but it should mostly work correctly.
17
- root_path = File.dirname(__FILE__)
18
- all_files = Dir.chdir(root_path) { Dir.glob("**/{*,.*}") }
19
- all_files.reject! { |file| [".", ".."].include?(File.basename(file)) }
20
- gitignore_path = File.join(root_path, ".gitignore")
21
- gitignore = File.readlines(gitignore_path)
22
- gitignore.map! { |line| line.chomp.strip }
23
- gitignore.reject! { |line| line.empty? || line =~ /^(#|!)/ }
24
-
25
- unignored_files = all_files.reject do |file|
26
- # Ignore any directories, the gemspec only cares about files
27
- next true if File.directory?(file)
28
-
29
- # Ignore any paths that match anything in the gitignore. We do
30
- # two tests here:
31
- #
32
- # - First, test to see if the entire path matches the gitignore.
33
- # - Second, match if the basename does, this makes it so that things
34
- # like '.DS_Store' will match sub-directories too (same behavior
35
- # as git).
36
- #
37
- gitignore.any? do |ignore|
38
- File.fnmatch(ignore, file, File::FNM_PATHNAME) ||
39
- File.fnmatch(ignore, File.basename(file), File::FNM_PATHNAME)
40
- end
41
- end
42
-
43
- spec.files = unignored_files
44
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
45
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
46
- spec.require_path = "lib"
47
-
48
- spec.add_dependency "softlayer_api"
49
-
50
- spec.add_development_dependency "bundler", "~> 1.3"
51
- spec.add_development_dependency "rake"
52
- spec.add_development_dependency "rspec"
53
- end
1
+ $:.unshift File.expand_path("../lib", __FILE__)
2
+ require "vagrant-softlayer/version"
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "vagrant-softlayer"
6
+ spec.version = VagrantPlugins::SoftLayer::VERSION
7
+ spec.authors = "Audiolize GmbH"
8
+ spec.email = ""
9
+ spec.description = "Enables Vagrant to manages SoftLayer CCI."
10
+ spec.summary = "Enables Vagrant to manages SoftLayer CCI."
11
+
12
+ # The following block of code determines the files that should be included
13
+ # in the gem. It does this by reading all the files in the directory where
14
+ # this gemspec is, and parsing out the ignored files from the gitignore.
15
+ # Note that the entire gitignore(5) syntax is not supported, specifically
16
+ # the "!" syntax, but it should mostly work correctly.
17
+ root_path = File.dirname(__FILE__)
18
+ all_files = Dir.chdir(root_path) { Dir.glob("**/{*,.*}") }
19
+ all_files.reject! { |file| [".", ".."].include?(File.basename(file)) }
20
+ gitignore_path = File.join(root_path, ".gitignore")
21
+ gitignore = File.readlines(gitignore_path)
22
+ gitignore.map! { |line| line.chomp.strip }
23
+ gitignore.reject! { |line| line.empty? || line =~ /^(#|!)/ }
24
+
25
+ unignored_files = all_files.reject do |file|
26
+ # Ignore any directories, the gemspec only cares about files
27
+ next true if File.directory?(file)
28
+
29
+ # Ignore any paths that match anything in the gitignore. We do
30
+ # two tests here:
31
+ #
32
+ # - First, test to see if the entire path matches the gitignore.
33
+ # - Second, match if the basename does, this makes it so that things
34
+ # like '.DS_Store' will match sub-directories too (same behavior
35
+ # as git).
36
+ #
37
+ gitignore.any? do |ignore|
38
+ File.fnmatch(ignore, file, File::FNM_PATHNAME) ||
39
+ File.fnmatch(ignore, File.basename(file), File::FNM_PATHNAME)
40
+ end
41
+ end
42
+
43
+ spec.files = unignored_files
44
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
45
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
46
+ spec.require_path = "lib"
47
+
48
+ spec.add_dependency "softlayer_api"
49
+
50
+ spec.add_development_dependency "bundler", "~> 1.3"
51
+ spec.add_development_dependency "rake"
52
+ spec.add_development_dependency "rspec"
53
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vagrant-softlayer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-11-18 00:00:00.000000000 Z
12
+ date: 2013-12-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: softlayer_api
@@ -81,6 +81,7 @@ executables: []
81
81
  extensions: []
82
82
  extra_rdoc_files: []
83
83
  files:
84
+ - CHANGELOG.md
84
85
  - dummy.box
85
86
  - example_box/metadata.json
86
87
  - example_box/README.md
@@ -88,6 +89,8 @@ files:
88
89
  - lib/vagrant-softlayer/action/create_instance.rb
89
90
  - lib/vagrant-softlayer/action/destroy_instance.rb
90
91
  - lib/vagrant-softlayer/action/is.rb
92
+ - lib/vagrant-softlayer/action/join_load_balancer.rb
93
+ - lib/vagrant-softlayer/action/load_balancer_cleanup.rb
91
94
  - lib/vagrant-softlayer/action/message.rb
92
95
  - lib/vagrant-softlayer/action/read_ssh_info.rb
93
96
  - lib/vagrant-softlayer/action/read_state.rb
@@ -105,6 +108,7 @@ files:
105
108
  - lib/vagrant-softlayer/errors.rb
106
109
  - lib/vagrant-softlayer/plugin.rb
107
110
  - lib/vagrant-softlayer/provider.rb
111
+ - lib/vagrant-softlayer/util/load_balancer.rb
108
112
  - lib/vagrant-softlayer/util/network.rb
109
113
  - lib/vagrant-softlayer/util/warden.rb
110
114
  - lib/vagrant-softlayer/version.rb
@@ -131,7 +135,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
131
135
  version: '0'
132
136
  segments:
133
137
  - 0
134
- hash: -3883830637352667112
138
+ hash: 2669020036350751985
135
139
  required_rubygems_version: !ruby/object:Gem::Requirement
136
140
  none: false
137
141
  requirements:
@@ -140,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
144
  version: '0'
141
145
  segments:
142
146
  - 0
143
- hash: -3883830637352667112
147
+ hash: 2669020036350751985
144
148
  requirements: []
145
149
  rubyforge_project:
146
150
  rubygems_version: 1.8.23