Skip to content

Add flavor extra_specs#817

Open
chess-knight wants to merge 1 commit into
k-orc:mainfrom
chess-knight:flavor-extra-specs
Open

Add flavor extra_specs#817
chess-knight wants to merge 1 commit into
k-orc:mainfrom
chess-knight:flavor-extra-specs

Conversation

@chess-knight

Copy link
Copy Markdown

Add Flavor extra_specs.

Also, create common type ExtraSpec and use it for the Flavor and VolumeType.

Closes #738

@github-actions github-actions Bot added the semver:major Breaking change label Jun 11, 2026

@eshulman2 eshulman2 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.

Thanks for the PR, generally LGTM except for the missing interface aliases.

return deletes
}

func (actuator flavorActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller generic.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) {

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.

flavorActuator now implements ReconcileResourceActuator via GetResourceReconcilers, but the type alias and compile-time assertion are missing. Compare securitygroup/actuator.go which has:

reconcileResourceActuator = interfaces.ReconcileResourceActuator[orcObjectPT, orcObjectT, osResourceT]
// ...
var _ reconcileResourceActuator = securityGroupActuator{}

The resourceReconciler alias was added, but reconcileResourceActuator wasn't. Please add:

reconcileResourceActuator = generic.ReconcileResourceActuator[orcObjectPT, orcObjectT, osResourceT]
var _ reconcileResourceActuator = flavorActuator{}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

To be fair, this was not a big problem. This compile-time assertions are there to help developers and provide quicker feedback loop to ensure the actuator satisfies the interface. It has no functional use.

}
}

func TestExtraSpecUpdates(t *testing.T) {

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.

Nice to have: would be great to add a test covering the entire logic of the update function eg. what happens if update succeed but delete fails, etc

// +kubebuilder:validation:MaxItems:=128
// +listType=atomic
// +optional
ExtraSpecs []ExtraSpec `json:"extraSpecs,omitempty"`

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.

The []ExtraSpec slice has no uniqueness constraint on name. If a user specifies the same name twice, extraSpecsToMap silently uses the last value - no error or warning at admission time.

A CEL validation rule on the field would catch this at admission time:

// +kubebuilder:validation:XValidation:rule="self.all(x, self.filter(y, y.name == x.name).size() == 1)",message="extraSpecs names must be unique"
ExtraSpecs []ExtraSpec `json:"extraSpecs,omitempty"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thinking about it, we should probably change the the list type to map.

	// extraSpecs is a map of key-value pairs that define extra specifications for the flavor.
	// +kubebuilder:validation:MaxItems:=128
	// +listType=map
	// +listMapKey=name
	// +optional
	ExtraSpecs []ExtraSpec `json:"extraSpecs,omitempty"`

// +listType=atomic
// +optional
ExtraSpecs []VolumeTypeExtraSpec `json:"extraSpecs,omitempty"`
ExtraSpecs []ExtraSpec `json:"extraSpecs,omitempty"`

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.

same as above:

The []ExtraSpec slice has no uniqueness constraint on name. If a user specifies the same name twice, extraSpecsToMap silently uses the last value - no error or warning at admission time.

A CEL validation rule on the field would catch this at admission time:

// +kubebuilder:validation:XValidation:rule="self.all(x, self.filter(y, y.name == x.name).size() == 1)",message="extraSpecs names must be unique"
ExtraSpecs []ExtraSpec `json:"extraSpecs,omitempty"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There too, this should be change to a map list type, but this should be done in a separate PR.

We'll still need to revert the changes and keep the VolumeTypeExtraSpec type.

Signed-off-by: Roman Hros <roman.hros@dnation.cloud>
@chess-knight

Copy link
Copy Markdown
Author

Thanks for the PR, generally LGTM except for the missing interface aliases.

Thank you for the review, I added everything requested

@eshulman2

Copy link
Copy Markdown
Contributor

@mandre can you please approve the workflow, then I'll re-review

@mandre

mandre commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

@chess-knight apologies for the lack of input on your PR, I was quite busy lately. I'll review very soon, stay tuned.

@mandre mandre left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Oof, this was much more work than I originally expected due to the fact this is actually a separate API call. There are a few critical changes we need to make (use a separate FlavorExtraSpecs stuct, and drop the change from CreateResource) before we can merge.

// description contains a free form description of the flavor.
// +kubebuilder:validation:MinLength:=1
// +kubebuilder:validation:MaxLength:=65535
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="description is immutable"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just noting that the description should in theory be mutable (https://docs.openstack.org/api-ref/compute/#update-flavor-description) but this is clearly out of scope for this PR.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, I know about mutable description, but as you said i didn't want to change it with this PR.

if len(resource.ExtraSpecs) > 0 {
extraSpecs := extraSpecsToMap(resource.ExtraSpecs)

_, err = actuator.osClient.CreateFlavorExtraSpecs(ctx, osResource.ID, flavors.ExtraSpecsOpts(extraSpecs))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We can't create the extra spec in CreateResource because this is a different API call that might fail. This should be handled in a separate reconciliation, and we should rely on updateResource instead (or better even, call it reconcileExtraSpecs).

// +kubebuilder:validation:MaxLength:=64
type KeystoneName string

type ExtraSpec struct {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm unsure about making the ExtraSpec a common type. The different projects might enforce different rules.

For instance, nova allows spaces in flavors extraspec keys while cinder does not for volumetypes extraspec keys.

BTW, we should add CEL validations for those.

// +kubebuilder:validation:MaxItems:=128
// +listType=atomic
// +optional
ExtraSpecs []ExtraSpec `json:"extraSpecs,omitempty"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thinking about it, we should probably change the the list type to map.

	// extraSpecs is a map of key-value pairs that define extra specifications for the flavor.
	// +kubebuilder:validation:MaxItems:=128
	// +listType=map
	// +listMapKey=name
	// +optional
	ExtraSpecs []ExtraSpec `json:"extraSpecs,omitempty"`

// +listType=atomic
// +optional
ExtraSpecs []VolumeTypeExtraSpec `json:"extraSpecs,omitempty"`
ExtraSpecs []ExtraSpec `json:"extraSpecs,omitempty"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There too, this should be change to a map list type, but this should be done in a separate PR.

We'll still need to revert the changes and keep the VolumeTypeExtraSpec type.

return progress.WrapError(actuator.osClient.DeleteFlavor(ctx, flavor.ID))
}

func (actuator flavorActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's rename this function to reconcileExtraSpecs, as this is a single concern reconciler.

}

if len(updates) > 0 {
_, err := actuator.osClient.CreateFlavorExtraSpecs(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

OK, it seems (from the gophercloud comment at least) that nova does an upsert.

The nova doc doesn't state it explicitly.

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

Labels

semver:major Breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nova: Flavor extra_specs

3 participants