bosh-director 1.3200.0 → 1.3202.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 634bdc4da008c31275e924ef19624d4e812d281a
4
- data.tar.gz: 819f05473912f7888618d23d6277705a7c360440
3
+ metadata.gz: 2c0a7cb66eb81b7bc267bfbe73d9c21b7493842a
4
+ data.tar.gz: cb26be183757635f946fc7db7b440e5d5cd745fa
5
5
  SHA512:
6
- metadata.gz: b4ea8e52ad2ed2802492c8cfd3ed2ff5a723b9a2b7812d929f8cc422826a204ef4715ad97975a68470e04555078bf3b71aefe1f3468464a296e75d6a37a6dc22
7
- data.tar.gz: 7044c0da7e694f38d7792b7ff65b8e609f99bb5251b65930137fb64ca07de7fcc3ffb6440aa9a3960ab6bfd5f834cb5a90dc618c910a909a52b9aadff5f62aa3
6
+ metadata.gz: c007a923353b2face4ab79b4e11a5aee2a5ef8838433cd4bb4bdb3339336f4b8d54727fb2cf9f8761d9f0cae51b644ca42a8ba6e15b19c23a3a6fe2405f0f00c
7
+ data.tar.gz: f4e508a6c6920cd4da88f4c614d07b09f690962ef900485bfda426f6e09159765595e0549530e96192b3dd9f7dc9722f84f156eb173ae7ecc3dafcb6184c877a
@@ -0,0 +1,7 @@
1
+ Sequel.migration do
2
+ change do
3
+ alter_table(:deployments) do
4
+ add_column :scopes, String, default: 'bosh.admin'
5
+ end
6
+ end
7
+ end
data/lib/bosh/director.rb CHANGED
@@ -88,6 +88,7 @@ require 'bosh/director/problem_scanner/scanner'
88
88
  require 'bosh/director/problem_resolver'
89
89
  require 'bosh/director/error_ignorer'
90
90
  require 'bosh/director/deployment_deleter'
91
+ require 'bosh/director/permission_authorizer'
91
92
  require 'bosh/director/transactor'
92
93
  require 'bosh/director/sequel'
93
94
  require 'common/thread_pool'
@@ -125,7 +125,7 @@ module Bosh::Director
125
125
 
126
126
  delete '/:deployment/snapshots/:cid' do
127
127
  deployment = @deployment_manager.find_by_name(params[:deployment])
128
- snapshot = @snapshot_manager.find_by_cid(deployment, params[:cid])
128
+ @snapshot_manager.find_by_cid(deployment, params[:cid])
129
129
 
130
130
  task = @snapshot_manager.delete_snapshots_task(current_user, [params[:cid]])
131
131
  redirect "/tasks/#{task.id}"
@@ -133,8 +133,8 @@ module Bosh::Director
133
133
 
134
134
  get '/', scope: :read do
135
135
  latest_cloud_config = Api::CloudConfigManager.new.latest
136
- deployments = Models::Deployment.order_by(:name.asc).map do |deployment|
137
- cloud_config = if deployment.cloud_config.nil?
136
+ deployments = @deployment_manager.find_available(token_scopes).map do |deployment|
137
+ cloud_config = if deployment.cloud_config.nil?
138
138
  'none'
139
139
  elsif deployment.cloud_config == latest_cloud_config
140
140
  'latest'
@@ -272,9 +272,10 @@ module Bosh::Director
272
272
  context = JSON.parse(params['context'])
273
273
  cloud_config = Api::CloudConfigManager.new.find_by_id(context['cloud_config_id'])
274
274
  else
275
- cloud_config =Api::CloudConfigManager.new.latest
275
+ cloud_config = Api::CloudConfigManager.new.latest
276
276
  end
277
277
 
278
+ options.merge!('scopes' => token_scopes)
278
279
  task = @deployment_manager.create_deployment(current_user, request.body, cloud_config, options)
279
280
  redirect "/tasks/#{task.id}"
280
281
  end
@@ -3,14 +3,21 @@ module Bosh::Director
3
3
  class DeploymentManager
4
4
  include ApiHelper
5
5
 
6
- # Finds deployment by name
7
- # @param [String] name
8
- # @return [Models::Deployment] Deployment model
9
- # @raise [DeploymentNotFound]
6
+ def initialize
7
+ @permission_authorizer = Bosh::Director::PermissionAuthorizer.new
8
+ end
9
+
10
10
  def find_by_name(name)
11
11
  DeploymentLookup.new.by_name(name)
12
12
  end
13
13
 
14
+ def find_available(token_scopes)
15
+ deployments = Bosh::Director::Models::Deployment.order_by(:name.asc).all
16
+ deployments.select do |deployment|
17
+ @permission_authorizer.is_authorized?(deployment.scopes.split((',')), token_scopes)
18
+ end
19
+ end
20
+
14
21
  def create_deployment(username, deployment_manifest, cloud_config, options = {})
15
22
  random_name = "deployment-#{SecureRandom.uuid}"
16
23
  deployment_manifest_dir = Dir::tmpdir
