Skip to content

LightOS driver: record volume project in provider_id - #28

Open
yuval-lb wants to merge 4 commits into
masterfrom
yuval/LBM1-44442-project-in-provider-id
Open

LightOS driver: record volume project in provider_id#28
yuval-lb wants to merge 4 commits into
masterfrom
yuval/LBM1-44442-project-in-provider-id

Conversation

@yuval-lb

@yuval-lb yuval-lb commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

PR description
Retyping a volume between two volume types that name different LightOS projects
left the source volume behind on the storage. It vanished from OpenStack's view
but kept consuming capacity, leaving only a permission-denied or not-found error
in the cinder-volume log.

A LightOS volume is addressed by its project, and the driver read that project
from the volume type on every call. A volume type is mutable. When a retype
needs the volume moved, Cinder deletes the source through a record that
deliberately keeps the destination volume type — a volume type is "not a key
for volume deletion" per Volume.finish_volume_migration. So the driver asked
the destination project to delete a volume living in the source project.

A volume's project and LightOS UUID are now recorded in its provider_id at
creation, and travel with whichever record addresses that volume, because
Cinder swaps provider_id between the two records during the hand-over. The
volume type stays as a fallback, so no data migration is needed.

Three commits: record the project and resolve from it; create volumes in the
project their volume type names (Cinder copies provider_id onto the
temporary migration volume, where it still names the source project); and stamp
pre-existing volumes at service start. That backfill runs before the service
accepts requests, so it takes the project from the volume type and makes no
cluster calls — provider_id accepts a project on its own, and the UUID is
resolved by name when needed.

How was the PR tested?

  • 9 new unit tests. Two drive a cross-project retype through the real
    VolumeManager.update_migrated_volume and finish_volume_migration and
    assert the source volume is gone. Others cover resolution, the volume-type
    fallback, an unparseable provider_id, creation ignoring an inherited one,
    and the backfill — that test fails on any cluster call, so "no IO" is
    asserted rather than assumed. 44/44 pass; 5 fail without this change.
    flake8 clean.
  • On a 3-node cluster with a volume type per project: retyped across projects.
    The source project kept only its untouched volume, the destination held the
    migrated one, and the delete went to the source project. Before this change
    it went to the destination and returned 404.
  • Backfill at scale: 100 volumes created by the old driver, then restarted on
    this branch. All stamped, no volume lookups, 940ms. A per-volume-lookup
    approach took 6.8s for the same 100, which extrapolates to ~10 min at 10,000.
    Three restarts of 100 already-stamped volumes took ~280ms each.

Not tested: the cross-project retype ran on commit 2 of 3, which the backfill
change does not touch. Test volumes were empty, so no migration copied real
data.

PR dependencies

Jira Ticket
Issue: LBM1-44442

@yuval-lb
yuval-lb force-pushed the yuval/LBM1-44442-project-in-provider-id branch from c775cef to e97dca6 Compare August 6, 2026 09:14
Retyping a volume between two volume types that name different LightOS
projects left the source volume behind on the storage. It disappeared
from OpenStack's view but kept consuming capacity. The only trace was a
permission-denied or not-found error in the cinder-volume log.

The trigger is any retype that changes lightos:project_name. A LightOS
volume cannot move between projects, so Cinder copies the data into a
new volume in the destination project and deletes the original
afterwards.

A LightOS volume is addressed by its project, so the driver has to know
which project a volume lives in. It used to read that from the volume
type on every call, and a volume type is mutable. Cinder deliberately
leaves the destination volume type on the database record it uses to
delete the source volume. So the driver asked the destination project to
delete a volume that lived in the source project, and the delete failed.

The project is now recorded on the volume when it is created, along with
the LightOS volume UUID, and travels with whichever record addresses
that volume. The volume type is only a fallback. Volumes created before
this change are stamped when the volume service starts.

Recording the UUID also drops a lookup: operations that need it no
longer resolve it from the volume name first. A delete that finds
nothing now counts as success, because the volume is addressed by a
recorded identity rather than a derived one.

If this recurs, look for a delete addressed to a project the volume was
never created in.

Assisted-by: Claude Opus 5
Signed-off-by: Yuval Brave <yuval@lightbitslabs.com>
A cross-project retype moved no data: the new volume was created in the
source project, so the volume never reached the project the operator
asked for. Caught on a live cluster - a retype from goose to fox left
both volumes in goose and fox empty.

Cinder builds the temporary volume for a migration by copying most
fields off the source volume, and provider_id is one of them. The driver
read the project back out of that inherited provider_id, which still
named the source project.

A volume type says where to create a volume. A provider_id records where
a volume already lives. Creation now reads the volume type, and only
operations on an existing volume read provider_id.

