bosh_deployer 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,3 +48,6 @@ cloud:
48
48
 
49
49
  apply_spec:
50
50
  properties: {}
51
+ agent:
52
+ blobstore: {}
53
+ nats: {}
@@ -0,0 +1,50 @@
1
+ ---
2
+ name:
3
+
4
+ logging:
5
+ level: INFO
6
+
7
+ dir:
8
+
9
+ network:
10
+ type: dynamic
11
+ cloud_properties: {}
12
+
13
+ env:
14
+ bosh:
15
+ password:
16
+
17
+ resources:
18
+ persistent_disk: 4096
19
+ cloud_properties:
20
+ instance_type: m1.small
21
+ availability_zone:
22
+
23
+ cloud:
24
+ plugin: openstack
25
+ properties:
26
+ openstack:
27
+ auth_url:
28
+ username:
29
+ api_key:
30
+ tenant:
31
+ default_key_name:
32
+ default_security_groups: []
33
+ ssh_user: vcap
34
+ registry:
35
+ endpoint: http://admin:admin@localhost:25889
36
+ user: admin
37
+ password: admin
38
+ stemcell:
39
+ kernel_id:
40
+ disk: 4096
41
+ agent:
42
+ ntp: []
43
+ blobstore:
44
+ plugin: local
45
+ properties:
46
+ blobstore_path: /var/vcap/micro_bosh/data/cache
47
+ mbus:
48
+
49
+ apply_spec:
50
+ properties: {}
@@ -80,12 +80,14 @@ module Bosh::Cli::Command
80
80
 
81
81
  def perform(*options)
82
82
  update = options.delete("--update")
83
+ force = options.delete("--force")
83
84
  stemcell = options.shift
84
85
 
85
86
  err "No deployment set" unless deployment
86
87
 
88
+ manifest = load_yaml_file(deployment)
89
+
87
90
  if stemcell.nil?
88
- manifest = load_yaml_file(deployment)
89
91
  unless manifest.is_a?(Hash)
90
92
  err("Invalid manifest format")
91
93
  end
@@ -114,6 +116,15 @@ module Bosh::Cli::Command
114
116
  err "Instance exists. Did you mean to --update?"
115
117
  end
116
118
 
119
+ # make sure the user knows a persistent disk is a really good idea
120
+ unless dig_hash(manifest, "resources", "persistent_disk")
121
+ unless force
122
+ msg = "No persistent disk configured, use --force to deploy anyway"
123
+ quit(msg.red)
124
+ end
125
+ say("WARNING! The micro bosh data won't be persisted".red)
126
+ end
127
+
117
128
  confirmation = "Deploying new"
118
129
 
119
130
  method = :create_deployment
@@ -10,7 +10,7 @@ module Bosh::Deployer
10
10
  include Helpers
11
11
 
12
12
  attr_accessor :logger, :db, :uuid, :resources, :cloud_options,
13
- :spec_properties, :bosh_ip, :env, :name
13
+ :spec_properties, :agent_properties, :bosh_ip, :env, :name
14
14
 
15
15
  def configure(config)
16
16
  plugin = cloud_plugin(config)
@@ -31,7 +31,9 @@ module Bosh::Deployer
31
31
  @logger.level = Logger.const_get(config["logging"]["level"].upcase)
32
32
  @logger.formatter = ThreadFormatter.new
33
33
 
34
- @spec_properties = config["apply_spec"]["properties"]
34
+ apply_spec = config["apply_spec"]
35
+ @spec_properties = apply_spec["properties"]
36
+ @agent_properties = apply_spec["agent"]
35
37
 
36
38
  @db = Sequel.sqlite
37
39
 
@@ -87,7 +89,9 @@ module Bosh::Deployer
87
89
  end
88
90
 
89
91
  def networks