@@ -2,9 +2,15 @@ module Bosh::Director
2
2
  module Api
3
3
  module Extensions
4
4
  module Scoping
5
+ ROUTES_WITH_EXTENDED_TIMEOUT = ['/stemcells', '/releases', '/restore']
6
+
5
7
  module Helpers
6
8
  def current_user
7
9
  @user.username if @user
10
+ end
11
+
12
+ def token_scopes
13
+ @user.scopes if @user
8
14
  end
9
15
  end
10
16
 
@@ -29,7 +35,11 @@ module Bosh::Director
29
35
 
30
36
  if auth_provided
31
37
  begin
32
- @user = identity_provider.get_user(request.env)
38
+ extended_token_timeout = ROUTES_WITH_EXTENDED_TIMEOUT.include?(request.path) &&
39
+ request.media_type == mime_type(:multipart) &&
40
+ request.request_method == 'POST'
41
+
42
+ @user = identity_provider.get_user(request.env, extended_token_timeout: extended_token_timeout)
33
43
  rescue AuthenticationError
34
44
  end
35
45
  end
@@ -6,7 +6,7 @@ module Bosh
6
6
  class LocalIdentityProvider
7
7
  extend Forwardable
8
8
 
9
- def initialize(options, _)
9
+ def initialize(options)
10
10
  users = options.fetch('users', [])
11
11
  @user_manager = Bosh::Director::Api::UserManagerProvider.new.user_manager(users)
12
12
  end
@@ -18,7 +18,7 @@ module Bosh
18
18
  {'type' => 'basic', 'options' => {}}
19
19
  end
20
20
 
21
- def get_user(request_env)
21
+ def get_user(request_env, _)
22
22
  auth ||= Rack::Auth::Basic::Request.new(request_env)
23
23
  raise AuthenticationError unless auth.provided? && auth.basic? && auth.credentials
24
24
 
@@ -38,7 +38,11 @@ module Bosh
38
38
  end
39
39
  end
40
40
 
41
- class LocalUser < Struct.new(:username, :password); end
41
+ class LocalUser < Struct.new(:username, :password)
42
+ def scopes
43
+ ['bosh.admin']
44
+ end
45
+ end
42
46
  end
43
47
  end
44
48
  end
@@ -4,10 +4,12 @@ module Bosh
4
4
  module Director
5
5
  module Api
6
6
  class UAAIdentityProvider
7
- def initialize(options, director_uuid_provider)
7
+ MAX_TOKEN_EXTENSION_TIME_IN_SECONDS = 3600
8
+
9
+ def initialize(options)
8
10
  @url = options.fetch('url')
9
11
  Config.logger.debug "Initializing UAA Identity provider with url #{@url}"
10
- @director_uuid = director_uuid_provider.uuid
12
+ @permission_authorizer = Bosh::Director::PermissionAuthorizer.new
11
13
  @token_coder = CF::UAA::TokenCoder.new(skey: options.fetch('symmetric_key', nil), pkey: options.fetch('public_key', nil), scope: [])
12
14
  end
13
15
 
@@ -24,9 +26,20 @@ module Bosh
24
26
  }
25
27
  end
26
28
 
27
- def get_user(request_env)
29
+ def get_user(request_env, options)
28
30
  auth_header = request_env['HTTP_AUTHORIZATION']
29
- token = @token_coder.decode(auth_header)
31
+
32
+ if options[:extended_token_timeout]
33
+ request_time_in_seconds = request_env.fetch('HTTP_X_BOSH_UPLOAD_REQUEST_TIME').to_i
34
+ request_time_in_seconds = MAX_TOKEN_EXTENSION_TIME_IN_SECONDS if request_time_in_seconds > MAX_TOKEN_EXTENSION_TIME_IN_SECONDS
35
+
36
+ Config.logger.debug("Using extended token timeout, request took #{request_time_in_seconds} seconds")
37
+
38
+ token = @token_coder.decode_at_reference_time(auth_header, Time.now.to_i - request_time_in_seconds)
39
+ else
40
+ token = @token_coder.decode(auth_header)
41
+ end
42
+
30
43
  UaaUser.new(token)
31
44
  rescue CF::UAA::DecodeError, CF::UAA::AuthError => e
32
45
  raise AuthenticationError, e.message
@@ -35,36 +48,16 @@ module Bosh
35
48
  def valid_access?(user, requested_access)
36
49
  if user.scopes
37
50
  required_scopes = required_scopes(requested_access)
38
- return has_admin_scope?(user.scopes) || contains_requested_scope?(required_scopes, user.scopes)
51
+ return @permission_authorizer.has_team_admin_scope?(user.scopes) ||
52
+ @permission_authorizer.has_admin_scope?(user.scopes) ||
53
+ @permission_authorizer.contains_requested_scope?(required_scopes, user.scopes)
39
54
  end
40
55
 
41
56
  false
42
57
  end
43
58
 
44
59
  def required_scopes(requested_access)
