Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSI: allow updates to volumes on re-registration #12167

Merged
merged 5 commits into from
Mar 7, 2022

Conversation

tgross
Copy link
Member

@tgross tgross commented Mar 1, 2022

Fixes #11786 (as described in #11786 (comment))

The CSI CreateVolume RPC is idempotent given that the topology,
capabilities, and parameters are unchanged. CSI volumes have many
user-defined fields that are immutable once set, and many fields that
are not user-settable.

Update the Register RPC so that updating a volume via the API merges
onto any existing volume without touching Nomad-controlled fields,
while validating it with the same strict requirements expected for
idempotent CreateVolume RPCs.


Note to reviewers: I've broken out two small refactoring commits, so this is likely best reviewed commit-by-commit.

Clarify that this state store method is used for everything, not just
for the `Register` RPC.
CSI `CreateVolume` RPC is idempotent given that the topology,
capabilities, and parameters are unchanged. CSI volumes have many
user-defined fields that are immutable once set, and many fields that
are not user-settable.

Update the `Register` RPC so that updating a volume via the API merges
onto any existing volume without touching Nomad-controlled fields,
while validating it with the same strict requirements expected for
idempotent `CreateVolume` RPCs.
@tgross tgross force-pushed the csi-validate-volume-updates branch from 294ef74 to 2c23e25 Compare March 3, 2022 16:26
@tgross tgross marked this pull request as ready for review March 3, 2022 16:26
@vercel vercel bot temporarily deployed to Preview – nomad March 3, 2022 16:26 Inactive
Copy link
Contributor

@lgfa29 lgfa29 left a comment

Choose a reason for hiding this comment

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

LGMT.

I added a couple of comments, but I don't know if they are actually problems.

Comment on lines +2172 to +2173
s.CSIVolumeDenormalize(nil, old.Copy())
if old.InUse() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does passing a Copy to CSIVolumeDenormalize means that old is not denormalized at this point?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes! We denormalize-on-read rather than having to push volume updates on every allocation status update. The volume only gets written with those denormalized updates on claim transitions (which happens in either csi_hook.go on the client or in volumewatcher on the leader).

Comment on lines 805 to 818
for _, otherTopo := range other.RequestedTopologies.Required {
if !otherTopo.MatchFound(v.Topologies) {
err = errors.New(
"volume topology requirement update was not compatible with existing topology")
break
}
}
for _, otherTopo := range other.RequestedTopologies.Preferred {
if !otherTopo.MatchFound(v.Topologies) {
err = errors.New(
"volume topology preference update was not compatible with existing topology")
break
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

If topologies are immutable, do we need to check in the other direction as well? Meaning, can an update request reduce the scope of the topologies?

I wrote a test case like this and it fails because it doesn't return an error"

		{
			name: "invalid topology update - missing",
			v: &CSIVolume{
				Topologies: []*CSITopology{
					{Segments: map[string]string{"rack": "R1"}},
					{Segments: map[string]string{"rack": "R3"}},
				},
			},
			update: &CSIVolume{
				RequestedTopologies: &CSITopologyRequest{
					Required: []*CSITopology{
						{Segments: map[string]string{"rack": "R1"}},
					},
				},
			},
			expected: "volume topology request update was not compatible with existing topology",
			expectFn: func(t *testing.T, v *CSIVolume) {
				require.Len(t, v.Topologies, 2)
			},
		},

Copy link
Member Author

Choose a reason for hiding this comment

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

I think we're ok, but I should definitely add a test covering this case that asserts the expected behavior so that it's not only "accidentally ok".

The TopologyRequirement message in the CreateVolume spec has examples, but the high-level description is this:

If requisite is specified, the provisioned volume MUST be accessible from at least one of the requisite topologies.

The easiest example is to assume we're creating an AWS EBS volume. These are accessible within single availability zone. So suppose I provide the following topology request:

topology_request {
  required {
    topology { segments { "topology.ebs.csi.aws.com/zone" = "us-east-1a" } }
    topology { segments { "topology.ebs.csi.aws.com/zone" = "us-east-1b" } }
  }
}

The plugin can create the volume in either us-east-1a or us-east-1b, and the resulting volume object topology will have topology like this:

&CSIVolume{
	Topologies: []*CSITopology{
		{Segments: map[string]string{"topology.ebs.csi.aws.com/zone": "us-east-1a"},
		},
	},
}

So what we're trying to do here is to allow user to update their request to be:

topology_request {
  required {
    topology { segments { "topology.ebs.csi.aws.com/zone" = "us-east-1a" } }
  }
}

Because that request could have been sent and resulted in the same topology.

All that being said, having described all that I'm wondering whether it really makes sense to let users change the topology_request at all if it can't result in a changed topology. This feels like something we'll write in the docs and then folks will open issues about anyways because it's confusing behavior.

Copy link
Member Author

@tgross tgross Mar 4, 2022

Choose a reason for hiding this comment

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

Hm, that behavior is even more confusing because you can't reduce your request to:

topology_request {}

I think I'm going to change this so that once Topologies is set by the plugin, you can't change the TopologyRequest at all. That's going to have less weird edge cases.

Copy link
Member Author

Choose a reason for hiding this comment

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

@lgfa29 I've done that in e6146f6

Copy link
Member

@shoenig shoenig left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

@lgfa29 lgfa29 left a comment

Choose a reason for hiding this comment

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

Nice, thanks for the explanation.

@github-actions
Copy link

I'm going to lock this pull request because it has been closed for 120 days ⏳. This helps our maintainers find and focus on the active contributions.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 26, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CSI: updates on register/create aren't allowed
3 participants