bosh_aws_cpi 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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