45
- permissions[requested_access]
46
- end
47
-
48
- private
49
-
50
- def permissions
51
- {
52
- :read => ['bosh.admin', "bosh.#{@director_uuid}.admin", 'bosh.read', "bosh.#{@director_uuid}.read"],
53
- :write => ['bosh.admin', "bosh.#{@director_uuid}.admin"]
54
- }
55
- end
56
-
57
- def has_admin_scope?(token_scopes)
58
- !(intersect(permissions[:write], token_scopes).empty?)
59
- end
60
-
61
- def contains_requested_scope?(valid_scopes, token_scopes)
62
- return false unless valid_scopes
63
- !(intersect(valid_scopes, token_scopes).empty?)
64
- end
65
-
66
- def intersect(valid_scopes, token_scopes)
67
- valid_scopes & token_scopes
60
+ @permission_authorizer.permissions[requested_access]
68
61
  end
69
62
  end
70
63
 
@@ -430,7 +430,7 @@ module Bosh::Director
430
430
  end
431
431
 
432
432
  Config.logger.debug("Director configured with '#{provider_name}' user management provider")
433
- provider_class.new(user_management[provider_name] || {}, Bosh::Director::Api::DirectorUUIDProvider.new(Config))
433
+ provider_class.new(user_management[provider_name] || {})
434
434
  end
435
435
  end
436
436
 
@@ -1,22 +1,40 @@
1
- module Bosh
2
- module Director
3
- module DeploymentPlan
4
- class DeploymentRepo
5
- def find_or_create_by_name(name)
6
- deployment = Models::Deployment.find(name: name)
7
- return deployment if deployment
1
+ module Bosh::Director
2
+ module DeploymentPlan
3
+ class DeploymentRepo
4
+ def initialize
5
+ @permission_authorizer = Bosh::Director::PermissionAuthorizer.new
6
+ end
7
+
8
+ def find_or_create_by_name(name, options={})
9
+ attributes = {name: name}
10
+ deployment = Bosh::Director::Models::Deployment.find(attributes)
11
+
12
+ if options['scopes']
13
+ attributes.merge!(scopes: options['scopes'].join(','))
14
+ end
15
+
16
+ if options['scopes'] && deployment
17
+ @permission_authorizer.raise_error_if_unauthorized(options['scopes'], deployment.scopes.split(','))
18
+ end
19
+
20
+ return deployment if deployment
21
+
22
+ create_for_attributes(attributes)
23
+ end
24
+
25
+ private
8
26
 
9
- canonical_name = Canonicalizer.canonicalize(name)
10
- transactor = Transactor.new
11
- transactor.retryable_transaction(Models::Deployment.db) do
12
- Models::Deployment.each do |other|
13
- if Canonicalizer.canonicalize(other.name) == canonical_name
27
+ def create_for_attributes(attributes)
28
+ canonical_name = Canonicalizer.canonicalize(attributes[:name])
29
+ transactor = Transactor.new
30
+ transactor.retryable_transaction(Models::Deployment.db) do
31
+ Bosh::Director::Models::Deployment.each do |other|
32
+ if Canonicalizer.canonicalize(other.name) == canonical_name
14
33
  raise DeploymentCanonicalNameTaken,
15
- "Invalid deployment name `#{name}', canonical name already taken (`#{canonical_name}')"
16
- end
34
+ "Invalid deployment name `#{attributes[:name]}', canonical name already taken (`#{canonical_name}')"
17
35
  end
18
- Models::Deployment.create(name: name)
19
36
  end
37
+ Bosh::Director::Models::Deployment.create(attributes)
20
38
  end
21
39
  end
22
40
  end
@@ -70,7 +70,17 @@ module Bosh::Director
70
70
  end
71
71
 
72
72
  def parse_jobs
73
+ if @deployment_manifest.has_key?('jobs') && @deployment_manifest.has_key?('instance_groups')
74
+ raise JobBothInstanceGroupAndJob, "Deployment specifies both jobs and instance_groups keys, only one is allowed"
75
+ end
76
+
73
77
  jobs = safe_property(@deployment_manifest, 'jobs', :class => Array, :default => [])
78
+ instance_groups = safe_property(@deployment_manifest, 'instance_groups', :class => Array, :default => [])
79
+
80
+ if !instance_groups.empty?
81
+ jobs = instance_groups
82
+ end
83
+
74
84
  jobs.each do |job_spec|
75
85
  # get state specific for this job or all jobs
76
86
  state_overrides = @job_states.fetch(job_spec['name'], @job_states.fetch('*', {}))
@@ -114,6 +114,7 @@ module Bosh::Director
114
114
  state: state,
115
115
  compilation: @compilation,
116
116
  uuid: SecureRandom.uuid,
117
+ availability_zone: availability_zone_name,
117
118
  bootstrap: false
118
119
  })
119
120
  @uuid = @model.uuid
@@ -116,6 +116,11 @@ module Bosh::Director
116
116
 
117
117
  def parse_templates
118
118
  templates = safe_property(@job_spec, 'templates', class: Array, optional: true)
119
+ jobs = safe_property(@job_spec, 'jobs', class: Array, optional: true)
120
+
121
+ if jobs && !jobs.empty?
122
+ templates = jobs
123
+ end
119
124
 
