Skip to content

Add create, attach, and list endpoints to dynamic disks API#2743

Draft
Alphasite wants to merge 1 commit into
mainfrom
feature/dynamic-disk-phase2-cli-endpoints
Draft

Add create, attach, and list endpoints to dynamic disks API#2743
Alphasite wants to merge 1 commit into
mainfrom
feature/dynamic-disk-phase2-cli-endpoints

Conversation

@Alphasite

@Alphasite Alphasite commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Extends the BOSH Director's dynamic disk API with three new endpoints to support Phase 2 CLI commands (TNZ-109499):

  • GET /dynamic_disks — list all dynamic disks (bosh.admin or bosh.dynamic-disks.list scope)
  • POST /dynamic_disks — create a disk in the IaaS without attaching it (bosh.dynamic-disks.create scope)
  • POST /dynamic_disks/:name/attach — attach an existing named disk to a VM (bosh.dynamic-disks.attach scope)

Changes

New files

  • src/bosh-director/lib/bosh/director/jobs/dynamic_disks/create_dynamic_disk.rb — background job that creates a disk via CPI without attaching
  • src/bosh-director/lib/bosh/director/jobs/dynamic_disks/attach_dynamic_disk.rb — background job that attaches a named disk to a VM (idempotent)
  • src/bosh-director/spec/unit/bosh/director/jobs/dynamic_disks/create_dynamic_disk_spec.rb
  • src/bosh-director/spec/unit/bosh/director/jobs/dynamic_disks/attach_dynamic_disk_spec.rb

Modified files

  • src/bosh-director/lib/bosh/director/api/controllers/dynamic_disks_controller.rb — added 3 new routes
  • src/bosh-director/lib/bosh/director.rb — required new job classes
  • src/bosh-director/spec/support/test_identity_provider.rb — added dynamic-disks-lister and dynamic-disks-attacher test users
  • src/bosh-director/spec/unit/bosh/director/api/controllers/dynamic_disks_controller_spec.rb — full coverage for all 6 endpoints

Test plan

  • bundle exec rspec spec/unit/bosh/director/api/controllers/dynamic_disks_controller_spec.rb spec/unit/bosh/director/jobs/dynamic_disks/70 examples, 0 failures

Related: TNZ-109499, TNZ-99509

Adds three new Director API endpoints to complement the existing
provide/detach/delete endpoints:

- GET  /dynamic_disks          list all dynamic disks (bosh.dynamic-disks.list)
- POST /dynamic_disks          create disk without attaching (bosh.dynamic-disks.create)
- POST /dynamic_disks/:name/attach  attach existing disk to a VM (bosh.dynamic-disks.attach)

Implements CreateDynamicDisk and AttachDynamicDisk background jobs,
both on the :dynamic_disks worker queue. All operations are idempotent.

70 unit specs, all passing.

[TNZ-109499]

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings June 17, 2026 21:18
@Alphasite Alphasite marked this pull request as draft June 17, 2026 21:18
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Two new background job classes are introduced under Bosh::Director::Jobs::DynamicDisks: CreateDynamicDisk and AttachDynamicDisk. CreateDynamicDisk resolves the target instance and active VM, calls the CPI to create a disk, persists a Models::DynamicDisk record, and optionally updates disk metadata. AttachDynamicDisk resolves the disk, instance, and VM, enforces attachment-state invariants, calls the CPI to attach the disk under a VM lock, updates the model with VM/zone/hint data, and notifies the agent. Three new routes are added to DynamicDisksController: GET / lists all dynamic disks, POST / enqueues CreateDynamicDisk, and POST /:disk_name/attach enqueues AttachDynamicDisk. Both job files are added to the director require graph. Full unit test coverage is added for both jobs and all three controller routes.

Suggested reviewers

  • rkoster
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding create, attach, and list endpoints to the dynamic disks API, which matches the primary objective of extending the API with three new endpoints.
Description check ✅ Passed The description covers all required sections from the template: change summary with context (TNZ-109499), new and modified files, test results (70 examples, 0 failures), and is written for the operator audience. All sections are present and informative.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/dynamic-disk-phase2-cli-endpoints

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the BOSH Director dynamic disks API with endpoints to list disks, create a disk (without attaching), and attach an existing disk to a VM, to support the next phase of CLI commands.

Changes:

  • Add GET /dynamic_disks to list dynamic disks (new scope: bosh.dynamic-disks.list).
  • Add POST /dynamic_disks to create a dynamic disk in the IaaS without attaching it.
  • Add POST /dynamic_disks/:name/attach to attach an existing named disk to a VM via a background job.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/bosh-director/lib/bosh/director/api/controllers/dynamic_disks_controller.rb Adds list/create/attach endpoints and enqueues new background jobs
