vagrant-softlayer 0.1.0 → 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.
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