120
125
  if templates
121
126
  templates.each do |template_spec|
@@ -325,15 +330,23 @@ module Bosh::Director
325
330
  def validate_templates
326
331
  template_property = safe_property(@job_spec, 'template', optional: true)
327
332
  templates_property = safe_property(@job_spec, 'templates', optional: true)
333
+ jobs_property = safe_property(@job_spec, 'jobs', optional: true)
328
334
 
329
335
  if template_property && templates_property
330
- raise JobInvalidTemplates,
331
- "Job `#{@job.name}' specifies both template and templates keys, only one is allowed"
336
+ raise JobInvalidTemplates, "Job `#{@job.name}' specifies both template and templates keys, only one is allowed"
337
+ end
338
+
339
+ if templates_property && jobs_property
340
+ raise JobInvalidTemplates, "Job `#{@job.name}' specifies both templates and jobs keys, only one is allowed"
341
+ end
342
+
343
+ if template_property && jobs_property
344
+ raise JobInvalidTemplates, "Job `#{@job.name}' specifies both template and jobs keys, only one is allowed"
332
345
  end
333
346
 
334
- if [template_property, templates_property].compact.empty?
347
+ if [template_property, templates_property, jobs_property].compact.empty?
335
348
  raise ValidationMissingField,
336
- "Job `#{@job.name}' does not specify template or templates keys, one is required"
349
+ "Job `#{@job.name}' does not specify template, templates, or jobs keys, one is required"
337
350
  end
338
351
  end
339
352
 
@@ -69,10 +69,13 @@ module Bosh::Director
69
69
 
70
70
  static_ips = Set.new
71
71
  each_ip(static_property) do |ip|
72
- unless range.contains?(ip) && !restricted_ips.include?(ip)
72
+ if restricted_ips.include?(ip)
73
73
  raise NetworkStaticIpOutOfRange,
74
- "Static IP `#{format_ip(ip)}' is out of " +
75
- "network `#{network_name}' range"
74
+ "Static IP `#{format_ip(ip)}' is in network `#{network_name}' reserved range"
75
+ end
76
+ unless range.contains?(ip)
77
+ raise NetworkStaticIpOutOfRange,
78
+ "Static IP `#{format_ip(ip)}' is out of network `#{network_name}' range"
76
79
  end
77
80
  static_ips.add(ip)
78
81
  end
@@ -51,7 +51,7 @@ module Bosh
51
51
  @logger.debug("Migrated cloud config manifest:\n#{cloud_manifest}")
52
52
  name = deployment_manifest['name']
53
53
 
54
- deployment_model = @deployment_repo.find_or_create_by_name(name)
54
+ deployment_model = @deployment_repo.find_or_create_by_name(name, options)
55
55
 
