bosh_deployer 0.4.0 → 0.5.0

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