src/bosh-director/lib/bosh/director/jobs/dynamic_disks/create_dynamic_disk.rb New job to create a dynamic disk without attaching
src/bosh-director/lib/bosh/director/jobs/dynamic_disks/attach_dynamic_disk.rb New job to attach an existing dynamic disk to an instance’s active VM
src/bosh-director/lib/bosh/director.rb Requires the newly added job classes
src/bosh-director/spec/support/test_identity_provider.rb Adds a scoped test user for the list endpoint
src/bosh-director/spec/unit/bosh/director/api/controllers/dynamic_disks_controller_spec.rb Adds controller coverage for the new endpoints and scope enforcement
src/bosh-director/spec/unit/bosh/director/jobs/dynamic_disks/create_dynamic_disk_spec.rb Adds unit tests for the create job
src/bosh-director/spec/unit/bosh/director/jobs/dynamic_disks/attach_dynamic_disk_spec.rb Adds unit tests for the attach job

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

include ValidationHelper

get '/', scope: :list_dynamic_disks do
disks = Models::DynamicDisk.all.map do |disk|
Comment on lines +27 to +33
cloud_properties = find_disk_cloud_properties(instance, @disk_pool_name)
cloud = Bosh::Director::CloudFactory.create.get(vm.cpi)

disk_model = Models::DynamicDisk.find(name: @disk_name)
if disk_model.nil?
cloud_properties['name'] = @disk_name
disk_cid = cloud.create_disk(@disk_size, cloud_properties, vm.cid)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/bosh-director/lib/bosh/director/api/controllers/dynamic_disks_controller.rb`:
- Around line 8-21: The get '/' route handler in the DynamicDisksController has
an N+1 query issue because it retrieves all dynamic disks with
Models::DynamicDisk.all and then accesses the associated objects
(disk.deployment and disk.vm.instance) within the map block, causing separate
database queries for each disk. Fix this by using Sequel's eager loading method
on the Models::DynamicDisk query to pre-load the deployment and vm associations
along with the nested instance association before performing the map operation.

In
`@src/bosh-director/lib/bosh/director/jobs/dynamic_disks/create_dynamic_disk.rb`:
- Around line 30-42: The code directly mutates the cloud_properties hash by
assigning cloud_properties['name'] = `@disk_name` on line 32, which could cause
unintended side effects if this hash is shared or cached. Instead of modifying
the original hash in place, create a new hash by merging the cloud_properties
with a new hash containing the name key, then pass this merged hash to the
cloud.create_disk call. This preserves the original cloud_properties hash and
prevents potential side effects from mutations.

In
`@src/bosh-director/spec/unit/bosh/director/api/controllers/dynamic_disks_controller_spec.rb`:
- Around line 337-356: Expand the validation test coverage for the POST '/'
endpoint to match the comprehensiveness of the existing POST '/provide' endpoint
tests. In addition to the current test contexts for disk_name being nil and
disk_size being 0, add more test cases covering nil values for other fields,
empty string validations, and boundary value tests for disk_size and other
numeric properties. Reference the validation test patterns used in the POST
'/provide' endpoint (lines 84-168) to ensure consistency in what scenarios are
being tested, while leveraging the same safe_property validation helper that
both endpoints use.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fb65caa2-5ab2-45be-a214-c92ffe8705bb

📥 Commits

Reviewing files that changed from the base of the PR and between df5d172 and 7e2883d.

📒 Files selected for processing (8)
  • src/bosh-director/lib/bosh/director.rb
  • src/bosh-director/lib/bosh/director/api/controllers/dynamic_disks_controller.rb
  • src/bosh-director/lib/bosh/director/jobs/dynamic_disks/attach_dynamic_disk.rb
  • src/bosh-director/lib/bosh/director/jobs/dynamic_disks/create_dynamic_disk.rb
  • src/bosh-director/spec/support/test_identity_provider.rb
  • src/bosh-director/spec/unit/bosh/director/api/controllers/dynamic_disks_controller_spec.rb
  • src/bosh-director/spec/unit/bosh/director/jobs/dynamic_disks/attach_dynamic_disk_spec.rb
  • src/bosh-director/spec/unit/bosh/director/jobs/dynamic_disks/create_dynamic_disk_spec.rb

Comment on lines +8 to +21
get '/', scope: :list_dynamic_disks do
disks = Models::DynamicDisk.all.map do |disk|
{
name: disk.name,
disk_cid: disk.disk_cid,
deployment: disk.deployment&.name,
instance: disk.vm&.instance&.name,
availability_zone: disk.availability_zone,
size: disk.size,
cpi: disk.cpi,
}
end
json_encode(disks)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider eager loading associations to prevent N+1 queries.

The current implementation accesses disk.deployment and disk.vm.instance inside the map block, which may trigger separate queries for each disk. With many dynamic disks, this could degrade performance.

Consider using Sequel's eager loading:

⚡ Suggested optimization
-      get '/', scope: :list_dynamic_disks do
-        disks = Models::DynamicDisk.all.map do |disk|
+      get '/', scope: :list_dynamic_disks do
+        disks = Models::DynamicDisk.eager(:deployment, vm: :instance).all.map do |disk|
           {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/bosh-director/lib/bosh/director/api/controllers/dynamic_disks_controller.rb`