56
56
  attrs = {
57
57
  name: name,
@@ -27,10 +27,13 @@ module Bosh::Director
27
27
  disk.update(:active => true) if disk
28
28
  end
29
29
 
30
- orphan_mounted_persistent_disk(instance, old_disk) if old_disk
30
+ orphan_mounted_persistent_disk(instance.model, old_disk) if old_disk
31
31
 
32
32
  inactive_disks = Models::PersistentDisk.where(active: false, instance: instance.model)
33
- inactive_disks.each{|disk| detach_and_orphan_disk(disk, instance.model) }
33
+ inactive_disks.each do |disk|
34
+ detach_disk(instance.model, disk)
35
+ orphan_disk(disk)
36
+ end
34
37
  end
35
38
 
36
39
  def attach_disks_if_needed(instance_plan)
@@ -65,6 +68,24 @@ module Bosh::Director
65
68
  end
66
69
  end
67
70
 
71
+ def unorphan_disk(disk, instance_id)
72
+ @transactor.retryable_transaction(Bosh::Director::Config.db) do
73
+ new_disk = Models::PersistentDisk.create(
74
+ disk_cid: disk.disk_cid,
75
+ instance_id: instance_id,
76
+ active: true,
77
+ size: disk.size,
78
+ cloud_properties: disk.cloud_properties)
79
+
80
+ disk.orphan_snapshots.each do |snapshot|
81
+ Models::Snapshot.create(persistent_disk: new_disk, snapshot_cid: snapshot.snapshot_cid, clean: snapshot.clean)
82
+ snapshot.destroy
83
+ end
84
+
85
+ disk.destroy
86
+ end
87
+ end
88
+
68
89
  def list_orphan_disks
69
90
  Models::OrphanDisk.all.map do |disk|
70
91
  {
@@ -92,7 +113,7 @@ module Bosh::Director
92
113
  def unmount_disk_for(instance_plan)
93
114
  disk = instance_plan.instance.model.persistent_disk
94
115
  return if disk.nil?
95
- unmount(instance_plan.instance, disk)
116
+ unmount_disk(instance_plan.instance.model, disk)
96
117
  end
97
118
 
98
119
  def delete_orphan_disk(orphan_disk)
@@ -109,33 +130,62 @@ module Bosh::Director
109
130
  end
110
131
  end
111
132
 
112
- def orphan_mounted_persistent_disk(instance, disk)
113
- unmount(instance, disk)
114
-
115
- disk_cid = disk.disk_cid
116
- if disk_cid.nil?
117
- @logger.info('Skipping disk detaching, instance does not have a disk')
118
- return
119
- end
120
-
121
- detach_and_orphan_disk(disk, instance.model)
122
- end
123
-
124
133
  def attach_disk(instance_model)
125
134
  disk_cid = instance_model.persistent_disk_cid
126
135
  return @logger.info('Skipping disk attaching') if disk_cid.nil?
127
136
 
128
137
  begin
129
138
  @cloud.attach_disk(instance_model.vm_cid, disk_cid)
130
- AgentClient.with_vm_credentials_and_agent_id(instance_model.credentials, instance_model.agent_id).mount_disk(disk_cid)
139
+ agent_client(instance_model).mount_disk(disk_cid)
131
140
  rescue => e
132
141
  @logger.warn("Failed to attach disk to new VM: #{e.inspect}")
133
142
  raise e
134
143
  end
135
144
  end
136
145
 
146
+ def detach_disk(instance_model, disk)
147
+ begin
148
+ @logger.info("Detaching disk #{disk.disk_cid}")
149
+ @cloud.detach_disk(instance_model.vm_cid, disk.disk_cid)
150
+ rescue Bosh::Clouds::DiskNotAttached
151
+ if disk.active
152
+ raise CloudDiskNotAttached,
153
+ "`#{instance_model}' VM should have persistent disk attached " +
154
+ "but it doesn't (according to CPI)"
155
+ end
156
+ end
157
+ end
158
+
159
+ def unmount_disk(instance_model, disk)
160
+ disk_cid = disk.disk_cid
161
+ if disk_cid.nil?
162
+ @logger.info('Skipping disk unmounting, instance does not have a disk')
163
+ return
164
+ end
165
+
166
+ if agent_mounted_disks(instance_model).include?(disk_cid)
167
+ @logger.info("Stopping instance '#{instance_model}' before unmount")
168
+ agent_client(instance_model).stop
169
+ @logger.info("Unmounting disk '#{disk_cid}'")
170
+ agent_client(instance_model).unmount_disk(disk_cid)
171
+ end
172
+ end
173
+
137
174
  private
138
175
 
176
+ def orphan_mounted_persistent_disk(instance_model, disk)
177
+ unmount_disk(instance_model, disk)
178
+
179
+ disk_cid = disk.disk_cid
180
+ if disk_cid.nil?
181
+ @logger.info('Skipping disk detaching, instance does not have a disk')
182
+ return
183
+ end
184
+
185
+ detach_disk(instance_model, disk)
186
+ orphan_disk(disk)
187
+ end
188
+
139
189
  def delete_orphan_snapshot(orphan_snapshot)
140
190
  begin
141
191
  snapshot_cid = orphan_snapshot.snapshot_cid
@@ -157,36 +207,7 @@ module Bosh::Director
157
207
  clean: snapshot.clean,
158
208
  snapshot_created_at: snapshot.created_at
159
209
  )
160
- snapshot.delete
161
- end
162
- end
163
-
164
- def detach_and_orphan_disk(disk, instance_model)
165
- begin
166
- @logger.info("Detaching disk #{disk.disk_cid}")
167
- @cloud.detach_disk(instance_model.vm_cid, disk.disk_cid)
168
- rescue Bosh::Clouds::DiskNotAttached
169
- if disk.active
170
- raise CloudDiskNotAttached,
171
- "`#{instance_model}' VM should have persistent disk attached " +
172
- "but it doesn't (according to CPI)"
173
- end
174
- end
175
- orphan_disk(disk)
176
- end
177
-
178
- def unmount(instance, disk)
179
- disk_cid = disk.disk_cid
180
- if disk_cid.nil?
181
- @logger.info('Skipping disk unmounting, instance does not have a disk')
182
- return
183
- end
184
-
185
- if agent_mounted_disks(instance).include?(disk_cid)
186
- @logger.info("Stopping instance '#{instance}' before unmount")
187
- agent_client(instance).stop
188
- @logger.info("Unmounting disk '#{disk_cid}'")
189
- agent_client(instance).unmount_disk(disk_cid)
210
+ snapshot.destroy
190
211
  end
191
212
  end
192
213
 
@@ -196,7 +217,7 @@ module Bosh::Director
196
217
  def check_persistent_disk(instance_plan)
197
218
  instance = instance_plan.instance
198
219
  return if instance.model.persistent_disks.empty?
199
- agent_disk_cid = agent_mounted_disks(instance).first
220
+ agent_disk_cid = agent_mounted_disks(instance.model).first
200
221
 
201
222
  if agent_disk_cid.nil? && !instance_plan.needs_disk?
202
223
  @logger.debug('Disk is already detached')
@@ -214,12 +235,12 @@ module Bosh::Director
214
235
  end
215
236
  end
216
237
 
217
- def agent_mounted_disks(instance)
218
- agent_client(instance).list_disk
238
+ def agent_mounted_disks(instance_model)
239
+ agent_client(instance_model).list_disk
219
240
  end
220
241
 
221
- def agent_client(instance)
222
- AgentClient.with_vm_credentials_and_agent_id(instance.model.credentials, instance.model.agent_id)
242
+ def agent_client(instance_model)
243
+ AgentClient.with_vm_credentials_and_agent_id(instance_model.credentials, instance_model.agent_id)
223
244
  end
224
245
 
225
246
  def create_and_attach_disk(instance_plan, vm_recreator)
@@ -247,14 +268,14 @@ module Bosh::Director
247
268
  end
248
269
 
249
270
  def mount_and_migrate_disk(instance, new_disk, old_disk)
250
- agent_client = agent_client(instance)
271
+ agent_client = agent_client(instance.model)
251
272
  agent_client.mount_disk(new_disk.disk_cid)
252
273
  # Mirgate to and from cids are actually ignored by the agent.
253
274
  # The first mount invocation is the source, and the last mount invocation is the target.
254
275
  agent_client.migrate_disk(old_disk.disk_cid, new_disk.disk_cid) if old_disk
255
276
  rescue => e
256
277
  @logger.debug("Failed to migrate disk, deleting new disk. #{e.inspect}")
257
- orphan_mounted_persistent_disk(instance, new_disk)
278
+ orphan_mounted_persistent_disk(instance.model, new_disk)
258
279
  raise e
259
280
  end
260
281
 
@@ -264,8 +285,8 @@ module Bosh::Director
264
285
 
265
286
  disk_size = job.persistent_disk_type.disk_size
266
287
  cloud_properties = job.persistent_disk_type.cloud_properties
267
-
268
288
  disk_cid = @cloud.create_disk(disk_size, cloud_properties, instance_model.vm_cid)
289
+
269
290
  Models::PersistentDisk.create(
270
291
  disk_cid: disk_cid,
271
292
  active: false,
@@ -175,6 +175,7 @@ module Bosh::Director
175
175
  JobMissingAvailabilityZones = err(140017)
176
176
  JobUnknownAvailabilityZone = err(140018)
177
177
  JobAmbiguousEnv = err(140019)
178
+ JobBothInstanceGroupAndJob = err(140020)
178
179
 
179
180
  # Manifest parsing: job networks section
180
181
  JobUnknownNetwork = err(150001)
@@ -267,4 +268,7 @@ module Bosh::Director
267
268
  AttachDiskErrorUnknownInstance = err(520001)
268
269
  AttachDiskNoPersistentDisk = err(520002)
269
270
  AttachDiskInvalidInstanceState = err(520003)
271
+
272
+ # Authorization errors
273
+ UnauthorizedToAccessDeployment = err(600000)
270
274
  end
@@ -24,7 +24,14 @@ module Bosh::Director
24
24
 
25
25
  if @instance.state == 'started'
26
26
  if current_state['job_state'] != 'running'
27
- raise AgentJobNotRunning, "`#{@instance}' is not running after update"
27
+ failing_jobs = Array(current_state['processes']).map do |process|
28
+ process['name'] if process['state'] != 'starting' && process['state'] != 'running'
29
+ end.compact
30
+
31
+ error_message = "`#{@instance}' is not running after update."
32
+ error_message += " Review logs for failed jobs: #{failing_jobs.join(", ")}" if !failing_jobs.empty?
33
+
34
+ raise AgentJobNotRunning, error_message
28
35
  else
29
36
  @agent_client.run_script('post-start', {})
30
37
  end
@@ -18,11 +18,28 @@ module Bosh::Director
18
18
  @instance_id = instance_id
19
19
  @disk_cid = disk_cid
20
20
  @transactor = Transactor.new
21
+ @disk_manager = DiskManager.new(Config.cloud, logger)
21
22
  end
22
23
 
23
24
  def perform
24
25
  instance = query_instance_model
26
+ validate_instance(instance)
25
27
 
28
+ @transactor.retryable_transaction(instance.db) do
29
+ handle_previous_disk(instance) if instance.persistent_disk
30
+ handle_new_disk(instance)
31
+ end
32
+
33
+ "attached disk '#{@disk_cid}' to '#{@job_name}/#{@instance_id}' in deployment '#{@deployment_name}'"
34
+ end
35
+
36
+ private
37
+
38
+ def query_instance_model
39
+ Models::Instance.filter(job: @job_name, uuid: @instance_id).first
40
+ end
41
+
42
+ def validate_instance(instance)
26
43
  if instance.nil? || instance.deployment.name != @deployment_name
27
44
  raise AttachDiskErrorUnknownInstance, "Instance '#{@job_name}/#{@instance_id}' in deployment '#{@deployment_name}' was not found"
28
45
  end
@@ -30,94 +47,34 @@ module Bosh::Director
30
47
  if instance.state != 'detached' && instance.state != 'stopped'
31
48
  raise AttachDiskInvalidInstanceState, "Instance '#{@job_name}/#{@instance_id}' in deployment '#{@deployment_name}' must be in 'bosh stopped' state"
32
49
  end
50
+ end
33
51
 
34
- if instance.persistent_disk.nil?
35
- raise AttachDiskNoPersistentDisk, "Job '#{@job_name}' is not configured with a persistent disk"
36
- end
37
-
52
+ def handle_previous_disk(instance)
38
53
  previous_persistent_disk = instance.persistent_disk
39
- @transactor.retryable_transaction(instance.db) do
40
- instance.persistent_disk.update(active: false)
41
-
42
- orphan_disk = Models::OrphanDisk[:disk_cid => @disk_cid]
43
- if orphan_disk
44
-
45
- current_disk = Models::PersistentDisk[instance_id: instance.id]
46
- if current_disk
47
- create_orphan_disk_from_current_disk(current_disk)
48
- end
49
-
50
- create_new_disk_from_orphan_disk(orphan_disk, instance)
51
- else
52
- Models::PersistentDisk.create(disk_cid: @disk_cid, instance_id: instance.id, active: true, size: 1, cloud_properties: {})
53
- end
54
- end
54
+ previous_persistent_disk.update(active: false)
55
55
 
56
56
  if instance.state == 'stopped'
57
- instance = query_instance_model
58
- deployment_plan_instance = deployment_plan_instance(instance)
59
- if deployment_plan_instance.nil?
60
- raise AttachDiskErrorUnknownInstance, "Deployment plan instance not found for instance model with id #{@instance_id}"
61
- end
62
-
63
- disk_manager = DiskManager.new(Config.cloud, logger)
64
- disk_manager.orphan_mounted_persistent_disk(deployment_plan_instance, previous_persistent_disk)
65
- disk_manager.attach_disk(instance)
57
+ @disk_manager.detach_disk(instance, previous_persistent_disk)
58
+ @disk_manager.unmount_disk(instance, previous_persistent_disk)
66
59
  end
67
60
 
68
- "attached disk '#{@disk_cid}' to '#{@job_name}/#{@instance_id}' in deployment '#{@deployment_name}'"
61
+ @disk_manager.orphan_disk(previous_persistent_disk)
69
62
  end
70
63
 
71
- private
72
-
73
- def create_orphan_disk_from_current_disk(current_disk)
74
- new_orphan_disk = Models::OrphanDisk.create(disk_cid: current_disk.disk_cid,
75
- size: current_disk.size,
76
- availability_zone: current_disk.instance.availability_zone,
77
- deployment_name: current_disk.instance.deployment.name,
78
- instance_name: current_disk.instance.name,
79
- cloud_properties: current_disk.cloud_properties)
80
-
81
- current_disk.snapshots.each do |snapshot|
82
- Models::OrphanSnapshot.create(orphan_disk: new_orphan_disk,
83
- snapshot_cid: snapshot.snapshot_cid,
84
- clean: snapshot.clean,
85
- snapshot_created_at: snapshot.created_at)
86
- snapshot.destroy
64
+ def handle_new_disk(instance)
65
+ orphan_disk = Models::OrphanDisk[:disk_cid => @disk_cid]
66
+ if orphan_disk
67
+ @disk_manager.unorphan_disk(orphan_disk, instance.id)
68
+ else
69
+ Models::PersistentDisk.create(disk_cid: @disk_cid, instance_id: instance.id, active: true, size: 1, cloud_properties: {})
87
70
  end
88
71
 
89
- current_disk.destroy
90
- end
91
-
92
- def create_new_disk_from_orphan_disk(orphan_disk, instance)
93
- new_disk = Models::PersistentDisk.create(disk_cid: orphan_disk.disk_cid,
94
- instance_id: instance.id,
95
- active: true,
96
- size: orphan_disk.size,
97
- cloud_properties: orphan_disk.cloud_properties)
98
-
99
- orphan_disk.orphan_snapshots.each do |snapshot|
100
- Models::Snapshot.create(persistent_disk: new_disk, snapshot_cid: snapshot.snapshot_cid, clean: snapshot.clean)
101
- snapshot.destroy
72
+ if instance.state == 'stopped'
73
+ instance = query_instance_model
74
+ @disk_manager.attach_disk(instance)
102
75
  end
103
-
104
- orphan_disk.destroy
105
- end
106
-
107
- def query_instance_model
108
- Models::Instance.filter(job: @job_name, uuid: @instance_id).to_a.first
109
76
  end
110
77
 
111
- def deployment_plan_instance(instance)
112
- deployment_model = Api::DeploymentLookup.new.by_name(@deployment_name)
113
- planner_factory = DeploymentPlan::PlannerFactory.create(logger)
114
- deployment_plan = planner_factory.create_from_model(deployment_model)
115
- job = deployment_plan.job(@job_name)
116
-
117
- deployment_plan_instance = DeploymentPlan::Instance.create_from_job(job, 0, instance.state, deployment_model, instance.state, instance.availability_zone, logger)
118
- deployment_plan_instance.bind_existing_instance_model(instance)
119
- deployment_plan_instance
120
- end
121
78
  end
122
79
  end
123
80
  end
@@ -12,8 +12,8 @@ module Bosh::Director
12
12
  def initialize(manifest_file_path, cloud_config_id, options = {})
13
13
  @blobstore = App.instance.blobstores.blobstore
14
14
  @manifest_file_path = manifest_file_path
15
- @options = options
16
15
  @cloud_config_id = cloud_config_id
16
+ @options = options
17
17
  end
18
18
 
19
19
  def perform
@@ -26,6 +26,11 @@ module Bosh::Director::Models
26
26
  end
27
27
  end
28
28
 
29
+ def self.uuid
30
+ uuid = first(name: 'uuid')
31
+ return uuid.value if uuid
32
+ end
33
+
29
34
  def self.update_or_create_uuid(value, logger)
30
35
  if where(name: 'uuid').update(value: value) == 0
31
36
  create(name: 'uuid', value: value)
@@ -0,0 +1,54 @@
1
+ module Bosh::Director
2
+ class PermissionAuthorizer
3
+ def initialize
4
+ @director_uuid ||= Bosh::Director::Models::DirectorAttribute.uuid
5
+ end
6
+
7
+ def has_admin_scope?(token_scopes)
8
+ !(intersect(permissions[:write], token_scopes).empty?)
9
+ end
10
+
11
+ def has_admin_or_director_read_scope?(token_scopes)
12
+ !(intersect(permissions[:read], token_scopes).empty?)
13
+ end
14
+
15
+ def has_team_admin_scope?(token_scopes)
16
+ token_scopes.any? do |e|
17
+ /bosh.teams.[^\.]+.admin/ =~ e
18
+ end
19
+ end
20
+
21
+ def contains_requested_scope?(valid_scopes, token_scopes)
22
+ return false unless valid_scopes
23
+ !(intersect(valid_scopes, token_scopes).empty?)
24
+ end
25
+
26
+ def permissions
27
+ {
28
+ :read => ['bosh.admin', "bosh.#{@director_uuid}.admin", 'bosh.read', "bosh.#{@director_uuid}.read"],
29
+ :write => ['bosh.admin', "bosh.#{@director_uuid}.admin"]
30
+ }
31
+ end
32
+
33
+ def is_authorized?(provided_scopes, token_scopes)
34
+ return true if has_admin_or_director_read_scope?(token_scopes)
35
+
36
+ return contains_requested_scope?(provided_scopes, token_scopes)
37
+ end
38
+
39
+ def raise_error_if_unauthorized(provided_scopes, deployment_scopes)
40
+ return if has_admin_scope?(provided_scopes)
41
+
42
+ if (deployment_scopes & provided_scopes).empty?
43
+ raise Bosh::Director::UnauthorizedToAccessDeployment,
44
+ 'You are unauthorized to view this deployment. Please contact the BOSH admin.'
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def intersect(valid_scopes, token_scopes)
51
+ valid_scopes & token_scopes
52
+ end
53
+ end
54
+ end
@@ -1,5 +1,5 @@
1
1
  module Bosh
2
2
  module Director
3
- VERSION = '1.3200.0'
3
+ VERSION = '1.3202.0'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bosh-director
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3200.0
4
+ version: 1.3202.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - VMware
@@ -16,98 +16,98 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.3200.0
19
+ version: 1.3202.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 1.3200.0
26
+ version: 1.3202.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bosh_cpi
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.3200.0
33
+ version: 1.3202.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.3200.0
40
+ version: 1.3202.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bosh-registry
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 1.3200.0
47
+ version: 1.3202.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 1.3200.0
54
+ version: 1.3202.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: blobstore_client
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 1.3200.0
61
+ version: 1.3202.0
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 1.3200.0
68
+ version: 1.3202.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bosh-core
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 1.3200.0
75
+ version: 1.3202.0
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 1.3200.0
82
+ version: 1.3202.0
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: bosh-director-core
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 1.3200.0
89
+ version: 1.3202.0
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 1.3200.0
96
+ version: 1.3202.0
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: bosh-template
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 1.3200.0
103
+ version: 1.3202.0
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 1.3200.0
110
+ version: 1.3202.0
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: bosh_openstack_cpi
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -658,6 +658,7 @@ files:
658
658
  - db/migrations/director/20151229184742_add_vm_attributes_to_instance.rb
659
659
  - db/migrations/director/20160108191637_drop_vm_env_json_from_instance.rb
660
660
  - db/migrations/director/20160121003800_drop_vms_fkeys.rb
661
+ - db/migrations/director/20160211193904_add_scopes_to_deployment.rb
661
662
  - db/migrations/dns/20120123234908_initial.rb
662
663
  - lib/bosh/director.rb
663
664
  - lib/bosh/director/agent_client.rb
@@ -907,6 +908,7 @@ files:
907
908
  - lib/bosh/director/models/user.rb
908
909
  - lib/bosh/director/nats_rpc.rb
909
910
  - lib/bosh/director/network_reservation.rb
911
+ - lib/bosh/director/permission_authorizer.rb
910
912
  - lib/bosh/director/problem_handlers/base.rb
911
913
  - lib/bosh/director/problem_handlers/inactive_disk.rb
912
914
  - lib/bosh/director/problem_handlers/invalid_problem.rb