bosh_openstack_cpi 0.0.7 → 1.5.0.pre.1113
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +0 -5
- data/USAGE.md +55 -27
- data/bin/bosh_openstack_console +2 -10
- data/lib/bosh_openstack_cpi.rb +1 -0
- data/lib/cloud/openstack.rb +7 -2
- data/lib/cloud/openstack/cloud.rb +345 -261
- data/lib/cloud/openstack/dynamic_network.rb +1 -0
- data/lib/cloud/openstack/helpers.rb +77 -19
- data/lib/cloud/openstack/manual_network.rb +35 -0
- data/lib/cloud/openstack/network.rb +1 -0
- data/lib/cloud/openstack/network_configurator.rb +76 -37
- data/lib/cloud/openstack/tag_manager.rb +17 -0
- data/lib/cloud/openstack/version.rb +2 -1
- data/lib/cloud/openstack/vip_network.rb +15 -12
- metadata +40 -75
- data/Rakefile +0 -62
- data/bosh_cpi.md +0 -82
- data/lib/cloud/openstack/registry_client.rb +0 -127
- data/spec/assets/sample_config.yml +0 -14
- data/spec/integration/cpi_test.rb +0 -119
- data/spec/spec_helper.rb +0 -146
- data/spec/unit/attach_disk_spec.rb +0 -111
- data/spec/unit/cloud_spec.rb +0 -31
- data/spec/unit/configure_networks_spec.rb +0 -106
- data/spec/unit/create_disk_spec.rb +0 -82
- data/spec/unit/create_stemcell_spec.rb +0 -315
- data/spec/unit/create_vm_spec.rb +0 -252
- data/spec/unit/delete_disk_spec.rb +0 -37
- data/spec/unit/delete_stemcell_spec.rb +0 -80
- data/spec/unit/delete_vm_spec.rb +0 -25
- data/spec/unit/detach_disk_spec.rb +0 -78
- data/spec/unit/helpers_spec.rb +0 -74
- data/spec/unit/network_configurator_spec.rb +0 -57
- data/spec/unit/reboot_vm_spec.rb +0 -32
- data/spec/unit/set_vm_metadata_spec.rb +0 -34
- data/spec/unit/validate_deployment_spec.rb +0 -17
data/README.md
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# OpenStack BOSH Cloud Provider Interface
|
2
2
|
|
3
|
-
[![Build Status](https://secure.travis-ci.org/piston/openstack-bosh-cpi.png)](http://travis-ci.org/piston/openstack-bosh-cpi)
|
4
|
-
|
5
3
|
## Bringing the world’s most popular open source platform-as-a-service to the world’s most popular open source infrastructure-as-a-service platform
|
6
4
|
|
7
5
|
This repo contains software designed to manage the deployment of Cloud Foundry on top of OpenStack, using Cloud Foundry BOSH. Say what?
|
@@ -18,9 +16,6 @@ Cloud Foundry is the leading open source platform-as-a-service (PaaS) offering w
|
|
18
16
|
|
19
17
|
Cloud Foundry BOSH is an open source tool chain for release engineering, deployment and lifecycle management of large scale distributed services. In this manual we describe the architecture, topology, configuration, and use of BOSH, as well as the structure and conventions used in packaging and deployment.
|
20
18
|
|
21
|
-
* BOSH Source Code: https://github.com/cloudfoundry/bosh
|
22
|
-
* BOSH Documentation: https://github.com/cloudfoundry/oss-docs/blob/master/bosh/documentation.md
|
23
|
-
|
24
19
|
## OpenStack and Cloud Foundry, Together using BOSH
|
25
20
|
|
26
21
|
Cloud Foundry BOSH defines a Cloud Provider Interface API that enables platform-as-a-service deployment across multiple cloud providers - initially VMWare's vSphere and AWS. Piston Cloud has partnered with VMWare to provide a CPI for OpenStack, opening up Cloud Foundry deployment to an entire ecosystem of public and private OpenStack deployments.
|
data/USAGE.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# BOSH OpenStack Cloud Provider Interface
|
2
|
+
# Copyright (c) 2009-2013 VMware, Inc.
|
2
3
|
# Copyright (c) 2012 Piston Cloud Computing, Inc.
|
3
4
|
|
4
5
|
For online documentation see: http://rubydoc.info/gems/bosh_openstack_cpi/
|
@@ -9,6 +10,8 @@ These options are passed to the OpenStack CPI when it is instantiated.
|
|
9
10
|
|
10
11
|
### OpenStack options
|
11
12
|
|
13
|
+
The registry options are passed to the Openstack CPI by the BOSH director based on the settings in `director.yml`:
|
14
|
+
|
12
15
|
* `auth_url` (required)
|
13
16
|
URL of the OpenStack Identity endpoint to connect to
|
14
17
|
* `username` (required)
|
@@ -20,45 +23,55 @@ These options are passed to the OpenStack CPI when it is instantiated.
|
|
20
23
|
* `region` (optional)
|
21
24
|
OpenStack region
|
22
25
|
* `endpoint_type` (optional)
|
23
|
-
OpenStack endpoint type
|
26
|
+
OpenStack endpoint type (publicURL (default), adminURL, internalURL)
|
27
|
+
* `state_timeout` (optional)
|
28
|
+
Timeout (in seconds) for OpenStack resources desired state (by default 300)
|
29
|
+
* `stemcell_public_visibility` (optional)
|
30
|
+
Set public visibility for stemcells (true or false (default))
|
24
31
|
* `default_key_name` (required)
|
25
32
|
default OpenStack ssh key name to assign to created virtual machines
|
26
33
|
* `default_security_group` (required)
|
27
34
|
default OpenStack security group to assign to created virtual machines
|
28
|
-
* `private_key` (required)
|
29
|
-
local path to the ssh private key, must match `default_key_name`
|
30
35
|
|
31
36
|
### Registry options
|
32
37
|
|
33
|
-
The registry options are passed to the Openstack CPI by the BOSH director based on the settings in `director.yml
|
38
|
+
The registry options are passed to the Openstack CPI by the BOSH director based on the settings in `director.yml`:
|
34
39
|
|
35
40
|
* `endpoint` (required)
|
36
41
|
OpenStack registry URL
|
37
42
|
* `user` (required)
|
38
43
|
OpenStack registry user
|
39
44
|
* `password` (required)
|
40
|
-
|
45
|
+
OpenStack registry password
|
41
46
|
|
42
47
|
### Agent options
|
43
48
|
|
44
|
-
|
49
|
+
The agent options are passed to the OpenStack CPI by the BOSH director based on the settings in `director.yml`:
|
50
|
+
|
51
|
+
## Network options
|
52
|
+
|
53
|
+
The OpenStack CPI supports these networks types:
|
54
|
+
|
55
|
+
* `type` (required)
|
56
|
+
can be `dynamic` for a DHCP assigned IP by OpenStack, `manual` for a static IP assigned manually at the BOSH deployment manifest or `vip` to use a Floating IP (which needs to be already allocated)
|
45
57
|
|
46
|
-
|
58
|
+
These options are specified under `cloud_properties` in the `networks` section of a BOSH deployment manifest:
|
47
59
|
|
48
|
-
|
60
|
+
* `security_groups` (optional)
|
61
|
+
the OpenStack security groups to assign to VMs. If not specified, it'll use the default security groups set at the OpenStack options
|
62
|
+
|
63
|
+
* `net_id` (required for `manual` networks)
|
64
|
+
the OpenStack Quantum network UUID to attach as a NIC to VMs.
|
65
|
+
|
66
|
+
## Resource pool options
|
67
|
+
|
68
|
+
These options are specified under `cloud_properties` in the `resource_pools` section of a BOSH deployment manifest:
|
49
69
|
|
50
70
|
* `instance_type` (required)
|
51
71
|
which type of instance (OpenStack flavor) the VMs should belong to
|
52
72
|
* `availability_zone` (optional)
|
53
73
|
the OpenStack availability zone the VMs should be created in
|
54
74
|
|
55
|
-
### Network options
|
56
|
-
|
57
|
-
These options are specified under `cloud_options` in the `networks` section of a BOSH deployment manifest.
|
58
|
-
|
59
|
-
* `type` (required)
|
60
|
-
can be either `dynamic` for a DHCP assigned IP by OpenStack, or `vip` to use a Floating IP (which needs to be already allocated)
|
61
|
-
|
62
75
|
## Example
|
63
76
|
|
64
77
|
This is a sample of how OpenStack specific properties are used in a BOSH deployment manifest:
|
@@ -70,36 +83,51 @@ This is a sample of how OpenStack specific properties are used in a BOSH deploym
|
|
70
83
|
...
|
71
84
|
|
72
85
|
networks:
|
73
|
-
- name: nginx_network
|
74
|
-
type: vip
|
75
|
-
cloud_properties: {}
|
76
86
|
- name: default
|
77
87
|
type: dynamic
|
78
88
|
cloud_properties:
|
79
89
|
security_groups:
|
80
|
-
|
81
|
-
|
90
|
+
- default
|
91
|
+
net_id: 2438bca2-24fa-450f-ae7b-ec2e53b51984
|
92
|
+
- name: static
|
93
|
+
type: manual
|
94
|
+
subnets:
|
95
|
+
- name: private
|
96
|
+
range: 10.0.1.0/24
|
97
|
+
gateway: 10.0.1.1
|
98
|
+
reserved:
|
99
|
+
- 10.0.1.2 - 10.0.1.9
|
100
|
+
static:
|
101
|
+
- 10.0.1.10 - 10.0.1.20
|
102
|
+
cloud_properties:
|
103
|
+
security_groups:
|
104
|
+
- default
|
105
|
+
net_id: 8d8b84b4-faa6-4605-9fbf-c179bdae4282
|
106
|
+
- name: floating
|
107
|
+
type: vip
|
108
|
+
cloud_properties: {}
|
82
109
|
...
|
83
110
|
|
84
111
|
resource_pools:
|
85
112
|
- name: common
|
86
113
|
network: default
|
87
|
-
size:
|
114
|
+
size: 1
|
88
115
|
stemcell:
|
89
|
-
name: bosh-
|
90
|
-
version:
|
116
|
+
name: bosh-openstack-kvm-ubuntu
|
117
|
+
version: latest
|
91
118
|
cloud_properties:
|
92
119
|
instance_type: m1.small
|
120
|
+
availability_zone:
|
93
121
|
|
94
122
|
...
|
95
123
|
|
96
124
|
properties:
|
97
125
|
openstack:
|
98
|
-
auth_url: http://pistoncloud.com/:5000/v2.0
|
126
|
+
auth_url: http://pistoncloud.com/:5000/v2.0
|
99
127
|
username: christopher
|
100
128
|
api_key: QRoqsenPsNGX6
|
101
129
|
tenant: Bosh
|
102
|
-
region:
|
130
|
+
region: RegionOne
|
131
|
+
endpoint_type: publicURL
|
103
132
|
default_key_name: bosh
|
104
|
-
default_security_groups: ["bosh"]
|
105
|
-
private_key: /home/bosh/.ssh/bosh.pem
|
133
|
+
default_security_groups: ["bosh"]
|
data/bin/bosh_openstack_console
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
# Copyright (c) 2009-2013 VMware, Inc.
|
3
4
|
# Copyright (c) 2012 Piston Cloud Computing, Inc.
|
4
5
|
|
5
6
|
# Usage example:
|
@@ -10,15 +11,6 @@
|
|
10
11
|
# [],
|
11
12
|
# {"foo" =>"bar"})
|
12
13
|
|
13
|
-
gemfile = File.expand_path("../../Gemfile", __FILE__)
|
14
|
-
|
15
|
-
if File.exists?(gemfile)
|
16
|
-
ENV["BUNDLE_GEMFILE"] = gemfile
|
17
|
-
require "rubygems"
|
18
|
-
require "bundler/setup"
|
19
|
-
end
|
20
|
-
|
21
|
-
$:.unshift(File.expand_path("../../lib", __FILE__))
|
22
14
|
require "bosh_openstack_cpi"
|
23
15
|
require "irb"
|
24
16
|
require "irb/completion"
|
@@ -38,7 +30,7 @@ unless config_file
|
|
38
30
|
exit(1)
|
39
31
|
end
|
40
32
|
|
41
|
-
@config =
|
33
|
+
@config = Psych.load_file(config_file)
|
42
34
|
|
43
35
|
module ConsoleHelpers
|
44
36
|
def cpi
|
data/lib/bosh_openstack_cpi.rb
CHANGED
data/lib/cloud/openstack.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# Copyright (c) 2009-2013 VMware, Inc.
|
1
2
|
# Copyright (c) 2012 Piston Cloud Computing, Inc.
|
2
3
|
|
3
4
|
module Bosh
|
@@ -6,24 +7,28 @@ end
|
|
6
7
|
|
7
8
|
require "fog"
|
8
9
|
require "httpclient"
|
10
|
+
require "json"
|
9
11
|
require "pp"
|
10
12
|
require "set"
|
11
13
|
require "tmpdir"
|
12
|
-
require "
|
14
|
+
require "securerandom"
|
13
15
|
require "yajl"
|
14
16
|
|
17
|
+
require "common/exec"
|
15
18
|
require "common/thread_pool"
|
16
19
|
require "common/thread_formatter"
|
17
20
|
|
21
|
+
require 'bosh/registry/client'
|
18
22
|
require "cloud"
|
19
23
|
require "cloud/openstack/helpers"
|
20
24
|
require "cloud/openstack/cloud"
|
21
|
-
require "cloud/openstack/
|
25
|
+
require "cloud/openstack/tag_manager"
|
22
26
|
require "cloud/openstack/version"
|
23
27
|
|
24
28
|
require "cloud/openstack/network_configurator"
|
25
29
|
require "cloud/openstack/network"
|
26
30
|
require "cloud/openstack/dynamic_network"
|
31
|
+
require "cloud/openstack/manual_network"
|
27
32
|
require "cloud/openstack/vip_network"
|
28
33
|
|
29
34
|
module Bosh
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# Copyright (c) 2009-2013 VMware, Inc.
|
1
2
|
# Copyright (c) 2012 Piston Cloud Computing, Inc.
|
2
3
|
|
3
4
|
module Bosh::OpenStackCloud
|
@@ -6,6 +7,9 @@ module Bosh::OpenStackCloud
|
|
6
7
|
class Cloud < Bosh::Cloud
|
7
8
|
include Helpers
|
8
9
|
|
10
|
+
BOSH_APP_DIR = "/var/vcap/bosh"
|
11
|
+
FIRST_DEVICE_NAME_LETTER = "b"
|
12
|
+
|
9
13
|
attr_reader :openstack
|
10
14
|
attr_reader :registry
|
11
15
|
attr_reader :glance
|
@@ -22,15 +26,21 @@ module Bosh::OpenStackCloud
|
|
22
26
|
@options = options.dup
|
23
27
|
|
24
28
|
validate_options
|
29
|
+
initialize_registry
|
25
30
|
|
26
31
|
@logger = Bosh::Clouds::Config.logger
|
27
32
|
|
28
33
|
@agent_properties = @options["agent"] || {}
|
29
34
|
@openstack_properties = @options["openstack"]
|
30
|
-
@registry_properties = @options["registry"]
|
31
35
|
|
32
36
|
@default_key_name = @openstack_properties["default_key_name"]
|
33
37
|
@default_security_groups = @openstack_properties["default_security_groups"]
|
38
|
+
@state_timeout = @openstack_properties["state_timeout"]
|
39
|
+
@stemcell_public_visibility = @openstack_properties["stemcell_public_visibility"]
|
40
|
+
|
41
|
+
unless @openstack_properties["auth_url"].match(/\/tokens$/)
|
42
|
+
@openstack_properties["auth_url"] = @openstack_properties["auth_url"] + "/tokens"
|
43
|
+
end
|
34
44
|
|
35
45
|
openstack_params = {
|
36
46
|
:provider => "OpenStack",
|
@@ -38,9 +48,15 @@ module Bosh::OpenStackCloud
|
|
38
48
|
:openstack_username => @openstack_properties["username"],
|
39
49
|
:openstack_api_key => @openstack_properties["api_key"],
|
40
50
|
:openstack_tenant => @openstack_properties["tenant"],
|
41
|
-
:openstack_region => @openstack_properties["region"]
|
51
|
+
:openstack_region => @openstack_properties["region"],
|
52
|
+
:openstack_endpoint_type => @openstack_properties["endpoint_type"]
|
42
53
|
}
|
43
|
-
|
54
|
+
begin
|
55
|
+
@openstack = Fog::Compute.new(openstack_params)
|
56
|
+
rescue Exception => e
|
57
|
+
@logger.error(e)
|
58
|
+
cloud_error("Unable to connect to the OpenStack Compute API. Check task debug log for details.")
|
59
|
+
end
|
44
60
|
|
45
61
|
glance_params = {
|
46
62
|
:provider => "OpenStack",
|
@@ -49,17 +65,14 @@ module Bosh::OpenStackCloud
|
|
49
65
|
:openstack_api_key => @openstack_properties["api_key"],
|
50
66
|
:openstack_tenant => @openstack_properties["tenant"],
|
51
67
|
:openstack_region => @openstack_properties["region"],
|
52
|
-
:openstack_endpoint_type => @openstack_properties["endpoint_type"]
|
53
|
-
"publicURL"
|
68
|
+
:openstack_endpoint_type => @openstack_properties["endpoint_type"]
|
54
69
|
}
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
registry_user,
|
62
|
-
registry_password)
|
70
|
+
begin
|
71
|
+
@glance = Fog::Image.new(glance_params)
|
72
|
+
rescue Exception => e
|
73
|
+
@logger.error(e)
|
74
|
+
cloud_error("Unable to connect to the OpenStack Image Service API. Check task debug log for details.")
|
75
|
+
end
|
63
76
|
|
64
77
|
@metadata_lock = Mutex.new
|
65
78
|
end
|
@@ -75,12 +88,8 @@ module Bosh::OpenStackCloud
|
|
75
88
|
# @option cloud_properties [String] infrastructure Stemcell infraestructure
|
76
89
|
# @option cloud_properties [String] disk_format Image disk format
|
77
90
|
# @option cloud_properties [String] container_format Image container format
|
78
|
-
# @option cloud_properties [optional, String] kernel_id UUID of the kernel
|
79
|
-
# image stored at OpenStack
|
80
91
|
# @option cloud_properties [optional, String] kernel_file Name of the
|
81
92
|
# kernel image file provided at the stemcell archive
|
82
|
-
# @option cloud_properties [optional, String] ramdisk_id UUID of the
|
83
|
-
# ramdisk image stored at OpenStack
|
84
93
|
# @option cloud_properties [optional, String] ramdisk_file Name of the
|
85
94
|
# ramdisk image file provided at the stemcell archive
|
86
95
|
# @return [String] OpenStack image UUID of the stemcell
|
@@ -88,83 +97,41 @@ module Bosh::OpenStackCloud
|
|
88
97
|
with_thread_name("create_stemcell(#{image_path}...)") do
|
89
98
|
begin
|
90
99
|
Dir.mktmpdir do |tmp_dir|
|
91
|
-
@logger.info("
|
92
|
-
image_name = "BOSH-#{generate_unique_name}"
|
93
|
-
|
94
|
-
# 1. Unpack image to temp directory
|
95
|
-
unpack_image(tmp_dir, image_path)
|
96
|
-
root_image = File.join(tmp_dir, "root.img")
|
97
|
-
|
98
|
-
# 2. If image contains a kernel file, upload it to glance service
|
99
|
-
kernel_id = nil
|
100
|
-
if cloud_properties["kernel_id"]
|
101
|
-
kernel_id = cloud_properties["kernel_id"]
|
102
|
-
elsif cloud_properties["kernel_file"]
|
103
|
-
kernel_image = File.join(tmp_dir, cloud_properties["kernel_file"])
|
104
|
-
unless File.exists?(kernel_image)
|
105
|
-
cloud_error("Kernel image " \
|
106
|
-
"#{cloud_properties['kernel_file']} " \
|
107
|
-
"is missing from stemcell archive")
|
108
|
-
end
|
109
|
-
kernel_params = {
|
110
|
-
:name => "#{image_name}-AKI",
|
111
|
-
:disk_format => "aki",
|
112
|
-
:container_format => "aki",
|
113
|
-
:location => kernel_image,
|
114
|
-
:properties => {
|
115
|
-
:stemcell => image_name
|
116
|
-
}
|
117
|
-
}
|
118
|
-
@logger.info("Uploading kernel image...")
|
119
|
-
kernel_id = upload_image(kernel_params)
|
120
|
-
end
|
121
|
-
|
122
|
-
# 3. If image contains a ramdisk file, upload it to glance service
|
123
|
-
ramdisk_id = nil
|
124
|
-
if cloud_properties["ramdisk_id"]
|
125
|
-
ramdisk_id = cloud_properties["ramdisk_id"]
|
126
|
-
elsif cloud_properties["ramdisk_file"]
|
127
|
-
ramdisk_image = File.join(tmp_dir, cloud_properties["ramdisk_file"])
|
128
|
-
unless File.exists?(ramdisk_image)
|
129
|
-
cloud_error("Ramdisk image " \
|
130
|
-
"#{cloud_properties['ramdisk_file']} " \
|
131
|
-
"is missing from stemcell archive")
|
132
|
-
end
|
133
|
-
ramdisk_params = {
|
134
|
-
:name => "#{image_name}-ARI",
|
135
|
-
:disk_format => "ari",
|
136
|
-
:container_format => "ari",
|
137
|
-
:location => ramdisk_image,
|
138
|
-
:properties => {
|
139
|
-
:stemcell => image_name
|
140
|
-
}
|
141
|
-
}
|
142
|
-
@logger.info("Uploading ramdisk image...")
|
143
|
-
ramdisk_id = upload_image(ramdisk_params)
|
144
|
-
end
|
145
|
-
|
146
|
-
# 4. Upload image using Glance service
|
100
|
+
@logger.info("Creating new image...")
|
147
101
|
image_params = {
|
148
|
-
:name =>
|
102
|
+
:name => "BOSH-#{generate_unique_name}",
|
149
103
|
:disk_format => cloud_properties["disk_format"],
|
150
104
|
:container_format => cloud_properties["container_format"],
|
151
|
-
:
|
152
|
-
:is_public => true
|
105
|
+
:is_public => @stemcell_public_visibility.nil? ? false : @stemcell_public_visibility,
|
153
106
|
}
|
107
|
+
|
154
108
|
image_properties = {}
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
109
|
+
vanilla_options = ["name", "version", "os_type", "os_distro", "architecture", "auto_disk_config"]
|
110
|
+
vanilla_options.reject{ |o| cloud_properties[o].nil? }.each do |key|
|
111
|
+
image_properties[key.to_sym] = cloud_properties[key]
|
112
|
+
end
|
113
|
+
image_params[:properties] = image_properties unless image_properties.empty?
|
114
|
+
|
115
|
+
# If image_location is set in cloud properties, then pass the copy-from parm. Then Glance will fetch it
|
116
|
+
# from the remote location on a background job and store it in its repository.
|
117
|
+
# Otherwise, unpack image to temp directory and upload to Glance the root image.
|
118
|
+
if cloud_properties["image_location"]
|
119
|
+
@logger.info("Using remote image from `#{cloud_properties["image_location"]}'...")
|
120
|
+
image_params[:copy_from] = cloud_properties["image_location"]
|
121
|
+
else
|
122
|
+
@logger.info("Extracting stemcell file to `#{tmp_dir}'...")
|
123
|
+
unpack_image(tmp_dir, image_path)
|
124
|
+
image_params[:location] = File.join(tmp_dir, "root.img")
|
165
125
|
end
|
166
|
-
|
167
|
-
|
126
|
+
|
127
|
+
# Upload image using Glance service
|
128
|
+
@logger.debug("Using image parms: `#{image_params.inspect}'")
|
129
|
+
image = with_openstack { @glance.images.create(image_params) }
|
130
|
+
|
131
|
+
@logger.info("Creating new image `#{image.id}'...")
|
132
|
+
wait_resource(image, :active)
|
133
|
+
|
134
|
+
image.id.to_s
|
168
135
|
end
|
169
136
|
rescue => e
|
170
137
|
@logger.error(e)
|
@@ -182,33 +149,9 @@ module Bosh::OpenStackCloud
|
|
182
149
|
def delete_stemcell(stemcell_id)
|
183
150
|
with_thread_name("delete_stemcell(#{stemcell_id})") do
|
184
151
|
@logger.info("Deleting stemcell `#{stemcell_id}'...")
|
185
|
-
image = @glance.images.find_by_id(stemcell_id)
|
152
|
+
image = with_openstack { @glance.images.find_by_id(stemcell_id) }
|
186
153
|
if image
|
187
|
-
|
188
|
-
if kernel_id
|
189
|
-
kernel = @glance.images.find_by_id(kernel_id)
|
190
|
-
if kernel && kernel.properties["stemcell"]
|
191
|
-
if kernel.properties["stemcell"] == image.name
|
192
|
-
@logger.info("Deleting kernel `#{kernel_id}'...")
|
193
|
-
kernel.destroy
|
194
|
-
@logger.info("Kernel `#{kernel_id}' is now deleted")
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
ramdisk_id = image.properties["ramdisk_id"]
|
200
|
-
if ramdisk_id
|
201
|
-
ramdisk = @glance.images.find_by_id(ramdisk_id)
|
202
|
-
if ramdisk && ramdisk.properties["stemcell"]
|
203
|
-
if ramdisk.properties["stemcell"] == image.name
|
204
|
-
@logger.info("Deleting ramdisk `#{ramdisk_id}'...")
|
205
|
-
ramdisk.destroy
|
206
|
-
@logger.info("Ramdisk `#{ramdisk_id}' is now deleted")
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
image.destroy
|
154
|
+
with_openstack { image.destroy }
|
212
155
|
@logger.info("Stemcell `#{stemcell_id}' is now deleted")
|
213
156
|
else
|
214
157
|
@logger.info("Stemcell `#{stemcell_id}' not found. Skipping.")
|
@@ -240,50 +183,76 @@ module Bosh::OpenStackCloud
|
|
240
183
|
server_name = "vm-#{generate_unique_name}"
|
241
184
|
|
242
185
|
network_configurator = NetworkConfigurator.new(network_spec)
|
243
|
-
security_groups =
|
244
|
-
network_configurator.security_groups(@default_security_groups)
|
245
|
-
@logger.debug("Using security groups: `#{security_groups.join(', ')}'")
|
246
186
|
|
247
|
-
|
248
|
-
|
249
|
-
|
187
|
+
openstack_security_groups = with_openstack { @openstack.security_groups }.collect { |sg| sg.name }
|
188
|
+
security_groups = network_configurator.security_groups(@default_security_groups)
|
189
|
+
security_groups.each do |sg|
|
190
|
+
cloud_error("Security group `#{sg}' not found") unless openstack_security_groups.include?(sg)
|
250
191
|
end
|
192
|
+
@logger.debug("Using security groups: `#{security_groups.join(', ')}'")
|
193
|
+
|
194
|
+
nics = network_configurator.nics
|
195
|
+
@logger.debug("Using NICs: `#{nics.join(', ')}'")
|
196
|
+
|
197
|
+
image = with_openstack { @openstack.images.find { |i| i.id == stemcell_id } }
|
198
|
+
cloud_error("Image `#{stemcell_id}' not found") if image.nil?
|
251
199
|
@logger.debug("Using image: `#{stemcell_id}'")
|
252
200
|
|
253
|
-
flavor = @openstack.flavors.find { |f|
|
254
|
-
|
255
|
-
if flavor
|
256
|
-
|
201
|
+
flavor = with_openstack { @openstack.flavors.find { |f| f.name == resource_pool["instance_type"] } }
|
202
|
+
cloud_error("Flavor `#{resource_pool["instance_type"]}' not found") if flavor.nil?
|
203
|
+
if flavor_has_ephemeral_disk?(flavor)
|
204
|
+
if flavor.ram
|
205
|
+
# Ephemeral disk size should be at least the double of the vm total memory size, as agent will need:
|
206
|
+
# - vm total memory size for swapon,
|
207
|
+
# - the rest for /vcar/vcap/data
|
208
|
+
min_ephemeral_size = (flavor.ram / 1024) * 2
|
209
|
+
if flavor.ephemeral < min_ephemeral_size
|
210
|
+
cloud_error("Flavor `#{resource_pool["instance_type"]}' should have at least #{min_ephemeral_size}Gb " +
|
211
|
+
"of ephemeral disk")
|
212
|
+
end
|
213
|
+
end
|
257
214
|
end
|
258
215
|
@logger.debug("Using flavor: `#{resource_pool["instance_type"]}'")
|
259
216
|
|
217
|
+
keyname = resource_pool["key_name"] || @default_key_name
|
218
|
+
keypair = with_openstack { @openstack.key_pairs.find { |k| k.name == keyname } }
|
219
|
+
cloud_error("Key-pair `#{keyname}' not found") if keypair.nil?
|
220
|
+
@logger.debug("Using key-pair: `#{keypair.name}' (#{keypair.fingerprint})")
|
221
|
+
|
260
222
|
server_params = {
|
261
223
|
:name => server_name,
|
262
224
|
:image_ref => image.id,
|
263
225
|
:flavor_ref => flavor.id,
|
264
|
-
:key_name =>
|
226
|
+
:key_name => keypair.name,
|
265
227
|
:security_groups => security_groups,
|
266
|
-
:
|
267
|
-
|
228
|
+
:nics => nics,
|
229
|
+
:user_data => Yajl::Encoder.encode(user_data(server_name, network_spec)),
|
230
|
+
:personality => [{
|
231
|
+
"path" => "#{BOSH_APP_DIR}/user_data.json",
|
232
|
+
"contents" => Yajl::Encoder.encode(user_data(server_name, network_spec, keypair.public_key))
|
233
|
+
}]
|
268
234
|
}
|
269
235
|
|
270
|
-
availability_zone = select_availability_zone(disk_locality,
|
271
|
-
|
272
|
-
if availability_zone
|
273
|
-
server_params[:availability_zone] = availability_zone
|
274
|
-
end
|
236
|
+
availability_zone = select_availability_zone(disk_locality, resource_pool["availability_zone"])
|
237
|
+
server_params[:availability_zone] = availability_zone if availability_zone
|
275
238
|
|
276
|
-
|
239
|
+
@logger.debug("Using boot parms: `#{server_params.inspect}'")
|
240
|
+
server = with_openstack { @openstack.servers.create(server_params) }
|
277
241
|
|
278
242
|
@logger.info("Creating new server `#{server.id}'...")
|
279
|
-
|
243
|
+
begin
|
244
|
+
wait_resource(server, :active, :state)
|
245
|
+
rescue Bosh::Clouds::CloudError => e
|
246
|
+
@logger.warn("Failed to create server: #{e.message}")
|
247
|
+
raise Bosh::Clouds::VMCreationFailed.new(true)
|
248
|
+
end
|
280
249
|
|
281
250
|
@logger.info("Configuring network for server `#{server.id}'...")
|
282
251
|
network_configurator.configure(@openstack, server)
|
283
252
|
|
284
253
|
@logger.info("Updating settings for server `#{server.id}'...")
|
285
|
-
settings = initial_agent_settings(server_name, agent_id, network_spec,
|
286
|
-
|
254
|
+
settings = initial_agent_settings(server_name, agent_id, network_spec, environment,
|
255
|
+
flavor_has_ephemeral_disk?(flavor))
|
287
256
|
@registry.update_settings(server.name, settings)
|
288
257
|
|
289
258
|
server.id.to_s
|
@@ -298,10 +267,10 @@ module Bosh::OpenStackCloud
|
|
298
267
|
def delete_vm(server_id)
|
299
268
|
with_thread_name("delete_vm(#{server_id})") do
|
300
269
|
@logger.info("Deleting server `#{server_id}'...")
|
301
|
-
server = @openstack.servers.get(server_id)
|
270
|
+
server = with_openstack { @openstack.servers.get(server_id) }
|
302
271
|
if server
|
303
|
-
server.destroy
|
304
|
-
wait_resource(server, :terminated, :state, true)
|
272
|
+
with_openstack { server.destroy }
|
273
|
+
wait_resource(server, [:terminated, :deleted], :state, true)
|
305
274
|
|
306
275
|
@logger.info("Deleting settings for server `#{server.id}'...")
|
307
276
|
@registry.delete_settings(server.name)
|
@@ -311,6 +280,18 @@ module Bosh::OpenStackCloud
|
|
311
280
|
end
|
312
281
|
end
|
313
282
|
|
283
|
+
##
|
284
|
+
# Checks if an OpenStack server exists
|
285
|
+
#
|
286
|
+
# @param [String] server_id OpenStack server UUID
|
287
|
+
# @return [Boolean] True if the vm exists
|
288
|
+
def has_vm?(server_id)
|
289
|
+
with_thread_name("has_vm?(#{server_id})") do
|
290
|
+
server = with_openstack { @openstack.servers.get(server_id) }
|
291
|
+
!server.nil? && ![:terminated, :deleted].include?(server.state.downcase.to_sym)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
314
295
|
##
|
315
296
|
# Reboots an OpenStack Server
|
316
297
|
#
|
@@ -318,10 +299,9 @@ module Bosh::OpenStackCloud
|
|
318
299
|
# @return [void]
|
319
300
|
def reboot_vm(server_id)
|
320
301
|
with_thread_name("reboot_vm(#{server_id})") do
|
321
|
-
server = @openstack.servers.get(server_id)
|
322
|
-
unless server
|
323
|
-
|
324
|
-
end
|
302
|
+
server = with_openstack { @openstack.servers.get(server_id) }
|
303
|
+
cloud_error("Server `#{server_id}' not found") unless server
|
304
|
+
|
325
305
|
soft_reboot(server)
|
326
306
|
end
|
327
307
|
end
|
@@ -332,28 +312,20 @@ module Bosh::OpenStackCloud
|
|
332
312
|
# @param [String] server_id OpenStack server UUID
|
333
313
|
# @param [Hash] network_spec Raw network spec passed by director
|
334
314
|
# @return [void]
|
335
|
-
# @raise [Bosh::Clouds:NotSupported]
|
315
|
+
# @raise [Bosh::Clouds:NotSupported] If there's a network change that requires the recreation of the VM
|
336
316
|
def configure_networks(server_id, network_spec)
|
337
317
|
with_thread_name("configure_networks(#{server_id}, ...)") do
|
338
318
|
@logger.info("Configuring `#{server_id}' to use the following " \
|
339
319
|
"network settings: #{network_spec.pretty_inspect}")
|
340
|
-
|
341
320
|
network_configurator = NetworkConfigurator.new(network_spec)
|
342
|
-
server = @openstack.servers.get(server_id)
|
343
|
-
|
344
|
-
sg = @openstack.list_security_groups(server_id).body["security_groups"]
|
345
|
-
actual = sg.collect { |s| s["name"] }.sort
|
346
|
-
new = network_configurator.security_groups(@default_security_groups)
|
347
|
-
|
348
|
-
# If the security groups change, we need to recreate the VM
|
349
|
-
# as you can't change the security group of a running server,
|
350
|
-
# we need to send the InstanceUpdater a request to do it for us
|
351
|
-
unless actual == new
|
352
|
-
raise Bosh::Clouds::NotSupported,
|
353
|
-
"security groups change requires VM recreation: %s to %s" %
|
354
|
-
[actual.join(", "), new.join(", ")]
|
355
|
-
end
|
356
321
|
|
322
|
+
server = with_openstack { @openstack.servers.get(server_id) }
|
323
|
+
cloud_error("Server `#{server_id}' not found") unless server
|
324
|
+
|
325
|
+
compare_security_groups(server, network_configurator.security_groups(@default_security_groups))
|
326
|
+
|
327
|
+
compare_private_ip_addresses(server, network_configurator.private_ip)
|
328
|
+
|
357
329
|
network_configurator.configure(@openstack, server)
|
358
330
|
|
359
331
|
update_agent_settings(server) do |settings|
|
@@ -371,17 +343,9 @@ module Bosh::OpenStackCloud
|
|
371
343
|
# @return [String] OpenStack volume UUID
|
372
344
|
def create_disk(size, server_id = nil)
|
373
345
|
with_thread_name("create_disk(#{size}, #{server_id})") do
|
374
|
-
unless size.kind_of?(Integer)
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
if (size < 1024)
|
379
|
-
cloud_error("Minimum disk size is 1 GiB")
|
380
|
-
end
|
381
|
-
|
382
|
-
if (size > 1024 * 1000)
|
383
|
-
cloud_error("Maximum disk size is 1 TiB")
|
384
|
-
end
|
346
|
+
raise ArgumentError, "Disk size needs to be an integer" unless size.kind_of?(Integer)
|
347
|
+
cloud_error("Minimum disk size is 1 GiB") if (size < 1024)
|
348
|
+
cloud_error("Maximum disk size is 1 TiB") if (size > 1024 * 1000)
|
385
349
|
|
386
350
|
volume_params = {
|
387
351
|
:name => "volume-#{generate_unique_name}",
|
@@ -390,14 +354,14 @@ module Bosh::OpenStackCloud
|
|
390
354
|
}
|
391
355
|
|
392
356
|
if server_id
|
393
|
-
server = @openstack.servers.get(server_id)
|
357
|
+
server = with_openstack { @openstack.servers.get(server_id) }
|
394
358
|
if server && server.availability_zone
|
395
359
|
volume_params[:availability_zone] = server.availability_zone
|
396
360
|
end
|
397
361
|
end
|
398
362
|
|
399
363
|
@logger.info("Creating new volume...")
|
400
|
-
volume = @openstack.volumes.create(volume_params)
|
364
|
+
volume = with_openstack { @openstack.volumes.create(volume_params) }
|
401
365
|
|
402
366
|
@logger.info("Creating new volume `#{volume.id}'...")
|
403
367
|
wait_resource(volume, :available)
|
@@ -415,14 +379,14 @@ module Bosh::OpenStackCloud
|
|
415
379
|
def delete_disk(disk_id)
|
416
380
|
with_thread_name("delete_disk(#{disk_id})") do
|
417
381
|
@logger.info("Deleting volume `#{disk_id}'...")
|
418
|
-
volume = @openstack.volumes.get(disk_id)
|
382
|
+
volume = with_openstack { @openstack.volumes.get(disk_id) }
|
419
383
|
if volume
|
420
384
|
state = volume.status
|
421
385
|
if state.to_sym != :available
|
422
386
|
cloud_error("Cannot delete volume `#{disk_id}', state is #{state}")
|
423
387
|
end
|
424
388
|
|
425
|
-
volume.destroy
|
389
|
+
with_openstack { volume.destroy }
|
426
390
|
wait_resource(volume, :deleted, :status, true)
|
427
391
|
else
|
428
392
|
@logger.info("Volume `#{disk_id}' not found. Skipping.")
|
@@ -438,14 +402,11 @@ module Bosh::OpenStackCloud
|
|
438
402
|
# @return [void]
|
439
403
|
def attach_disk(server_id, disk_id)
|
440
404
|
with_thread_name("attach_disk(#{server_id}, #{disk_id})") do
|
441
|
-
server = @openstack.servers.get(server_id)
|
442
|
-
unless server
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
unless server
|
447
|
-
cloud_error("Volume `#{disk_id}' not found")
|
448
|
-
end
|
405
|
+
server = with_openstack { @openstack.servers.get(server_id) }
|
406
|
+
cloud_error("Server `#{server_id}' not found") unless server
|
407
|
+
|
408
|
+
volume = with_openstack { @openstack.volumes.get(disk_id) }
|
409
|
+
cloud_error("Volume `#{disk_id}' not found") unless volume
|
449
410
|
|
450
411
|
device_name = attach_volume(server, volume)
|
451
412
|
|
@@ -465,14 +426,11 @@ module Bosh::OpenStackCloud
|
|
465
426
|
# @return [void]
|
466
427
|
def detach_disk(server_id, disk_id)
|
467
428
|
with_thread_name("detach_disk(#{server_id}, #{disk_id})") do
|
468
|
-
server = @openstack.servers.get(server_id)
|
469
|
-
unless server
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
unless server
|
474
|
-
cloud_error("Volume `#{disk_id}' not found")
|
475
|
-
end
|
429
|
+
server = with_openstack { @openstack.servers.get(server_id) }
|
430
|
+
cloud_error("Server `#{server_id}' not found") unless server
|
431
|
+
|
432
|
+
volume = with_openstack { @openstack.volumes.get(disk_id) }
|
433
|
+
cloud_error("Volume `#{disk_id}' not found") unless volume
|
476
434
|
|
477
435
|
detach_volume(server, volume)
|
478
436
|
|
@@ -484,6 +442,64 @@ module Bosh::OpenStackCloud
|
|
484
442
|
end
|
485
443
|
end
|
486
444
|
|
445
|
+
##
|
446
|
+
# Takes a snapshot of an OpenStack volume
|
447
|
+
#
|
448
|
+
# @param [String] disk_id OpenStack volume UUID
|
449
|
+
# @param [Hash] metadata Metadata key/value pairs to add to snapshot
|
450
|
+
# @return [String] OpenStack snapshot UUID
|
451
|
+
# @raise [Bosh::Clouds::CloudError] if volume is not found
|
452
|
+
def snapshot_disk(disk_id, metadata)
|
453
|
+
with_thread_name("snapshot_disk(#{disk_id})") do
|
454
|
+
volume = with_openstack { @openstack.volumes.get(disk_id) }
|
455
|
+
cloud_error("Volume `#{disk_id}' not found") unless volume
|
456
|
+
|
457
|
+
devices = []
|
458
|
+
volume.attachments.each { |attachment| devices << attachment["device"] unless attachment.empty? }
|
459
|
+
|
460
|
+
description = [:deployment, :job, :index].collect { |key| metadata[key] }
|
461
|
+
description << devices.first.split('/').last unless devices.empty?
|
462
|
+
snapshot_params = {
|
463
|
+
:name => "snapshot-#{generate_unique_name}",
|
464
|
+
:description => description.join('/'),
|
465
|
+
:volume_id => volume.id
|
466
|
+
}
|
467
|
+
|
468
|
+
@logger.info("Creating new snapshot for volume `#{disk_id}'...")
|
469
|
+
snapshot = @openstack.snapshots.new(snapshot_params)
|
470
|
+
with_openstack { snapshot.save(true) }
|
471
|
+
|
472
|
+
@logger.info("Creating new snapshot `#{snapshot.id}' for volume `#{disk_id}'...")
|
473
|
+
wait_resource(snapshot, :available)
|
474
|
+
|
475
|
+
snapshot.id.to_s
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
##
|
480
|
+
# Deletes an OpenStack volume snapshot
|
481
|
+
#
|
482
|
+
# @param [String] snapshot_id OpenStack snapshot UUID
|
483
|
+
# @return [void]
|
484
|
+
# @raise [Bosh::Clouds::CloudError] if snapshot is not in available state
|
485
|
+
def delete_snapshot(snapshot_id)
|
486
|
+
with_thread_name("delete_snapshot(#{snapshot_id})") do
|
487
|
+
@logger.info("Deleting snapshot `#{snapshot_id}'...")
|
488
|
+
snapshot = with_openstack { @openstack.snapshots.get(snapshot_id) }
|
489
|
+
if snapshot
|
490
|
+
state = snapshot.status
|
491
|
+
if state.to_sym != :available
|
492
|
+
cloud_error("Cannot delete snapshot `#{snapshot_id}', state is #{state}")
|
493
|
+
end
|
494
|
+
|
495
|
+
with_openstack { snapshot.destroy }
|
496
|
+
wait_resource(snapshot, :deleted, :status, true)
|
497
|
+
else
|
498
|
+
@logger.info("Snapshot `#{snapshot_id}' not found. Skipping.")
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
487
503
|
##
|
488
504
|
# Set metadata for an OpenStack server
|
489
505
|
#
|
@@ -492,14 +508,13 @@ module Bosh::OpenStackCloud
|
|
492
508
|
# @return [void]
|
493
509
|
def set_vm_metadata(server_id, metadata)
|
494
510
|
with_thread_name("set_vm_metadata(#{server_id}, ...)") do
|
495
|
-
|
496
|
-
|
497
|
-
cloud_error("Server `#{server_id}' not found")
|
498
|
-
end
|
511
|
+
with_openstack do
|
512
|
+
server = @openstack.servers.get(server_id)
|
513
|
+
cloud_error("Server `#{server_id}' not found") unless server
|
499
514
|
|
500
|
-
|
501
|
-
|
502
|
-
|
515
|
+
metadata.each do |name, value|
|
516
|
+
TagManager.tag(server, name, value)
|
517
|
+
end
|
503
518
|
end
|
504
519
|
end
|
505
520
|
end
|
@@ -524,7 +539,7 @@ module Bosh::OpenStackCloud
|
|
524
539
|
# @note this is a private method that is public to make it easier to test
|
525
540
|
def select_availability_zone(volumes, resource_pool_az)
|
526
541
|
if volumes && !volumes.empty?
|
527
|
-
disks = volumes.map { |vid| @openstack.volumes.get(vid) }
|
542
|
+
disks = volumes.map { |vid| with_openstack { @openstack.volumes.get(vid) } }
|
528
543
|
ensure_same_availability_zone(disks, resource_pool_az)
|
529
544
|
disks.first.availability_zone
|
530
545
|
else
|
@@ -555,7 +570,7 @@ module Bosh::OpenStackCloud
|
|
555
570
|
#
|
556
571
|
# @return [String] Unique name
|
557
572
|
def generate_unique_name
|
558
|
-
|
573
|
+
SecureRandom.uuid
|
559
574
|
end
|
560
575
|
|
561
576
|
##
|
@@ -564,11 +579,12 @@ module Bosh::OpenStackCloud
|
|
564
579
|
# @param [String] server_name server name
|
565
580
|
# @param [Hash] network_spec network specification
|
566
581
|
# @return [Hash] server user data
|
567
|
-
def user_data(server_name, network_spec)
|
582
|
+
def user_data(server_name, network_spec, public_key = nil)
|
568
583
|
data = {}
|
569
584
|
|
570
585
|
data["registry"] = { "endpoint" => @registry.endpoint }
|
571
586
|
data["server"] = { "name" => server_name }
|
587
|
+
data["openssh"] = { "public_key" => public_key } if public_key
|
572
588
|
|
573
589
|
with_dns(network_spec) do |servers|
|
574
590
|
data["dns"] = { "nameserver" => servers }
|
@@ -592,13 +608,12 @@ module Bosh::OpenStackCloud
|
|
592
608
|
end
|
593
609
|
|
594
610
|
##
|
595
|
-
# Generates initial agent settings. These settings will be read by
|
596
|
-
#
|
597
|
-
#
|
598
|
-
#
|
599
|
-
#
|
600
|
-
#
|
601
|
-
# (vdc through vdz, also some kernels will remap vd* to xvd*).
|
611
|
+
# Generates initial agent settings. These settings will be read by Bosh Agent from Bosh Registry on a target
|
612
|
+
# server. Disk conventions in Bosh Agent for OpenStack are:
|
613
|
+
# - system disk: /dev/sda
|
614
|
+
# - ephemeral disk: /dev/sdb
|
615
|
+
# - persistent disks: /dev/sdc through /dev/sdz
|
616
|
+
# As some kernels remap device names (from sd* to vd* or xvd*), Bosh Agent will lookup for the proper device name
|
602
617
|
#
|
603
618
|
# @param [String] server_name Name of the OpenStack server (will be picked
|
604
619
|
# up by agent to fetch registry settings)
|
@@ -606,8 +621,9 @@ module Bosh::OpenStackCloud
|
|
606
621
|
# assume its identity
|
607
622
|
# @param [Hash] network_spec Agent network spec
|
608
623
|
# @param [Hash] environment Environment settings
|
624
|
+
# @param [Boolean] has_ephemeral Has Ephemeral disk?
|
609
625
|
# @return [Hash] Agent settings
|
610
|
-
def initial_agent_settings(server_name, agent_id, network_spec, environment)
|
626
|
+
def initial_agent_settings(server_name, agent_id, network_spec, environment, has_ephemeral)
|
611
627
|
settings = {
|
612
628
|
"vm" => {
|
613
629
|
"name" => server_name
|
@@ -615,12 +631,12 @@ module Bosh::OpenStackCloud
|
|
615
631
|
"agent_id" => agent_id,
|
616
632
|
"networks" => network_spec,
|
617
633
|
"disks" => {
|
618
|
-
"system" => "/dev/
|
619
|
-
"ephemeral" => "/dev/vdb",
|
634
|
+
"system" => "/dev/sda",
|
620
635
|
"persistent" => {}
|
621
636
|
}
|
622
637
|
}
|
623
638
|
|
639
|
+
settings["disks"]["ephemeral"] = has_ephemeral ? "/dev/sdb" : nil
|
624
640
|
settings["env"] = environment if environment
|
625
641
|
settings.merge(@agent_properties)
|
626
642
|
end
|
@@ -630,9 +646,7 @@ module Bosh::OpenStackCloud
|
|
630
646
|
#
|
631
647
|
# @param [Fog::Compute::OpenStack::Server] server OpenStack server
|
632
648
|
def update_agent_settings(server)
|
633
|
-
unless block_given?
|
634
|
-
raise ArgumentError, "Block is not provided"
|
635
|
-
end
|
649
|
+
raise ArgumentError, "Block is not provided" unless block_given?
|
636
650
|
|
637
651
|
@logger.info("Updating settings for server `#{server.id}'...")
|
638
652
|
settings = @registry.read_settings(server.name)
|
@@ -647,7 +661,7 @@ module Bosh::OpenStackCloud
|
|
647
661
|
# @return [void]
|
648
662
|
def soft_reboot(server)
|
649
663
|
@logger.info("Soft rebooting server `#{server.id}'...")
|
650
|
-
server.reboot
|
664
|
+
with_openstack { server.reboot }
|
651
665
|
wait_resource(server, :active, :state)
|
652
666
|
end
|
653
667
|
|
@@ -658,7 +672,7 @@ module Bosh::OpenStackCloud
|
|
658
672
|
# @return [void]
|
659
673
|
def hard_reboot(server)
|
660
674
|
@logger.info("Hard rebooting server `#{server.id}'...")
|
661
|
-
server.reboot(type = 'HARD')
|
675
|
+
with_openstack { server.reboot(type = 'HARD') }
|
662
676
|
wait_resource(server, :active, :state)
|
663
677
|
end
|
664
678
|
|
@@ -669,31 +683,58 @@ module Bosh::OpenStackCloud
|
|
669
683
|
# @param [Fog::Compute::OpenStack::Volume] volume OpenStack volume
|
670
684
|
# @return [String] Device name
|
671
685
|
def attach_volume(server, volume)
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
wait_resource(volume, :"in-use")
|
687
|
-
new_attachment = dev_name
|
688
|
-
end
|
689
|
-
break
|
686
|
+
@logger.info("Attaching volume `#{volume.id}' to server `#{server.id}'...")
|
687
|
+
volume_attachments = with_openstack { server.volume_attachments }
|
688
|
+
device = volume_attachments.find { |a| a["volumeId"] == volume.id }
|
689
|
+
|
690
|
+
if device.nil?
|
691
|
+
device_name = select_device_name(volume_attachments, first_device_name_letter(server))
|
692
|
+
cloud_error("Server has too many disks attached") if device_name.nil?
|
693
|
+
|
694
|
+
@logger.info("Attaching volume `#{volume.id}' to server `#{server.id}', device name is `#{device_name}'")
|
695
|
+
with_openstack { volume.attach(server.id, device_name) }
|
696
|
+
wait_resource(volume, :"in-use")
|
697
|
+
else
|
698
|
+
device_name = device["device"]
|
699
|
+
@logger.info("Volume `#{volume.id}' is already attached to server `#{server.id}' in `#{device_name}'. Skipping.")
|
690
700
|
end
|
691
701
|
|
692
|
-
|
693
|
-
|
702
|
+
device_name
|
703
|
+
end
|
704
|
+
|
705
|
+
##
|
706
|
+
# Select the first available device name
|
707
|
+
#
|
708
|
+
# @param [Array] volume_attachments Volume attachments
|
709
|
+
# @param [String] first_device_name_letter First available letter for device names
|
710
|
+
# @return [String] First available device name or nil is none is available
|
711
|
+
def select_device_name(volume_attachments, first_device_name_letter)
|
712
|
+
(first_device_name_letter.."z").each do |char|
|
713
|
+
# Some kernels remap device names (from sd* to vd* or xvd*).
|
714
|
+
device_names = ["/dev/sd#{char}", "/dev/vd#{char}", "/dev/xvd#{char}"]
|
715
|
+
# Bosh Agent will lookup for the proper device name if we set it initially to sd*.
|
716
|
+
return "/dev/sd#{char}" if volume_attachments.select { |v| device_names.include?( v["device"]) }.empty?
|
717
|
+
@logger.warn("`/dev/sd#{char}' is already taken")
|
694
718
|
end
|
695
719
|
|
696
|
-
|
720
|
+
nil
|
721
|
+
end
|
722
|
+
|
723
|
+
##
|
724
|
+
# Returns the first letter to be used on device names
|
725
|
+
#
|
726
|
+
# @param [Fog::Compute::OpenStack::Server] server OpenStack server
|
727
|
+
# @return [String] First available letter
|
728
|
+
def first_device_name_letter(server)
|
729
|
+
letter = "#{FIRST_DEVICE_NAME_LETTER}"
|
730
|
+
return letter if server.flavor.nil?
|
731
|
+
return letter unless server.flavor.has_key?('id')
|
732
|
+
flavor = with_openstack { @openstack.flavors.find { |f| f.id == server.flavor['id'] } }
|
733
|
+
return letter if flavor.nil?
|
734
|
+
|
735
|
+
letter.succ! if flavor_has_ephemeral_disk?(flavor)
|
736
|
+
letter.succ! if flavor_has_swap_disk?(flavor)
|
737
|
+
letter
|
697
738
|
end
|
698
739
|
|
699
740
|
##
|
@@ -703,33 +744,68 @@ module Bosh::OpenStackCloud
|
|
703
744
|
# @param [Fog::Compute::OpenStack::Volume] volume OpenStack volume
|
704
745
|
# @return [void]
|
705
746
|
def detach_volume(server, volume)
|
706
|
-
|
707
|
-
|
708
|
-
|
747
|
+
@logger.info("Detaching volume `#{volume.id}' from `#{server.id}'...")
|
748
|
+
volume_attachments = with_openstack { server.volume_attachments }
|
749
|
+
if volume_attachments.find { |a| a["volumeId"] == volume.id }
|
750
|
+
with_openstack { volume.detach(server.id, volume.id) }
|
751
|
+
wait_resource(volume, :available)
|
752
|
+
else
|
753
|
+
@logger.info("Disk `#{volume.id}' is not attached to server `#{server.id}'. Skipping.")
|
754
|
+
end
|
755
|
+
end
|
709
756
|
|
710
|
-
|
711
|
-
|
712
|
-
|
757
|
+
##
|
758
|
+
# Compares actual server security groups with those specified at the network spec
|
759
|
+
#
|
760
|
+
# @param [Fog::Compute::OpenStack::Server] server OpenStack server
|
761
|
+
# @param [Array] specified_sg_names Security groups specified at the network spec
|
762
|
+
# @return [void]
|
763
|
+
# @raise [Bosh::Clouds:NotSupported] If the security groups change, we need to recreate the VM as you can't
|
764
|
+
# change the security group of a running server, so we need to send the InstanceUpdater a request to do it for us
|
765
|
+
def compare_security_groups(server, specified_sg_names)
|
766
|
+
actual_sg_names = with_openstack { server.security_groups }.collect { |sg| sg.name }
|
767
|
+
|
768
|
+
unless actual_sg_names.sort == specified_sg_names.sort
|
769
|
+
raise Bosh::Clouds::NotSupported,
|
770
|
+
"security groups change requires VM recreation: %s to %s" %
|
771
|
+
[actual_sg_names.join(", "), specified_sg_names.join(", ")]
|
713
772
|
end
|
773
|
+
end
|
714
774
|
|
715
|
-
|
716
|
-
|
717
|
-
|
775
|
+
##
|
776
|
+
# Compares actual server private IP addresses with the IP address specified at the network spec
|
777
|
+
#
|
778
|
+
# @param [Fog::Compute::OpenStack::Server] server OpenStack server
|
779
|
+
# @param [String] specified_ip_address IP address specified at the network spec (if Manual network)
|
780
|
+
# @return [void]
|
781
|
+
# @raise [Bosh::Clouds:NotSupported] If the IP address change, we need to recreate the VM as you can't
|
782
|
+
# change the IP address of a running server, so we need to send the InstanceUpdater a request to do it for us
|
783
|
+
def compare_private_ip_addresses(server, specified_ip_address)
|
784
|
+
actual_ip_addresses = with_openstack { server.private_ip_addresses }
|
785
|
+
|
786
|
+
unless specified_ip_address.nil? || actual_ip_addresses.include?(specified_ip_address)
|
787
|
+
raise Bosh::Clouds::NotSupported,
|
788
|
+
"IP address change requires VM recreation: %s to %s" %
|
789
|
+
[actual_ip_addresses.join(", "), specified_ip_address]
|
790
|
+
end
|
718
791
|
end
|
719
792
|
|
720
793
|
##
|
721
|
-
#
|
794
|
+
# Checks if the OpenStack flavor has ephemeral disk
|
722
795
|
#
|
723
|
-
# @param [
|
724
|
-
# @return [
|
725
|
-
def
|
726
|
-
|
727
|
-
|
728
|
-
image = @glance.images.create(image_params)
|
729
|
-
total = Time.now - started_at
|
730
|
-
@logger.info("Created new image `#{image.id}', took #{total}s")
|
796
|
+
# @param [Fog::Compute::OpenStack::Flavor] OpenStack flavor
|
797
|
+
# @return [Boolean] true if flavor has ephemeral disk, false otherwise
|
798
|
+
def flavor_has_ephemeral_disk?(flavor)
|
799
|
+
flavor.ephemeral.nil? || flavor.ephemeral.to_i <= 0 ? false : true
|
800
|
+
end
|
731
801
|
|
732
|
-
|
802
|
+
##
|
803
|
+
# Checks if the OpenStack flavor has swap disk
|
804
|
+
#
|
805
|
+
# @param [Fog::Compute::OpenStack::Flavor] OpenStack flavor
|
806
|
+
# @return [Boolean] true if flavor has swap disk, false otherwise
|
807
|
+
def flavor_has_swap_disk?(flavor)
|
808
|
+
flavor.swap.nil? || flavor.swap.to_i <= 0 ? false : true
|
733
809
|
end
|
734
810
|
|
735
811
|
##
|
@@ -739,10 +815,11 @@ module Bosh::OpenStackCloud
|
|
739
815
|
# @param [String] image_path Local filesystem path to a stemcell image
|
740
816
|
# @return [void]
|
741
817
|
def unpack_image(tmp_dir, image_path)
|
742
|
-
|
743
|
-
if
|
744
|
-
|
745
|
-
|
818
|
+
result = Bosh::Exec.sh("tar -C #{tmp_dir} -xzf #{image_path} 2>&1", :on_error => :return)
|
819
|
+
if result.failed?
|
820
|
+
@logger.error("Extracting stemcell root image failed in dir #{tmp_dir}, " +
|
821
|
+
"tar returned #{result.exit_status}, output: #{result.output}")
|
822
|
+
cloud_error("Extracting stemcell root image failed. Check task debug log for details.")
|
746
823
|
end
|
747
824
|
root_image = File.join(tmp_dir, "root.img")
|
748
825
|
unless File.exists?(root_image)
|
@@ -775,8 +852,15 @@ module Bosh::OpenStackCloud
|
|
775
852
|
end
|
776
853
|
end
|
777
854
|
|
778
|
-
def
|
779
|
-
|
855
|
+
def initialize_registry
|
856
|
+
registry_properties = @options.fetch('registry')
|
857
|
+
registry_endpoint = registry_properties.fetch('endpoint')
|
858
|
+
registry_user = registry_properties.fetch('user')
|
859
|
+
registry_password = registry_properties.fetch('password')
|
860
|
+
|
861
|
+
@registry = Bosh::Registry::Client.new(registry_endpoint,
|
862
|
+
registry_user,
|
863
|
+
registry_password)
|
780
864
|
end
|
781
865
|
|
782
866
|
end
|