around lines 8 - 21, The get '/' route handler in the DynamicDisksController has
an N+1 query issue because it retrieves all dynamic disks with
Models::DynamicDisk.all and then accesses the associated objects
(disk.deployment and disk.vm.instance) within the map block, causing separate
database queries for each disk. Fix this by using Sequel's eager loading method
on the Models::DynamicDisk query to pre-load the deployment and vm associations
along with the nested instance association before performing the map operation.

Comment on lines +30 to +42
disk_model = Models::DynamicDisk.find(name: @disk_name)
if disk_model.nil?
cloud_properties['name'] = @disk_name
disk_cid = cloud.create_disk(@disk_size, cloud_properties, vm.cid)
disk_model = Models::DynamicDisk.create(
name: @disk_name,
disk_cid: disk_cid,
deployment_id: instance.deployment.id,
size: @disk_size,
disk_pool_name: @disk_pool_name,
cpi: vm.cpi,
)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider avoiding mutation of cloud_properties hash.

Line 32 modifies the cloud_properties hash returned by find_disk_cloud_properties. If that helper returns a shared or cached hash, this mutation could have unintended side effects.

♻️ Defensive alternative using merge
-        cloud_properties = find_disk_cloud_properties(instance, `@disk_pool_name`)
-        cloud = Bosh::Director::CloudFactory.create.get(vm.cpi)
-
-        disk_model = Models::DynamicDisk.find(name: `@disk_name`)
-        if disk_model.nil?
-          cloud_properties['name'] = `@disk_name`
-          disk_cid = cloud.create_disk(`@disk_size`, cloud_properties, vm.cid)
+        cloud_properties = find_disk_cloud_properties(instance, `@disk_pool_name`)
+        cloud = Bosh::Director::CloudFactory.create.get(vm.cpi)
+
+        disk_model = Models::DynamicDisk.find(name: `@disk_name`)
+        if disk_model.nil?
+          disk_cid = cloud.create_disk(`@disk_size`, cloud_properties.merge('name' => `@disk_name`), vm.cid)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
disk_model = Models::DynamicDisk.find(name: @disk_name)
if disk_model.nil?
cloud_properties['name'] = @disk_name
disk_cid = cloud.create_disk(@disk_size, cloud_properties, vm.cid)
disk_model = Models::DynamicDisk.create(
name: @disk_name,
disk_cid: disk_cid,
deployment_id: instance.deployment.id,
size: @disk_size,
disk_pool_name: @disk_pool_name,
cpi: vm.cpi,
)
end
disk_model = Models::DynamicDisk.find(name: `@disk_name`)
if disk_model.nil?
disk_cid = cloud.create_disk(`@disk_size`, cloud_properties.merge('name' => `@disk_name`), vm.cid)
disk_model = Models::DynamicDisk.create(
name: `@disk_name`,
disk_cid: disk_cid,
deployment_id: instance.deployment.id,
size: `@disk_size`,
disk_pool_name: `@disk_pool_name`,
cpi: vm.cpi,
)
end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/bosh-director/lib/bosh/director/jobs/dynamic_disks/create_dynamic_disk.rb`
around lines 30 - 42, The code directly mutates the cloud_properties hash by
assigning cloud_properties['name'] = `@disk_name` on line 32, which could cause
unintended side effects if this hash is shared or cached. Instead of modifying
the original hash in place, create a new hash by merging the cloud_properties
with a new hash containing the name key, then pass this merged hash to the
cloud.create_disk call. This preserves the original cloud_properties hash and
prevents potential side effects from mutations.

Comment on lines +337 to +356
context 'when disk_name is nil' do
let(:disk_name) { nil }

it 'raises an error' do
post '/', content, { 'CONTENT_TYPE' => 'application/json' }
expect(last_response.status).to eq(400)
expect(JSON.parse(last_response.body)['description']).to include("disk_name")
end
end

context 'when disk_size is 0' do
let(:disk_size) { 0 }

it 'raises an error' do
post '/', content, { 'CONTENT_TYPE' => 'application/json' }
expect(last_response.status).to eq(400)
expect(JSON.parse(last_response.body)['description']).to include("disk_size")
end
end
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider expanding validation test coverage for consistency.

The POST '/' endpoint validation tests only cover disk_name=nil and disk_size=0, whereas the existing POST '/provide' endpoint (lines 84-168) includes more comprehensive validation tests (nil values, empty strings, boundary values for multiple fields).

While both endpoints use the same safe_property validation helper (so the underlying validation is covered), expanding the test cases here would improve consistency and make the test suite more robust.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/bosh-director/spec/unit/bosh/director/api/controllers/dynamic_disks_controller_spec.rb`
around lines 337 - 356, Expand the validation test coverage for the POST '/'
endpoint to match the comprehensiveness of the existing POST '/provide' endpoint
tests. In addition to the current test contexts for disk_name being nil and
disk_size being 0, add more test cases covering nil values for other fields,
empty string validations, and boundary value tests for disk_size and other
numeric properties. Reference the validation test patterns used in the POST
'/provide' endpoint (lines 84-168) to ensure consistency in what scenarios are
being tested, while leveraging the same safe_property validation helper that
both endpoints use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Waiting for Changes | Open for Contribution

Development

Successfully merging this pull request may close these issues.

2 participants