Assisted-by: Claude Opus 5
Signed-off-by: Yuval Brave <yuval@lightbitslabs.com>
The backfill that records a project on volumes created before we stored
one issued a lookup per volume. It runs from init_host, before the volume
service accepts requests, so on a host with tens of thousands of volumes
that alone kept cinder-volume out of service for minutes. A lookup that
could not reach an API server raised, and nothing between here and
init_host catches it, so a single unreachable server stopped the service
from starting at all.

Only the project is needed, and the volume type already carries it, so
the backfill no longer talks to the cluster. provider_id now accepts a
project on its own, and the UUID is resolved by name when something
needs it. Volumes that already carry a project are skipped, so a
restart of a stamped deployment produces no updates.

Anything unexpected while resolving a project leaves that volume
unstamped instead of aborting startup. It falls back to its volume type,
as it did before being stamped.

Assisted-by: Claude Opus 5
Signed-off-by: Yuval Brave <yuval@lightbitslabs.com>
@yuval-lb
yuval-lb force-pushed the yuval/LBM1-44442-project-in-provider-id branch from e97dca6 to 0aad280 Compare August 6, 2026 10:20
@ronen-lb

ronen-lb commented Aug 9, 2026

Copy link
Copy Markdown

small clarifications to PR cover letter:

A LightOS volume is addressed by its project, and the driver read that project
from the volume type on every call. ->
A LightOS volume is addressed by its project and volume name, the driver gets that project
from the volume type on every call.

So the driver asked the destination project to delete a volume living in the source project. ->
So the driver errounously used the destination project, when deleting the original volume that may
have resided on a diffrent (source) project.

A volume's project and LightOS UUID are now recorded in its provider_id at
creation, and travel with whichever record addresses that volume, because
Cinder swaps provider_id between the two records during the hand-over. The
volume type stays as a fallback, so no data migration is needed.

Possibly note that project field is inited for all existing volumes at driver startup,
and only project field in provider id is currently used to access volume.

Three commits: record the project and resolve from it; create volumes in the
project their volume type names (Cinder copies provider_id onto the
temporary migration volume, where it still names the source project); and stamp
pre-existing volumes at service start. That backfill runs before the service
accepts requests, so it takes the project from the volume type and makes no
cluster calls — provider_id accepts a project on its own, and the UUID is
resolved by name when needed.

@ronen-lb ronen-lb 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.

Review scope: full diff.

Review assisted by an AI agent; findings were checked by a human before posting.

Comment thread releasenotes/notes/lightos-fix-retype-orphaned-volume-6ba3f4b1c8de7a25.yaml Outdated

try:
project_name = self._get_volume_type_project_name(volume)
except Exception:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[major] _get_volume_type_project_name catches every exception itself and returns LIGHTOS_DEFAULT_PROJECT_NAME, so this handler cannot fire, and the two cases it is meant to separate arrive here identically: an untyped volume, where default is the right answer, and a typed volume whose type could not be read, where it is a guess. In the second case the volume is stamped default permanently — and since provider_id now wins over the volume type, a volume that lives elsewhere stops resolving to its real project even once the type is readable again.

Having the helper distinguish "no project specified" from "lookup failed" would let a failed lookup leave the volume unstamped, which is what the comment on the next line describes.

Comment thread cinder/volume/drivers/lightos.py Outdated
Comment thread cinder/tests/unit/volume/drivers/lightos/test_lightos_storage.py Outdated
Comment thread cinder/volume/drivers/lightos.py Outdated
Motivation: the provider_id backfill guessed the default project when a
volume's type could not be read, and recorded the guess in provider_id,
where it outranks the volume type - a volume living elsewhere would
never resolve to its real project again, even once its type became
readable. Alongside it, three smaller review findings: the release note
linked a tracker its readers cannot open; a delete answered NOT_FOUND
counted as success with no trace, which is the only signal left when a
delete is addressed to the wrong project; and the retype tests carried
a parameter no caller used while duplicating the migration hand-over
inline.

Implementation: _get_volume_type_project_name resolves untyped volumes
to the default project and lets a failed type read raise, so the
backfill leaves such volumes unstamped and they keep resolving from
their volume type as before. Runtime address resolution keeps the old
default-project guess, now behind an explicit catch that logs a
warning. A NOT_FOUND delete logs the project and UUID it was addressed
to. The migration hand-over moved to a helper both retype tests share,
and create_cloned_volume's returned model_update is now asserted, since
a clone that stops recording its address would silently fall back to
the volume type.

Bug root cause: _get_volume_type_project_name caught every exception
internally and returned the default project, so the backfill's except
clause was dead code and a failed type read was indistinguishable from
a volume that legitimately has no type.

Issue: LBM1-44442

Assisted-by: Claude Fable 5
Change-Id: I43e73b8abefbd4541d16a76d551bb0d8d616e5ad
Signed-off-by: Yuval Brave <yuval@lightbitslabs.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants