bosh_aws_cpi 0.6.2 → 0.7.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/README.md +104 -0
- data/lib/cloud/aws/aki_picker.rb +68 -0
- data/lib/cloud/aws/cloud.rb +185 -100
- data/lib/cloud/aws/helpers.rb +28 -16
- data/lib/cloud/aws/registry_client.rb +3 -3
- data/lib/cloud/aws/version.rb +1 -1
- data/lib/cloud/aws.rb +1 -0
- data/spec/spec_helper.rb +11 -9
- data/spec/unit/aki_picker_spec.rb +29 -0
- data/spec/unit/create_stemcell_spec.rb +16 -14
- data/spec/unit/create_vm_spec.rb +41 -1
- data/spec/unit/delete_stemcell_spec.rb +11 -0
- data/spec/unit/helpers_spec.rb +27 -5
- data/spec/unit/set_vm_metadata_spec.rb +30 -0
- metadata +13 -7
- data/README +0 -3
data/README.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# BOSH AWS Cloud Provider Interface
|
2
|
+
Copyright (c) 2009-2012 VMware, Inc.
|
3
|
+
|
4
|
+
For online documentation see: http://rubydoc.info/gems/bosh_aws_cpi/
|
5
|
+
|
6
|
+
## Options
|
7
|
+
|
8
|
+
These options are passed to the AWS CPI when it is instantiated.
|
9
|
+
|
10
|
+
### AWS options
|
11
|
+
|
12
|
+
* `access_key_id` (required)
|
13
|
+
AWS IAM user access key
|
14
|
+
* `secret_access_key` (required)
|
15
|
+
AWS IAM secret access key
|
16
|
+
* `default_key_name` (required)
|
17
|
+
default AWS ssh key name to assign to created virtual machines
|
18
|
+
* `default_security_group` (required)
|
19
|
+
default AWS security group to assign to created virtual machines
|
20
|
+
* `ec2_private_key` (required)
|
21
|
+
local path to the ssh private key, must match `default_key_name`
|
22
|
+
* `region` (optional)
|
23
|
+
EC2 region, defaults to `us-east-1`
|
24
|
+
* `ec2_endpoint` (optional)
|
25
|
+
URL of the EC2 endpoint to connect to, defaults to the endpoint corresponding to the selected region,
|
26
|
+
or `DEFAULT_EC2_ENDPOINT` if no region has been selected
|
27
|
+
* `max_retries` (optional)
|
28
|
+
maximum number of time to retry an AWS API call, defaults to `DEFAULT_MAX_RETRIES`
|
29
|
+
|
30
|
+
### Registry options
|
31
|
+
|
32
|
+
The registry options are passed to the AWS CPI by the BOSH director based on the settings in `director.yml`, but can be
|
33
|
+
overridden if needed.
|
34
|
+
|
35
|
+
* `endpoint` (required)
|
36
|
+
registry URL
|
37
|
+
* `user` (required)
|
38
|
+
registry user
|
39
|
+
* `password` (required)
|
40
|
+
registry password
|
41
|
+
|
42
|
+
### Agent options
|
43
|
+
|
44
|
+
Agent options are passed to the AWS CPI by the BOSH director based on the settings in `director.yml`, but can be
|
45
|
+
overridden if needed.
|
46
|
+
|
47
|
+
### Resource pool options
|
48
|
+
|
49
|
+
These options are specified under `cloud_options` in the `resource_pools` section of a BOSH deployment manifest.
|
50
|
+
|
51
|
+
* `availability_zone` (optional)
|
52
|
+
the EC2 availability zone the VMs should be created in
|
53
|
+
* `instance_type` (required)
|
54
|
+
which [type of instance](http://aws.amazon.com/ec2/instance-types/) the VMs should belong to
|
55
|
+
|
56
|
+
### Network options
|
57
|
+
|
58
|
+
These options are specified under `cloud_options` in the `networks` section of a BOSH deployment manifest.
|
59
|
+
|
60
|
+
* `type` (required)
|
61
|
+
can be either `dynamic` for a DHCP assigned IP by AWS, or `vip` to use an Elastic IP (which needs to be already
|
62
|
+
allocated)
|
63
|
+
|
64
|
+
## Example
|
65
|
+
|
66
|
+
This is a sample of how AWS specific properties are used in a BOSH deployment manifest:
|
67
|
+
|
68
|
+
---
|
69
|
+
name: sample
|
70
|
+
director_uuid: 38ce80c3-e9e9-4aac-ba61-97c676631b91
|
71
|
+
|
72
|
+
...
|
73
|
+
|
74
|
+
networks:
|
75
|
+
- name: nginx_network
|
76
|
+
type: vip
|
77
|
+
cloud_properties: {}
|
78
|
+
- name: default
|
79
|
+
type: dynamic
|
80
|
+
cloud_properties:
|
81
|
+
security_groups:
|
82
|
+
- default
|
83
|
+
|
84
|
+
...
|
85
|
+
|
86
|
+
resource_pools:
|
87
|
+
- name: common
|
88
|
+
network: default
|
89
|
+
size: 3
|
90
|
+
stemcell:
|
91
|
+
name: bosh-stemcell
|
92
|
+
version: 0.6.7
|
93
|
+
cloud_properties:
|
94
|
+
instance_type: m1.small
|
95
|
+
|
96
|
+
...
|
97
|
+
|
98
|
+
properties:
|
99
|
+
aws:
|
100
|
+
access_key_id: AKIAIYJWVDUP4KRWBESQ
|
101
|
+
secret_access_key: EVGFswlmOvA33ZrU1ViFEtXC5Sugc19yPzokeWRf
|
102
|
+
default_key_name: bosh
|
103
|
+
default_security_groups: ["bosh"]
|
104
|
+
ec2_private_key: /home/bosh/.ssh/bosh
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Bosh::AwsCloud
|
2
|
+
class AKIPicker
|
3
|
+
|
4
|
+
# @param [AWS::Core::ServiceInterface] ec2
|
5
|
+
def initialize(ec2)
|
6
|
+
@ec2 = ec2
|
7
|
+
end
|
8
|
+
|
9
|
+
# finds the correct aki for the current region
|
10
|
+
# @param [String] architecture instruction architecture to find
|
11
|
+
# @param [String] root_device_name
|
12
|
+
# @return [String] EC2 image id
|
13
|
+
def pick(architecture, root_device_name)
|
14
|
+
candidate = pick_candidate(fetch_akis(architecture), root_device_name)
|
15
|
+
raise Bosh::Clouds::CloudError, "unable to find AKI" unless candidate
|
16
|
+
logger.info("auto-selected AKI: #{candidate.image_id}")
|
17
|
+
|
18
|
+
candidate.image_id
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def fetch_akis(architecture)
|
24
|
+
filter = aki_filter(architecture)
|
25
|
+
@ec2.client.describe_images(:filters => filter).images_set
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Hash] search filter
|
29
|
+
def aki_filter(architecture)
|
30
|
+
[
|
31
|
+
{:name => "architecture", :values => [architecture]},
|
32
|
+
{:name => "image-type", :values => %w[kernel]},
|
33
|
+
{:name => "owner-alias", :values => %w[amazon]}
|
34
|
+
]
|
35
|
+
end
|
36
|
+
|
37
|
+
def regexp(root_device_name)
|
38
|
+
# do nasty hackery to select boot device and version from
|
39
|
+
# the image_location string e.g. pv-grub-hd00_1.03-x86_64.gz
|
40
|
+
if root_device_name == "/dev/sda1"
|
41
|
+
/-hd00[-_](\d+)\.(\d+)/
|
42
|
+
else
|
43
|
+
/-hd0[-_](\d+)\.(\d+)/
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param [AWS::EC2::ImageCollection] akis
|
48
|
+
def pick_candidate(akis, root_device_name)
|
49
|
+
candidate = nil
|
50
|
+
major = 0
|
51
|
+
minor = 0
|
52
|
+
akis.each do |image|
|
53
|
+
match = image.image_location.match(regexp(root_device_name))
|
54
|
+
if match && match[1].to_i > major && match[2].to_i > minor
|
55
|
+
candidate = image
|
56
|
+
major = match[1].to_i
|
57
|
+
minor = match[2].to_i
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
candidate
|
62
|
+
end
|
63
|
+
|
64
|
+
def logger
|
65
|
+
Bosh::Clouds::Config.logger
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/cloud/aws/cloud.rb
CHANGED
@@ -5,20 +5,26 @@ module Bosh::AwsCloud
|
|
5
5
|
class Cloud < Bosh::Cloud
|
6
6
|
include Helpers
|
7
7
|
|
8
|
+
# default maximum number of times to retry an AWS API call
|
8
9
|
DEFAULT_MAX_RETRIES = 2
|
10
|
+
# default availability zone for instances and disks
|
9
11
|
DEFAULT_AVAILABILITY_ZONE = "us-east-1a"
|
10
12
|
DEFAULT_EC2_ENDPOINT = "ec2.amazonaws.com"
|
11
|
-
METADATA_TIMEOUT = 5 # seconds
|
12
|
-
DEVICE_POLL_TIMEOUT = 60 # seconds
|
13
|
+
METADATA_TIMEOUT = 5 # in seconds
|
14
|
+
DEVICE_POLL_TIMEOUT = 60 # in seconds
|
15
|
+
MAX_TAG_KEY_LENGTH = 127
|
16
|
+
MAX_TAG_VALUE_LENGTH = 255
|
13
17
|
|
14
18
|
attr_reader :ec2
|
15
19
|
attr_reader :registry
|
16
20
|
attr_accessor :logger
|
17
21
|
|
18
22
|
##
|
19
|
-
# Initialize BOSH AWS CPI
|
23
|
+
# Initialize BOSH AWS CPI. The contents of sub-hashes are defined in the {file:README.md}
|
20
24
|
# @param [Hash] options CPI options
|
21
|
-
#
|
25
|
+
# @option options [Hash] aws AWS specific options
|
26
|
+
# @option options [Hash] agent agent options
|
27
|
+
# @option options [Hash] registry agent options
|
22
28
|
def initialize(options)
|
23
29
|
@options = options.dup
|
24
30
|
|
@@ -30,6 +36,7 @@ module Bosh::AwsCloud
|
|
30
36
|
|
31
37
|
@agent_properties = @options["agent"] || {}
|
32
38
|
@aws_properties = @options["aws"]
|
39
|
+
@aws_region = @aws_properties.delete("region")
|
33
40
|
@registry_properties = @options["registry"]
|
34
41
|
|
35
42
|
@default_key_name = @aws_properties["default_key_name"]
|
@@ -38,7 +45,7 @@ module Bosh::AwsCloud
|
|
38
45
|
aws_params = {
|
39
46
|
:access_key_id => @aws_properties["access_key_id"],
|
40
47
|
:secret_access_key => @aws_properties["secret_access_key"],
|
41
|
-
:ec2_endpoint => @aws_properties["ec2_endpoint"] ||
|
48
|
+
:ec2_endpoint => @aws_properties["ec2_endpoint"] || default_ec2_endpoint,
|
42
49
|
:max_retries => @aws_properties["max_retries"] || DEFAULT_MAX_RETRIES,
|
43
50
|
:logger => @aws_logger
|
44
51
|
}
|
@@ -59,37 +66,31 @@ module Bosh::AwsCloud
|
|
59
66
|
registry_user,
|
60
67
|
registry_password)
|
61
68
|
|
69
|
+
@aki_picker = AKIPicker.new(@ec2)
|
62
70
|
@metadata_lock = Mutex.new
|
63
71
|
end
|
64
72
|
|
65
73
|
##
|
66
|
-
#
|
67
|
-
# @param [String] agent_id
|
68
|
-
# @param [String] stemcell_id AMI id
|
69
|
-
#
|
70
|
-
# @param [Hash] resource_pool
|
71
|
-
# @param [Hash] network_spec
|
72
|
-
# security groups they must
|
73
|
-
# @param [optional, Array] disk_locality
|
74
|
+
# Create an EC2 instance and wait until it's in running state
|
75
|
+
# @param [String] agent_id agent id associated with new VM
|
76
|
+
# @param [String] stemcell_id AMI id of the stemcell used to
|
77
|
+
# create the new instance
|
78
|
+
# @param [Hash] resource_pool resource pool specification
|
79
|
+
# @param [Hash] network_spec network specification, if it contains
|
80
|
+
# security groups they must already exist
|
81
|
+
# @param [optional, Array] disk_locality list of disks that
|
74
82
|
# might be attached to this instance in the future, can be
|
75
83
|
# used as a placement hint (i.e. instance will only be created
|
76
84
|
# if resource pool availability zone is the same as disk
|
77
85
|
# availability zone)
|
78
|
-
# @param [optional, Hash] environment
|
86
|
+
# @param [optional, Hash] environment data to be merged into
|
79
87
|
# agent settings
|
80
|
-
#
|
81
|
-
# @return [String] created instance id
|
88
|
+
# @return [String] EC2 instance id of the new virtual machine
|
82
89
|
def create_vm(agent_id, stemcell_id, resource_pool,
|
83
90
|
network_spec, disk_locality = nil, environment = nil)
|
84
91
|
with_thread_name("create_vm(#{agent_id}, ...)") do
|
85
92
|
network_configurator = NetworkConfigurator.new(network_spec)
|
86
93
|
|
87
|
-
user_data = {
|
88
|
-
"registry" => {
|
89
|
-
"endpoint" => @registry.endpoint
|
90
|
-
}
|
91
|
-
}
|
92
|
-
|
93
94
|
security_groups =
|
94
95
|
network_configurator.security_groups(@default_security_groups)
|
95
96
|
@logger.debug("using security groups: #{security_groups.join(', ')}")
|
@@ -107,7 +108,7 @@ module Bosh::AwsCloud
|
|
107
108
|
:key_name => resource_pool["key_name"] || @default_key_name,
|
108
109
|
:security_groups => security_groups,
|
109
110
|
:instance_type => resource_pool["instance_type"],
|
110
|
-
:user_data => Yajl::Encoder.encode(user_data)
|
111
|
+
:user_data => Yajl::Encoder.encode(user_data(network_spec))
|
111
112
|
}
|
112
113
|
|
113
114
|
instance_params[:availability_zone] =
|
@@ -131,8 +132,9 @@ module Bosh::AwsCloud
|
|
131
132
|
end
|
132
133
|
|
133
134
|
##
|
134
|
-
#
|
135
|
-
#
|
135
|
+
# Delete EC2 instance ("terminate" in AWS language) and wait until
|
136
|
+
# it reports as terminated
|
137
|
+
# @param [String] instance_id EC2 instance id
|
136
138
|
def delete_vm(instance_id)
|
137
139
|
with_thread_name("delete_vm(#{instance_id})") do
|
138
140
|
instance = @ec2.instances[instance_id]
|
@@ -153,8 +155,8 @@ module Bosh::AwsCloud
|
|
153
155
|
end
|
154
156
|
|
155
157
|
##
|
156
|
-
#
|
157
|
-
# @param [String] instance_id
|
158
|
+
# Reboot EC2 instance
|
159
|
+
# @param [String] instance_id EC2 instance id
|
158
160
|
def reboot_vm(instance_id)
|
159
161
|
with_thread_name("reboot_vm(#{instance_id})") do
|
160
162
|
instance = @ec2.instances[instance_id]
|
@@ -165,7 +167,7 @@ module Bosh::AwsCloud
|
|
165
167
|
##
|
166
168
|
# Creates a new EBS volume
|
167
169
|
# @param [Integer] size disk size in MiB
|
168
|
-
# @param [optional, String] instance_id
|
170
|
+
# @param [optional, String] instance_id EC2 instance id
|
169
171
|
# of the VM that this disk will be attached to
|
170
172
|
# @return [String] created EBS volume id
|
171
173
|
def create_disk(size, instance_id = nil)
|
@@ -182,11 +184,13 @@ module Bosh::AwsCloud
|
|
182
184
|
cloud_error("AWS CPI maximum disk size is 1 TiB")
|
183
185
|
end
|
184
186
|
|
187
|
+
# if the disk is created for an instance, use the same availability
|
188
|
+
# zone as they must match
|
185
189
|
if instance_id
|
186
190
|
instance = @ec2.instances[instance_id]
|
187
191
|
availability_zone = instance.availability_zone
|
188
192
|
else
|
189
|
-
availability_zone =
|
193
|
+
availability_zone = default_availability_zone
|
190
194
|
end
|
191
195
|
|
192
196
|
volume_params = {
|
@@ -203,10 +207,9 @@ module Bosh::AwsCloud
|
|
203
207
|
end
|
204
208
|
|
205
209
|
##
|
206
|
-
#
|
207
|
-
# @param [String] disk_id volume id
|
210
|
+
# Delete EBS volume
|
211
|
+
# @param [String] disk_id EBS volume id
|
208
212
|
# @raise [Bosh::Clouds::CloudError] if disk is not in available state
|
209
|
-
# @return nil
|
210
213
|
def delete_disk(disk_id)
|
211
214
|
with_thread_name("delete_disk(#{disk_id})") do
|
212
215
|
volume = @ec2.volumes[disk_id]
|
@@ -229,6 +232,9 @@ module Bosh::AwsCloud
|
|
229
232
|
end
|
230
233
|
end
|
231
234
|
|
235
|
+
# Attach an EBS volume to an EC2 instance
|
236
|
+
# @param [String] instance_id EC2 instance id of the virtual machine to attach the disk to
|
237
|
+
# @param [String] disk_id EBS volume id of the disk to attach
|
232
238
|
def attach_disk(instance_id, disk_id)
|
233
239
|
with_thread_name("attach_disk(#{instance_id}, #{disk_id})") do
|
234
240
|
instance = @ec2.instances[instance_id]
|
@@ -241,9 +247,13 @@ module Bosh::AwsCloud
|
|
241
247
|
settings["disks"]["persistent"] ||= {}
|
242
248
|
settings["disks"]["persistent"][disk_id] = device_name
|
243
249
|
end
|
250
|
+
@logger.info("Attached `#{disk_id}' to `#{instance_id}'")
|
244
251
|
end
|
245
252
|
end
|
246
253
|
|
254
|
+
# Detach an EBS volume from an EC2 instance
|
255
|
+
# @param [String] instance_id EC2 instance id of the virtual machine to detach the disk from
|
256
|
+
# @param [String] disk_id EBS volume id of the disk to detach
|
247
257
|
def detach_disk(instance_id, disk_id)
|
248
258
|
with_thread_name("detach_disk(#{instance_id}, #{disk_id})") do
|
249
259
|
instance = @ec2.instances[instance_id]
|
@@ -261,10 +271,10 @@ module Bosh::AwsCloud
|
|
261
271
|
end
|
262
272
|
end
|
263
273
|
|
264
|
-
#
|
265
|
-
# @param [String] instance_id instance
|
274
|
+
# Configure network for an EC2 instance
|
275
|
+
# @param [String] instance_id EC2 instance id
|
266
276
|
# @param [Hash] network_spec network properties
|
267
|
-
# @
|
277
|
+
# @raise [Bosh::Clouds:NotSupported] if the security groups change
|
268
278
|
def configure_networks(instance_id, network_spec)
|
269
279
|
with_thread_name("configure_networks(#{instance_id}, ...)") do
|
270
280
|
@logger.info("Configuring `#{instance_id}' to use the following " \
|
@@ -294,24 +304,26 @@ module Bosh::AwsCloud
|
|
294
304
|
end
|
295
305
|
|
296
306
|
##
|
297
|
-
# Creates a new AMI using stemcell image.
|
307
|
+
# Creates a new EC2 AMI using stemcell image.
|
298
308
|
# This method can only be run on an EC2 instance, as image creation
|
299
309
|
# involves creating and mounting new EBS volume as local block device.
|
300
310
|
# @param [String] image_path local filesystem path to a stemcell image
|
301
|
-
# @param [Hash] cloud_properties
|
311
|
+
# @param [Hash] cloud_properties AWS-specific stemcell properties
|
302
312
|
# @option cloud_properties [String] kernel_id
|
303
|
-
# AKI, auto-selected based on the region
|
313
|
+
# AKI, auto-selected based on the region, unless specified
|
304
314
|
# @option cloud_properties [String] root_device_name
|
305
|
-
# provided by the stemcell manifest
|
315
|
+
# block device path (e.g. /dev/sda1), provided by the stemcell manifest, unless specified
|
306
316
|
# @option cloud_properties [String] architecture
|
307
|
-
# provided by the stemcell manifest
|
317
|
+
# instruction set architecture (e.g. x86_64), provided by the stemcell manifest,
|
318
|
+
# unless specified
|
308
319
|
# @option cloud_properties [String] disk (2048)
|
309
320
|
# root disk size
|
321
|
+
# @return [String] EC2 AMI name of the stemcell
|
310
322
|
def create_stemcell(image_path, cloud_properties)
|
311
323
|
# TODO: refactor into several smaller methods
|
312
324
|
with_thread_name("create_stemcell(#{image_path}...)") do
|
313
325
|
begin
|
314
|
-
# These
|
326
|
+
# These three variables are used in 'ensure' clause
|
315
327
|
instance = nil
|
316
328
|
volume = nil
|
317
329
|
# 1. Create and mount new EBS volume (2GB default)
|
@@ -331,31 +343,14 @@ module Bosh::AwsCloud
|
|
331
343
|
snapshot = volume.create_snapshot
|
332
344
|
wait_resource(snapshot, :completed)
|
333
345
|
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
aki = find_aki(architecture, root_device_name)
|
338
|
-
|
339
|
-
# we could set :description here, but since we don't have a
|
340
|
-
# handle to the stemcell name and version, we can't set it
|
341
|
-
# to something useful :(
|
342
|
-
image_params = {
|
343
|
-
:name => "BOSH-#{generate_unique_name}",
|
344
|
-
:architecture => architecture,
|
345
|
-
:kernel_id => aki,
|
346
|
-
:root_device_name => root_device_name,
|
347
|
-
:block_device_mappings => {
|
348
|
-
"/dev/sda" => { :snapshot_id => snapshot.id },
|
349
|
-
"/dev/sdb" => "ephemeral0"
|
350
|
-
}
|
351
|
-
}
|
352
|
-
|
353
|
-
image = @ec2.images.create(image_params)
|
346
|
+
params = image_params(cloud_properties, snapshot.id)
|
347
|
+
image = @ec2.images.create(params)
|
354
348
|
wait_resource(image, :available, :state)
|
355
349
|
|
350
|
+
tag(image, "Name", params[:description]) if params[:description]
|
351
|
+
|
356
352
|
image.id
|
357
353
|
rescue => e
|
358
|
-
# TODO: delete snapshot?
|
359
354
|
@logger.error(e)
|
360
355
|
raise e
|
361
356
|
ensure
|
@@ -367,49 +362,53 @@ module Bosh::AwsCloud
|
|
367
362
|
end
|
368
363
|
end
|
369
364
|
|
370
|
-
#
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
filters << {:name => "owner-alias", :values => %w[amazon]}
|
365
|
+
# Delete a stemcell and the accompanying snapshots
|
366
|
+
# @param [String] stemcell_id EC2 AMI name of the stemcell to be deleted
|
367
|
+
def delete_stemcell(stemcell_id)
|
368
|
+
with_thread_name("delete_stemcell(#{stemcell_id})") do
|
369
|
+
snapshots = []
|
370
|
+
image = @ec2.images[stemcell_id]
|
377
371
|
|
378
|
-
|
372
|
+
image.block_device_mappings.each do |device, map|
|
373
|
+
id = map[:snapshot_id]
|
374
|
+
if id
|
375
|
+
@logger.debug("queuing snapshot #{id} for deletion")
|
376
|
+
snapshots << id
|
377
|
+
end
|
378
|
+
end
|
379
379
|
|
380
|
-
|
381
|
-
# the image_location string e.g. pv-grub-hd00_1.03-x86_64.gz
|
382
|
-
if root_device_name == "/dev/sda1"
|
383
|
-
regexp = /-hd00[-_](\d+)\.(\d+)/
|
384
|
-
else
|
385
|
-
regexp = /-hd0[-_](\d+)\.(\d+)/
|
386
|
-
end
|
380
|
+
image.deregister
|
387
381
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
match = image.image_location.match(regexp)
|
393
|
-
if match && match[1].to_i > major && match[2].to_i > minor
|
394
|
-
candidate = image
|
395
|
-
major = match[1].to_i
|
396
|
-
minor = match[2].to_i
|
382
|
+
snapshots.each do |id|
|
383
|
+
@logger.info("cleaning up snapshot #{id}")
|
384
|
+
snapshot = @ec2.snapshots[id]
|
385
|
+
snapshot.delete
|
397
386
|
end
|
398
387
|
end
|
399
|
-
|
400
|
-
cloud_error("unable to find AKI") unless candidate
|
401
|
-
@logger.info("auto-selected AKI: #{candidate.image_id}")
|
402
|
-
|
403
|
-
candidate.image_id
|
404
388
|
end
|
405
389
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
390
|
+
# Add tags to an instance. In addition to the suplied tags,
|
391
|
+
# it adds a 'Name' tag as it is shown in the AWS console.
|
392
|
+
# @param [String] vm vm id that was once returned by {#create_vm}
|
393
|
+
# @param [Hash] metadata metadata key/value pairs
|
394
|
+
# @return [void]
|
395
|
+
def set_vm_metadata(vm, metadata)
|
396
|
+
instance = @ec2.instances[vm]
|
397
|
+
|
398
|
+
# TODO should we clear existing tags that don't exist in metadata?
|
399
|
+
metadata.each_pair do |key, value|
|
400
|
+
tag(instance, key, value)
|
410
401
|
end
|
402
|
+
|
403
|
+
# should deployment name be included too?
|
404
|
+
job = metadata[:job]
|
405
|
+
index = metadata[:index]
|
406
|
+
tag(instance, "Name", "#{job}/#{index}") if job && index
|
407
|
+
rescue AWS::EC2::Errors::TagLimitExceeded => e
|
408
|
+
@logger.error("could not tag #{instance.id}: #{e.message}")
|
411
409
|
end
|
412
410
|
|
411
|
+
# @note Not implemented in the AWS CPI
|
413
412
|
def validate_deployment(old_manifest, new_manifest)
|
414
413
|
# Not implemented in VSphere CPI as well
|
415
414
|
not_implemented(:validate_deployment)
|
@@ -422,17 +421,19 @@ module Bosh::AwsCloud
|
|
422
421
|
# @param [String] resource_pool_az availability zone specified in
|
423
422
|
# the resource pool (may be nil)
|
424
423
|
# @return [String] availability zone to use
|
424
|
+
# @note this is a private method that is public to make it easier to test
|
425
425
|
def select_availability_zone(volumes, resource_pool_az)
|
426
426
|
if volumes && !volumes.empty?
|
427
427
|
disks = volumes.map { |vid| @ec2.volumes[vid] }
|
428
428
|
ensure_same_availability_zone(disks, resource_pool_az)
|
429
429
|
disks.first.availability_zone
|
430
430
|
else
|
431
|
-
resource_pool_az ||
|
431
|
+
resource_pool_az || default_availability_zone
|
432
432
|
end
|
433
433
|
end
|
434
434
|
|
435
435
|
# ensure all supplied availability zones are the same
|
436
|
+
# @note this is a private method that is public to make it easier to test
|
436
437
|
def ensure_same_availability_zone(disks, default)
|
437
438
|
zones = disks.map { |disk| disk.availability_zone }
|
438
439
|
zones << default if default
|
@@ -443,6 +444,70 @@ module Bosh::AwsCloud
|
|
443
444
|
|
444
445
|
private
|
445
446
|
|
447
|
+
# add a tag to something
|
448
|
+
def tag(taggable, key, value)
|
449
|
+
trimmed_key = key[0..(MAX_TAG_KEY_LENGTH - 1)]
|
450
|
+
trimmed_value = value[0..(MAX_TAG_VALUE_LENGTH - 1)]
|
451
|
+
taggable.add_tag(trimmed_key, :value => trimmed_value)
|
452
|
+
rescue AWS::EC2::Errors::InvalidParameterValue => e
|
453
|
+
@logger.error("could not tag #{taggable.id}: #{e.message}")
|
454
|
+
end
|
455
|
+
|
456
|
+
# Prepare EC2 user data
|
457
|
+
# @param [Hash] network_spec network specification
|
458
|
+
# @return [Hash] EC2 user data
|
459
|
+
def user_data(network_spec)
|
460
|
+
data = {}
|
461
|
+
|
462
|
+
data["registry"] = { "endpoint" => @registry.endpoint }
|
463
|
+
|
464
|
+
with_dns(network_spec) do |servers|
|
465
|
+
data["dns"] = { "nameserver" => servers }
|
466
|
+
end
|
467
|
+
|
468
|
+
data
|
469
|
+
end
|
470
|
+
|
471
|
+
# extract dns server list from network spec and yield the the list
|
472
|
+
# @param [Hash] network_spec network specification for instance
|
473
|
+
# @yield [Array]
|
474
|
+
def with_dns(network_spec)
|
475
|
+
network_spec.each_value do |properties|
|
476
|
+
if properties["dns"]
|
477
|
+
yield properties["dns"]
|
478
|
+
return
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
def image_params(cloud_properties, snapshot_id)
|
484
|
+
root_device_name = cloud_properties["root_device_name"]
|
485
|
+
architecture = cloud_properties["architecture"]
|
486
|
+
|
487
|
+
params = {
|
488
|
+
:name => "BOSH-#{generate_unique_name}",
|
489
|
+
:architecture => architecture,
|
490
|
+
:kernel_id => find_aki(architecture, root_device_name),
|
491
|
+
:root_device_name => root_device_name,
|
492
|
+
:block_device_mappings => {
|
493
|
+
"/dev/sda" => { :snapshot_id => snapshot_id },
|
494
|
+
"/dev/sdb" => "ephemeral0"
|
495
|
+
}
|
496
|
+
}
|
497
|
+
|
498
|
+
# old stemcells doesn't have name & version
|
499
|
+
if cloud_properties["name"] && cloud_properties["version"]
|
500
|
+
name = "#{cloud_properties['name']} #{cloud_properties['version']}"
|
501
|
+
params[:description] = name
|
502
|
+
end
|
503
|
+
|
504
|
+
params
|
505
|
+
end
|
506
|
+
|
507
|
+
def find_aki(architecture, root_device_name)
|
508
|
+
@aki_picker.pick(architecture, root_device_name)
|
509
|
+
end
|
510
|
+
|
446
511
|
##
|
447
512
|
# Generates initial agent settings. These settings will be read by agent
|
448
513
|
# from AWS registry (also a BOSH component) on a target instance. Disk
|
@@ -571,10 +636,11 @@ module Bosh::AwsCloud
|
|
571
636
|
attachment = volume.detach_from(instance, device_map[volume.id])
|
572
637
|
@logger.info("Detaching `#{volume.id}' from `#{instance.id}'")
|
573
638
|
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
639
|
+
wait_resource(attachment, :detached) do |error|
|
640
|
+
if error.is_a? AWS::Core::Resource::NotFound
|
641
|
+
@logger.info("attachment is no longer found, assuming it to be detached")
|
642
|
+
:detached
|
643
|
+
end
|
578
644
|
end
|
579
645
|
end
|
580
646
|
|
@@ -678,6 +744,25 @@ module Bosh::AwsCloud
|
|
678
744
|
end
|
679
745
|
end
|
680
746
|
|
747
|
+
def default_ec2_endpoint
|
748
|
+
if @aws_region
|
749
|
+
"ec2.#{@aws_region}.amazonaws.com"
|
750
|
+
else
|
751
|
+
DEFAULT_EC2_ENDPOINT
|
752
|
+
end
|
753
|
+
end
|
754
|
+
|
755
|
+
def default_availability_zone
|
756
|
+
if @aws_region
|
757
|
+
"#{@aws_region}b"
|
758
|
+
else
|
759
|
+
DEFAULT_AVAILABILITY_ZONE
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
def task_checkpoint
|
764
|
+
Bosh::Clouds::Config.task_checkpoint
|
765
|
+
end
|
681
766
|
end
|
682
767
|
|
683
768
|
end
|
data/lib/cloud/aws/helpers.rb
CHANGED
@@ -21,9 +21,13 @@ module Bosh::AwsCloud
|
|
21
21
|
|
22
22
|
started_at = Time.now
|
23
23
|
failures = 0
|
24
|
-
|
24
|
+
|
25
|
+
# all resources but Attachment have id
|
26
|
+
desc = resource.respond_to?(:id) ? resource.id : resource.to_s
|
25
27
|
|
26
28
|
loop do
|
29
|
+
task_checkpoint
|
30
|
+
|
27
31
|
duration = Time.now - started_at
|
28
32
|
|
29
33
|
if duration > timeout
|
@@ -35,21 +39,13 @@ module Bosh::AwsCloud
|
|
35
39
|
"(#{duration}s)")
|
36
40
|
end
|
37
41
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
# 2) sometimes when we create a new EC2 instance, AWS reports that
|
46
|
-
# the instance it returns is missing
|
47
|
-
# in both cases we just wait a little and retry...
|
48
|
-
raise e if failures > 3
|
49
|
-
failures += 1
|
50
|
-
@logger.error("#{e.message}: #{desc}")
|
51
|
-
sleep(1)
|
52
|
-
next
|
42
|
+
state = get_state_for(resource, state_method) do |error|
|
43
|
+
if block_given?
|
44
|
+
yield error
|
45
|
+
else
|
46
|
+
@logger.error("#{error.message}: #{desc}")
|
47
|
+
nil
|
48
|
+
end
|
53
49
|
end
|
54
50
|
|
55
51
|
# This is not a very strong convention, but some resources
|
@@ -71,6 +67,22 @@ module Bosh::AwsCloud
|
|
71
67
|
@logger.info("#{desc} is now #{target_state}, took #{total}s")
|
72
68
|
end
|
73
69
|
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def get_state_for(resource, state_method)
|
74
|
+
resource.send(state_method)
|
75
|
+
rescue AWS::EC2::Errors::InvalidAMIID::NotFound,
|
76
|
+
AWS::EC2::Errors::InvalidInstanceID::NotFound,
|
77
|
+
AWS::Core::Resource::NotFound => e
|
78
|
+
# ugly workaround for AWS race conditions:
|
79
|
+
# 1) sometimes when we upload a stemcell and proceed to create a VM
|
80
|
+
# from it, AWS reports that the AMI is missing
|
81
|
+
# 2) sometimes when we create a new EC2 instance, AWS reports that
|
82
|
+
# the instance it returns is missing
|
83
|
+
# in both cases we just catch the exception, wait a little and retry...
|
84
|
+
yield e
|
85
|
+
end
|
74
86
|
end
|
75
87
|
end
|
76
88
|
|
@@ -42,7 +42,7 @@ module Bosh::AwsCloud
|
|
42
42
|
payload = Yajl::Encoder.encode(settings)
|
43
43
|
url = "#{@endpoint}/instances/#{instance_id}/settings"
|
44
44
|
|
45
|
-
response = @client.put(url, payload, @headers)
|
45
|
+
response = @client.put(url, {:body => payload, :header => @headers})
|
46
46
|
|
47
47
|
if response.status != 200
|
48
48
|
cloud_error("Cannot update settings for `#{instance_id}', " \
|
@@ -59,7 +59,7 @@ module Bosh::AwsCloud
|
|
59
59
|
def read_settings(instance_id)
|
60
60
|
url = "#{@endpoint}/instances/#{instance_id}/settings"
|
61
61
|
|
62
|
-
response = @client.get(url, {
|
62
|
+
response = @client.get(url, {:header => @headers})
|
63
63
|
|
64
64
|
if response.status != 200
|
65
65
|
cloud_error("Cannot read settings for `#{instance_id}', " \
|
@@ -94,7 +94,7 @@ module Bosh::AwsCloud
|
|
94
94
|
def delete_settings(instance_id)
|
95
95
|
url = "#{@endpoint}/instances/#{instance_id}/settings"
|
96
96
|
|
97
|
-
response = @client.delete(url, @headers)
|
97
|
+
response = @client.delete(url, {:header => @headers})
|
98
98
|
|
99
99
|
if response.status != 200
|
100
100
|
cloud_error("Cannot delete settings for `#{instance_id}', " \
|
data/lib/cloud/aws/version.rb
CHANGED
data/lib/cloud/aws.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -68,21 +68,23 @@ def mock_registry(endpoint = "http://registry:3333")
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def mock_cloud(options = nil)
|
71
|
-
|
72
|
-
|
73
|
-
images = double("images")
|
71
|
+
ec2 = mock_ec2
|
72
|
+
AWS::EC2.stub(:new).and_return(ec2)
|
74
73
|
|
75
|
-
ec2
|
74
|
+
yield ec2 if block_given?
|
76
75
|
|
77
|
-
|
78
|
-
|
79
|
-
ec2.stub(:images).and_return(images)
|
76
|
+
Bosh::AwsCloud::Cloud.new(options || mock_cloud_options)
|
77
|
+
end
|
80
78
|
|
81
|
-
|
79
|
+
def mock_ec2
|
80
|
+
ec2 = double(AWS::EC2,
|
81
|
+
:instances => double("instances"),
|
82
|
+
:volumes => double("volumes"),
|
83
|
+
:images =>double("images"))
|
82
84
|
|
83
85
|
yield ec2 if block_given?
|
84
86
|
|
85
|
-
|
87
|
+
ec2
|
86
88
|
end
|
87
89
|
|
88
90
|
def dynamic_network_spec
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Bosh::AwsCloud::AKIPicker do
|
4
|
+
let(:akis) {
|
5
|
+
[
|
6
|
+
double("image-1", :root_device_name => "/dev/sda1",
|
7
|
+
:image_location => "pv-grub-hd00_1.03-x86_64.gz",
|
8
|
+
:image_id => "aki-b4aa75dd"),
|
9
|
+
double("image-2", :root_device_name => "/dev/sda1",
|
10
|
+
:image_location => "pv-grub-hd00_1.02-x86_64.gz",
|
11
|
+
:image_id => "aki-b4aa75d0")
|
12
|
+
]
|
13
|
+
}
|
14
|
+
let(:logger) {double("logger", :info => nil)}
|
15
|
+
let(:picker) {Bosh::AwsCloud::AKIPicker.new(double("ec2"))}
|
16
|
+
|
17
|
+
it "should pick the AKI with the highest version" do
|
18
|
+
picker.should_receive(:logger).and_return(logger)
|
19
|
+
picker.should_receive(:fetch_akis).and_return(akis)
|
20
|
+
picker.pick("x86_64", "/dev/sda1").should == "aki-b4aa75dd"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should raise an error when it can't pick an AKI" do
|
24
|
+
picker.should_receive(:fetch_akis).and_return(akis)
|
25
|
+
expect {
|
26
|
+
picker.pick("foo", "bar")
|
27
|
+
}.to raise_error Bosh::Clouds::CloudError, "unable to find AKI"
|
28
|
+
end
|
29
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Copyright (c) 2009-2012 VMware, Inc.
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
5
|
describe Bosh::AwsCloud::Cloud do
|
6
6
|
|
@@ -8,6 +8,10 @@ describe Bosh::AwsCloud::Cloud do
|
|
8
8
|
@tmp_dir = Dir.mktmpdir
|
9
9
|
end
|
10
10
|
|
11
|
+
after(:each) do
|
12
|
+
FileUtils.rm_rf(@tmp_dir)
|
13
|
+
end
|
14
|
+
|
11
15
|
describe "EBS-volume based flow" do
|
12
16
|
|
13
17
|
it "creates stemcell by copying an image to a new EBS volume" do
|
@@ -19,7 +23,7 @@ describe Bosh::AwsCloud::Cloud do
|
|
19
23
|
:device => "/dev/sdh",
|
20
24
|
:volume => volume)
|
21
25
|
|
22
|
-
snapshot = double("snapshot", :id => "s-baz")
|
26
|
+
snapshot = double("snapshot", :id => "s-baz", :delete => nil)
|
23
27
|
image = double("image", :id => "i-bar")
|
24
28
|
|
25
29
|
unique_name = UUIDTools::UUID.random_create.to_s
|
@@ -29,29 +33,25 @@ describe Bosh::AwsCloud::Cloud do
|
|
29
33
|
:architecture => "x86_64",
|
30
34
|
:kernel_id => "aki-b4aa75dd",
|
31
35
|
:root_device_name => "/dev/sda1",
|
36
|
+
:description => "bosh-stemcell 1.2.3",
|
32
37
|
:block_device_mappings => {
|
33
38
|
"/dev/sda" => { :snapshot_id => "s-baz" },
|
34
39
|
"/dev/sdb" => "ephemeral0"
|
35
40
|
}
|
36
41
|
}
|
37
42
|
|
43
|
+
|
44
|
+
image.should_receive(:add_tag).with("Name", {:value=>"bosh-stemcell 1.2.3"})
|
45
|
+
|
38
46
|
cloud = mock_cloud do |ec2|
|
39
47
|
ec2.volumes.stub(:[]).with("v-foo").and_return(volume)
|
40
48
|
ec2.instances.stub(:[]).with("i-current").and_return(current_instance)
|
41
49
|
ec2.images.should_receive(:create).with(image_params).and_return(image)
|
42
|
-
|
43
|
-
i1 = double("image-1", :root_device_name => "/dev/sda1",
|
44
|
-
:image_location => "pv-grub-hd00_1.03-x86_64.gz",
|
45
|
-
:image_id => "aki-b4aa75dd")
|
46
|
-
i2 = double("image-2", :root_device_name => "/dev/sda1",
|
47
|
-
:image_location => "pv-grub-hd00_1.02-x86_64.gz",
|
48
|
-
:image_id => "aki-b4aa75d0")
|
49
|
-
|
50
|
-
result = double("images_set", :images_set => [i1, i2])
|
51
|
-
client = double("client", :describe_images => result)
|
52
|
-
ec2.should_receive(:client).and_return(client)
|
53
50
|
end
|
54
51
|
|
52
|
+
cloud.should_receive(:find_aki).with("x86_64", "/dev/sda1")
|
53
|
+
.and_return("aki-b4aa75dd")
|
54
|
+
|
55
55
|
cloud.stub(:generate_unique_name).and_return(unique_name)
|
56
56
|
cloud.stub(:current_instance_id).and_return("i-current")
|
57
57
|
|
@@ -102,7 +102,9 @@ describe Bosh::AwsCloud::Cloud do
|
|
102
102
|
|
103
103
|
cloud_properties = {
|
104
104
|
"root_device_name" => "/dev/sda1",
|
105
|
-
"architecture" => "x86_64"
|
105
|
+
"architecture" => "x86_64",
|
106
|
+
"name" => "bosh-stemcell",
|
107
|
+
"version" => "1.2.3"
|
106
108
|
}
|
107
109
|
cloud.create_stemcell("/tmp/foo", cloud_properties).should == "i-bar"
|
108
110
|
end
|
data/spec/unit/create_vm_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Copyright (c) 2009-2012 VMware, Inc.
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
5
|
describe Bosh::AwsCloud::Cloud, "create_vm" do
|
6
6
|
|
@@ -81,6 +81,46 @@ describe Bosh::AwsCloud::Cloud, "create_vm" do
|
|
81
81
|
vm_id.should == "i-test"
|
82
82
|
end
|
83
83
|
|
84
|
+
it "passes dns servers in ec2 user data when present" do
|
85
|
+
unique_name = UUIDTools::UUID.random_create.to_s
|
86
|
+
|
87
|
+
user_data = {
|
88
|
+
"registry" => {
|
89
|
+
"endpoint" => "http://registry:3333"
|
90
|
+
},
|
91
|
+
"dns" => { "nameserver" => ["1.2.3.4"] }
|
92
|
+
}
|
93
|
+
|
94
|
+
sec_grp = double("security_group", :name => "default")
|
95
|
+
instance = double("instance",
|
96
|
+
:id => "i-test",
|
97
|
+
:elastic_ip => nil,
|
98
|
+
:security_groups => sec_grp)
|
99
|
+
client = double("client", :describe_images => fake_image_set)
|
100
|
+
|
101
|
+
network_spec = dynamic_network_spec
|
102
|
+
network_spec["dns"] = ["1.2.3.4"]
|
103
|
+
|
104
|
+
cloud = mock_cloud do |ec2|
|
105
|
+
ec2.instances.should_receive(:create).
|
106
|
+
with(ec2_params(user_data, %w[default])).
|
107
|
+
and_return(instance)
|
108
|
+
ec2.should_receive(:client).and_return(client)
|
109
|
+
end
|
110
|
+
|
111
|
+
cloud.should_receive(:generate_unique_name).and_return(unique_name)
|
112
|
+
cloud.should_receive(:wait_resource).with(instance, :running)
|
113
|
+
@registry.should_receive(:update_settings)
|
114
|
+
.with("i-test", agent_settings(unique_name, network_spec))
|
115
|
+
|
116
|
+
vm_id = cloud.create_vm("agent-id", "sc-id",
|
117
|
+
resource_pool_spec,
|
118
|
+
{ "network_a" => network_spec },
|
119
|
+
nil, { "test_env" => "value" })
|
120
|
+
|
121
|
+
vm_id.should == "i-test"
|
122
|
+
end
|
123
|
+
|
84
124
|
it "creates EC2 instance with security group" do
|
85
125
|
unique_name = UUIDTools::UUID.random_create.to_s
|
86
126
|
|
@@ -7,11 +7,22 @@ describe Bosh::AwsCloud::Cloud do
|
|
7
7
|
it "deregisters EC2 image" do
|
8
8
|
image = double("image", :id => "i-foo")
|
9
9
|
|
10
|
+
snapshot = double("snapshot")
|
11
|
+
snapshot.should_receive(:delete)
|
12
|
+
|
13
|
+
snapshots = double("snapshots")
|
14
|
+
snapshots.should_receive(:[]).with("snap-123").and_return(snapshot)
|
15
|
+
|
10
16
|
cloud = mock_cloud do |ec2|
|
11
17
|
ec2.images.stub(:[]).with("i-foo").and_return(image)
|
18
|
+
ec2.should_receive(:snapshots).and_return(snapshots)
|
12
19
|
end
|
13
20
|
|
14
21
|
image.should_receive(:deregister)
|
22
|
+
|
23
|
+
map = { "/dev/sda" => {:snapshot_id => "snap-123"} }
|
24
|
+
image.should_receive(:block_device_mappings).and_return(map)
|
25
|
+
|
15
26
|
cloud.delete_stemcell("i-foo")
|
16
27
|
end
|
17
28
|
|
data/spec/unit/helpers_spec.rb
CHANGED
@@ -4,15 +4,16 @@ require File.expand_path("../../spec_helper", __FILE__)
|
|
4
4
|
|
5
5
|
describe Bosh::AwsCloud::Helpers do
|
6
6
|
it "should time out" do
|
7
|
+
Bosh::Clouds::Config.stub(:task_checkpoint)
|
7
8
|
cloud = mock_cloud
|
8
9
|
|
9
10
|
resource = double("resource")
|
10
11
|
resource.stub(:status).and_return(:start)
|
11
12
|
cloud.stub(:sleep)
|
12
13
|
|
13
|
-
|
14
|
+
expect {
|
14
15
|
cloud.wait_resource(resource, :stop, :status, 0.1)
|
15
|
-
}.
|
16
|
+
}.to raise_error Bosh::Clouds::CloudError, /Timed out/
|
16
17
|
end
|
17
18
|
|
18
19
|
it "should not time out" do
|
@@ -28,15 +29,36 @@ describe Bosh::AwsCloud::Helpers do
|
|
28
29
|
end
|
29
30
|
|
30
31
|
it "should raise error when target state is wrong" do
|
32
|
+
Bosh::Clouds::Config.stub(:task_checkpoint)
|
31
33
|
cloud = mock_cloud
|
32
34
|
|
33
35
|
resource = double("resource")
|
34
36
|
resource.stub(:status).and_return(:started, :failed)
|
35
37
|
cloud.stub(:sleep)
|
36
38
|
|
37
|
-
|
39
|
+
expect {
|
38
40
|
cloud.wait_resource(resource, :stopped, :status, 0.1)
|
39
|
-
}.
|
40
|
-
|
41
|
+
}.to raise_error Bosh::Clouds::CloudError, /is failed, expected stopped/
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should swallow AWS::EC2::Errors::InvalidInstanceID::NotFound" do
|
45
|
+
Bosh::Clouds::Config.stub(:task_checkpoint)
|
46
|
+
cloud = mock_cloud
|
47
|
+
|
48
|
+
resource = double("resource")
|
49
|
+
return_values = [:raise, :raise, :raise, :start, :start, :stop]
|
50
|
+
i = 0
|
51
|
+
resource.stub(:status) do
|
52
|
+
i += 1
|
53
|
+
if return_values[i] == :raise
|
54
|
+
raise AWS::EC2::Errors::InvalidInstanceID::NotFound
|
55
|
+
end
|
56
|
+
return_values[i]
|
57
|
+
end
|
58
|
+
cloud.stub(:sleep)
|
59
|
+
|
60
|
+
#lambda {
|
61
|
+
cloud.wait_resource(resource, :stop, :status, 0.1)
|
62
|
+
#}.should_not raise_error AWS::EC2::Errors::InvalidInstanceID::NotFound
|
41
63
|
end
|
42
64
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe Bosh::AwsCloud::Cloud, "#set_vm_metadata" do
|
6
|
+
before :each do
|
7
|
+
@instance = double("instance", :id => "i-foobar")
|
8
|
+
|
9
|
+
@cloud = mock_cloud(mock_cloud_options) do |ec2|
|
10
|
+
ec2.instances.stub(:[]).with("i-foobar").and_return(@instance)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should add new tags" do
|
15
|
+
metadata = {:job => "job", :index => "index"}
|
16
|
+
@cloud.should_receive(:tag).with(@instance, :job, "job")
|
17
|
+
@cloud.should_receive(:tag).with(@instance, :index, "index")
|
18
|
+
@cloud.should_receive(:tag).with(@instance, "Name", "job/index")
|
19
|
+
@cloud.set_vm_metadata("i-foobar", metadata)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should trim key and value length" do
|
23
|
+
metadata = {"x"*128 => "y"*256}
|
24
|
+
@instance.should_receive(:add_tag) do |key, options|
|
25
|
+
key.size.should == 127
|
26
|
+
options[:value].size.should == 255
|
27
|
+
end
|
28
|
+
@cloud.set_vm_metadata("i-foobar", metadata)
|
29
|
+
end
|
30
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bosh_aws_cpi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.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:
|
12
|
+
date: 2013-01-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: aws-sdk
|
@@ -50,7 +50,7 @@ dependencies:
|
|
50
50
|
requirements:
|
51
51
|
- - ! '>='
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0.
|
53
|
+
version: 0.5.1
|
54
54
|
type: :runtime
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -58,7 +58,7 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.
|
61
|
+
version: 0.5.1
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
63
|
name: httpclient
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -117,6 +117,7 @@ files:
|
|
117
117
|
- bin/bosh_aws_console
|
118
118
|
- lib/bosh_aws_cpi.rb
|
119
119
|
- lib/cloud/aws.rb
|
120
|
+
- lib/cloud/aws/aki_picker.rb
|
120
121
|
- lib/cloud/aws/cloud.rb
|
121
122
|
- lib/cloud/aws/dynamic_network.rb
|
122
123
|
- lib/cloud/aws/helpers.rb
|
@@ -125,11 +126,12 @@ files:
|
|
125
126
|
- lib/cloud/aws/registry_client.rb
|
126
127
|
- lib/cloud/aws/version.rb
|
127
128
|
- lib/cloud/aws/vip_network.rb
|
128
|
-
- README
|
129
|
+
- README.md
|
129
130
|
- Rakefile
|
130
131
|
- spec/assets/stemcell-copy
|
131
132
|
- spec/integration/cpi_test.rb
|
132
133
|
- spec/spec_helper.rb
|
134
|
+
- spec/unit/aki_picker_spec.rb
|
133
135
|
- spec/unit/attach_disk_spec.rb
|
134
136
|
- spec/unit/cloud_spec.rb
|
135
137
|
- spec/unit/configure_networks_spec.rb
|
@@ -143,6 +145,7 @@ files:
|
|
143
145
|
- spec/unit/helpers_spec.rb
|
144
146
|
- spec/unit/network_configurator_spec.rb
|
145
147
|
- spec/unit/reboot_vm_spec.rb
|
148
|
+
- spec/unit/set_vm_metadata_spec.rb
|
146
149
|
- spec/unit/validate_deployment_spec.rb
|
147
150
|
homepage: http://www.vmware.com
|
148
151
|
licenses: []
|
@@ -158,7 +161,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
158
161
|
version: '0'
|
159
162
|
segments:
|
160
163
|
- 0
|
161
|
-
hash: -
|
164
|
+
hash: -1915781663242535072
|
162
165
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
166
|
none: false
|
164
167
|
requirements:
|
@@ -167,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
167
170
|
version: '0'
|
168
171
|
segments:
|
169
172
|
- 0
|
170
|
-
hash: -
|
173
|
+
hash: -1915781663242535072
|
171
174
|
requirements: []
|
172
175
|
rubyforge_project:
|
173
176
|
rubygems_version: 1.8.24
|
@@ -178,6 +181,7 @@ test_files:
|
|
178
181
|
- spec/assets/stemcell-copy
|
179
182
|
- spec/integration/cpi_test.rb
|
180
183
|
- spec/spec_helper.rb
|
184
|
+
- spec/unit/aki_picker_spec.rb
|
181
185
|
- spec/unit/attach_disk_spec.rb
|
182
186
|
- spec/unit/cloud_spec.rb
|
183
187
|
- spec/unit/configure_networks_spec.rb
|
@@ -191,4 +195,6 @@ test_files:
|
|
191
195
|
- spec/unit/helpers_spec.rb
|
192
196
|
- spec/unit/network_configurator_spec.rb
|
193
197
|
- spec/unit/reboot_vm_spec.rb
|
198
|
+
- spec/unit/set_vm_metadata_spec.rb
|
194
199
|
- spec/unit/validate_deployment_spec.rb
|
200
|
+
has_rdoc:
|
data/README
DELETED