90
- @networks ||= {
92
+ return @networks if @networks
93
+
94
+ @networks = {
91
95
  "bosh" => {
92
96
  "cloud_properties" => @net_conf["cloud_properties"],
93
97
  "netmask" => @net_conf["netmask"],
@@ -98,6 +102,14 @@ module Bosh::Deployer
98
102
  "default" => ["dns", "gateway"]
99
103
  }
100
104
  }
105
+ if @net_conf["vip"]
106
+ @networks["vip"] = {
107
+ "ip" => @net_conf["vip"],
108
+ "type" => "vip"
109
+ }
110
+ end
111
+
112
+ @networks
101
113
  end
102
114
 
103
115
  private
@@ -128,7 +128,15 @@ module Bosh::Deployer
128
128
 
129
129
  def discover_bosh_ip
130
130
  if exists?
131
- ip = cloud.ec2.instances[state.vm_cid].public_ip_address
131
+ # choose elastic IP over public, as any agent connecting to the
132
+ # deployed micro bosh will be cut off from the public IP when
133
+ # we re-deploy micro bosh
134
+ if cloud.ec2.instances[state.vm_cid].has_elastic_ip?
135
+ ip = cloud.ec2.instances[state.vm_cid].elastic_ip.public_ip
136
+ else
137
+ ip = cloud.ec2.instances[state.vm_cid].public_ip_address
138
+ end
139
+
132
140
  if ip != Config.bosh_ip
133
141
  Config.bosh_ip = ip
134
142
  logger.info("discovered bosh ip=#{Config.bosh_ip}")
@@ -142,14 +150,14 @@ module Bosh::Deployer
142
150
  cloud.ec2.instances[state.vm_cid].private_ip_address
143
151
  end
144
152
 
145
- # @return [Integer] size in kB
153
+ # @return [Integer] size in MiB
146
154
  def disk_size(cid)
147
- # AWS stores disk size in MiB but we work with kB
155
+ # AWS stores disk size in GiB but the CPI uses MiB
148
156
  cloud.ec2.volumes[cid].size * 1024
149
157
  end
150
158
 
151
159
  def persistent_disk_changed?
152
- # since AWS stores disk size in MiB and we use kB there
160
+ # since AWS stores disk size in GiB and the CPI uses MiB there
153
161
  # is a risk of conversion errors which lead to an unnecessary
154
162
  # disk migration, so we need to do a double conversion
155
163
  # here to avoid that
@@ -0,0 +1,256 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ require 'net/ssh'
4
+
5
+ module Bosh::Deployer
6
+ class InstanceManager
7
+
8
+ class Openstack < InstanceManager
9
+
10
+ def update_spec(spec)
11
+ spec = super(spec)
12
+ properties = spec["properties"]
13
+
14
+ properties["openstack"] =
15
+ Config.spec_properties["openstack"] ||
16
+ Config.cloud_options["properties"]["openstack"].dup
17
+
18
+ properties["openstack"]["registry"] = Config.cloud_options["properties"]["registry"]
19
+ properties["openstack"]["stemcell"] = Config.cloud_options["properties"]["stemcell"]
20
+
21
+ spec.delete("networks")
22
+
23
+ spec
24
+ end
25
+
26
+ def configure
27
+ properties = Config.cloud_options["properties"]
28
+ @ssh_user = properties["openstack"]["ssh_user"]
29
+ @ssh_port = properties["openstack"]["ssh_port"] || 22
30
+ @ssh_wait = properties["openstack"]["ssh_wait"] || 60
31
+
32
+ key = properties["openstack"]["private_key"]
33
+ unless key
34
+ raise ConfigError, "Missing properties.openstack.private_key"
35
+ end
36
+ @ssh_key = File.expand_path(key)
37
+ unless File.exists?(@ssh_key)
38
+ raise ConfigError, "properties.openstack.private_key '#{key}' does not exist"
39
+ end
40
+
41
+ uri = URI.parse(properties["registry"]["endpoint"])
42
+ user, password = uri.userinfo.split(":", 2)
43
+ @registry_port = uri.port
44
+
45
+ @registry_db = Tempfile.new("openstack_registry_db")
46
+ @registry_db_url = "sqlite://#{@registry_db.path}"
47
+
48
+ registry_config = {
49
+ "logfile" => "./openstack_registry.log",
50
+ "http" => {
51
+ "port" => uri.port,
52
+ "user" => user,
53
+ "password" => password
54
+ },
55
+ "db" => {
56
+ "database" => @registry_db_url
57
+ },
58
+ "openstack" => properties["openstack"]
59
+ }
60
+
61
+ @registry_config = Tempfile.new("openstack_registry_yml")
62
+ @registry_config.write(YAML.dump(registry_config))
63
+ @registry_config.close
64
+ end
65
+
66
+ def start
67
+ configure()
68
+
69
+ Sequel.connect(@registry_db_url) do |db|
70
+ migrate(db)
71
+ servers = @deployments["openstack_servers"]
72
+ db[:openstack_servers].insert_multiple(servers) if servers
73
+ end
74
+
75
+ unless has_openstack_registry?
76
+ raise "openstack_registry command not found - " +
77
+ "run 'gem install bosh_openstack_registry'"
78
+ end
79
+
80
+ cmd = "openstack_registry -c #{@registry_config.path}"
81
+
82
+ @registry_pid = spawn(cmd)
83
+
84
+ 5.times do
85
+ sleep 0.5
86
+ if Process.waitpid(@registry_pid, Process::WNOHANG)
87
+ raise Error, "`#{cmd}` failed, exit status=#{$?.exitstatus}"
88
+ end
89
+ end
90
+
91
+ timeout_time = Time.now.to_f + (60 * 5)
92
+ http_client = HTTPClient.new()
93
+ begin
94
+ http_client.head("http://127.0.0.1:#{@registry_port}")
95
+ sleep 0.5
96
+ rescue URI::Error, SocketError, Errno::ECONNREFUSED => e
97
+ if timeout_time - Time.now.to_f > 0
98
+ retry
99
+ else
100
+ raise "Cannot access openstack_registry: #{e.message}"
101
+ end
102
+ end
103
+ logger.info("openstack_registry is ready on port #{@registry_port}")
104
+ ensure
105
+ @registry_config.unlink if @registry_config
106
+ end
107
+
108
+ def stop
109
+ if @registry_pid && process_exists?(@registry_pid)
110
+ Process.kill("INT", @registry_pid)
111
+ Process.waitpid(@registry_pid)
112
+ end
113
+
114
+ return unless @registry_db_url
115
+
116
+ Sequel.connect(@registry_db_url) do |db|
117
+ @deployments["openstack_servers"] = db[:openstack_servers].map {|row| row}
118
+ end
119
+
120
+ save_state
121
+ @registry_db.unlink if @registry_db
122
+ end
123
+
124
+ def wait_until_agent_ready
125
+ tunnel(@registry_port)
126
+ super
127
+ end
128
+
129
+ def discover_bosh_ip
130
+ if exists?
131
+ server = cloud.openstack.servers.get(state.vm_cid)
132
+ ip = server.public_ip_address
133
+ ip = server.private_ip_address if ip.nil? || ip.empty?
134
+ if ip.nil? || ip.empty?
135
+ raise "Unable to discover bosh ip"
136
+ else
137
+ if ip["addr"] != Config.bosh_ip
138
+ Config.bosh_ip = ip["addr"]
139
+ logger.info("discovered bosh ip=#{Config.bosh_ip}")
140
+ end
141
+ end
142
+ end
143
+
144
+ super
145
+ end
146
+
147
+ def service_ip
148
+ ip = cloud.openstack.servers.get(state.vm_cid).private_ip_address
149
+ ip["addr"] unless ip.nil? || ip.empty?
150
+ end
151
+
152
+ # @return [Integer] size in MiB
153
+ def disk_size(cid)
154
+ # OpenStack stores disk size in GiB but we work with MiB
155
+ cloud.openstack.volumes.get(cid).size * 1024
156
+ end
157
+
158
+ def persistent_disk_changed?
159
+ # since OpenStack stores disk size in GiB and we use MiB there
160
+ # is a risk of conversion errors which lead to an unnecessary
161
+ # disk migration, so we need to do a double conversion
162
+ # here to avoid that
163
+ requested = (Config.resources['persistent_disk'] / 1024.0).ceil * 1024
164
+ requested != disk_size(state.disk_cid)
165
+ end
166
+
167
+ private
168
+
169
+ # TODO this code is similar to has_stemcell_copy?
170
+ # move the two into bosh_common later
171
+ def has_openstack_registry?(path=ENV['PATH'])
172
+ path.split(":").each do |dir|
173
+ return true if File.exist?(File.join(dir, "openstack_registry"))
174
+ end
175
+ false
176
+ end
177
+
178
+ def migrate(db)
179
+ db.create_table :openstack_servers do
180
+ primary_key :id
181
+ column :server_id, :text, :unique => true, :null => false
182
+ column :settings, :text, :null => false
183
+ end
184
+ end
185
+
186
+ def process_exists?(pid)
187
+ begin
188
+ Process.kill(0, pid)
189
+ rescue Errno::ESRCH
190
+ false
191
+ end
192
+ end
193
+
194
+ def socket_readable?(ip, port)
195
+ socket = TCPSocket.new(ip, port)
196
+ if IO.select([socket], nil, nil, 5)
197
+ logger.debug("tcp socket #{ip}:#{port} is readable")
198
+ yield
199
+ true
200
+ else
201
+ false
202
+ end
203
+ rescue SocketError => e
204
+ logger.debug("tcp socket #{ip}:#{port} SocketError: #{e.inspect}")
205
+ sleep 1
206
+ false
207
+ rescue SystemCallError => e
208
+ logger.debug("tcp socket #{ip}:#{port} SystemCallError: #{e.inspect}")
209
+ sleep 1
210
+ false
211
+ ensure
212
+ socket.close if socket
213
+ end
214
+
215
+ def tunnel(port)
216
+ return if @session
217
+
218
+ ip = discover_bosh_ip
219
+
220
+ loop until socket_readable?(ip, @ssh_port) do
221
+ #sshd is up, sleep while host keys are generated
222
+ sleep @ssh_wait
223
+ end
224
+
225
+ lo = "127.0.0.1"
226
+ cmd = "ssh -R #{port}:#{lo}:#{port} #{@ssh_user}@#{ip}"
227
+
228
+ logger.info("Preparing for ssh tunnel: #{cmd}")
229
+ loop do
230
+ begin
231
+ @session = Net::SSH.start(ip, @ssh_user, :keys => [@ssh_key],
232
+ :paranoid => false)
233
+ logger.debug("ssh #{@ssh_user}@#{ip}: ESTABLISHED")
234
+ break
235
+ rescue => e
236
+ logger.debug("ssh start #{@ssh_user}@#{ip} failed: #{e.inspect}")
237
+ sleep 1
238
+ end
239
+ end
240
+
241
+ @session.forward.remote(port, lo, port)
242
+ logger.info("`#{cmd}` started: OK")
243
+
244
+ Thread.new do
245
+ begin
246
+ @session.loop { true }
247
+ rescue IOError => e
248
+ logger.debug("`#{cmd}` terminated: #{e.inspect}")
249
+ @session = nil
250
+ end
251
+ end
252
+ end
253
+
254
+ end
255
+ end
256
+ end
@@ -27,7 +27,7 @@ module Bosh::Deployer
27
27
  end
28
28
  end
29
29
 
30
- # @return [Integer] size in kB
30
+ # @return [Integer] size in MiB
31
31
  def disk_size(cid)
32
32
  disk_model[cid].size
33
33
  end
@@ -350,13 +350,17 @@ module Bosh::Deployer
350
350
  properties["director"]["name"] = Config.name
351
351
  end
352
352
 
353
- %w{blobstore postgres director redis nats aws_registry}.each do |service|
354
- next unless properties[service]
355
- properties[service]["address"] = service_ip
353
+ # blobstore and nats need to use an elastic IP (if available),
354
+ # as when the micro bosh instance is re-created during a
355
+ # deployment, it might get a new private IP
356
+ %w{blobstore nats}.each do |service|
357
+ update_agent_service_address(properties, service, bosh_ip)
358
+ end
356
359
 
357
- if override = Config.spec_properties[service]
358
- properties[service].merge!(override)
359
- end
360
+ services = %w{postgres director redis blobstore nats aws_registry
361
+ openstack_registry}
362
+ services.each do |service|
363
+ update_service_address(properties, service, service_ip)
360
364
  end
361
365
 
362
366
  spec
@@ -382,6 +386,28 @@ module Bosh::Deployer
382
386
 
383
387
  private
384
388
 
389
+ # update the agent service section from the contents of the apply_spec
390
+ def update_agent_service_address(properties, service, address)
391
+ if Config.agent_properties
392
+ agent = properties["agent"] ||= {}
393
+ svc = agent[service] ||= {}
394
+ svc["address"] = address
395
+
396
+ if override = Config.agent_properties[service]
397
+ svc.merge!(override)
398
+ end
399
+ end
400
+ end
401
+
402
+ def update_service_address(properties, service, address)
403
+ return unless properties[service]
404
+ properties[service]["address"] = address
405
+
406
+ if override = Config.spec_properties[service]
407
+ properties[service].merge!(override)
408
+ end
409
+ end
410
+
385
411
  def bosh_ip
386
412
  Config.bosh_ip
387
413
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Bosh
4
4
  module Deployer
5
- VERSION = "0.4.0"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bosh_deployer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.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: 2012-07-23 00:00:00.000000000 Z
12
+ date: 2012-08-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bosh_common
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 0.4.0
21
+ version: 0.5.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: 0.4.0
29
+ version: 0.5.0
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: bosh_cpi
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -66,7 +66,7 @@ dependencies:
66
66
  requirements:
67
67
  - - ~>
68
68
  - !ruby/object:Gem::Version
69
- version: 0.6.0
69
+ version: 0.6.2
70
70
  type: :runtime
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
@@ -74,7 +74,55 @@ dependencies:
74
74
  requirements:
75
75
  - - ~>
76
76
  - !ruby/object:Gem::Version
77
- version: 0.6.0
77
+ version: 0.6.2
78
+ - !ruby/object:Gem::Dependency
79
+ name: bosh_aws_registry
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.2.2
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.2.2
94
+ - !ruby/object:Gem::Dependency
95
+ name: bosh_openstack_cpi
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 0.0.2
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 0.0.2
110
+ - !ruby/object:Gem::Dependency
111
+ name: bosh_openstack_registry
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 0.0.1
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 0.0.1
78
126
  - !ruby/object:Gem::Dependency
79
127
  name: agent_client
80
128
  requirement: !ruby/object:Gem::Requirement
@@ -114,6 +162,7 @@ extensions: []
114
162
  extra_rdoc_files: []
115
163
  files:
116
164
  - config/aws_defaults.yml
165
+ - config/openstack_defaults.yml
117
166
  - config/vsphere_defaults.yml
118
167
  - lib/bosh/cli/commands/micro.rb
119
168
  - lib/deployer.rb
@@ -122,6 +171,7 @@ files:
122
171
  - lib/deployer/helpers.rb
123
172
  - lib/deployer/instance_manager.rb
124
173
  - lib/deployer/instance_manager/aws.rb
174
+ - lib/deployer/instance_manager/openstack.rb
125
175
  - lib/deployer/instance_manager/vsphere.rb
126
176
  - lib/deployer/models/instance.rb
127
177
  - lib/deployer/version.rb
@@ -142,7 +192,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
142
192
  version: '0'
143
193
  segments:
144
194
  - 0
145
- hash: -397846320824722837
195
+ hash: -2032018102027977195
146
196
  required_rubygems_version: !ruby/object:Gem::Requirement
147
197
  none: false
148
198
  requirements:
@@ -151,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
201
  version: '0'
152
202
  segments:
153
203
  - 0
154
- hash: -397846320824722837
204
+ hash: -2032018102027977195
155
205
  requirements: []
156
206
  rubyforge_project:
157
207
  rubygems_version: 1.8.24