Skip to content

Add flavor extra_specs#817

Merged
mandre merged 1 commit into
k-orc:mainfrom
chess-knight:flavor-extra-specs
Jun 25, 2026
Merged

Add flavor extra_specs#817
mandre merged 1 commit into
k-orc:mainfrom
chess-knight:flavor-extra-specs

Conversation

@chess-knight

@chess-knight chess-knight commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Add Flavor extra_specs.

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

Comment thread api/v1alpha1/flavor_types.go Outdated
// +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"`

Comment thread api/v1alpha1/volumetype_types.go Outdated
// +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.

@chess-knight

Copy link
Copy Markdown
Contributor 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
Contributor 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.

Comment thread internal/controllers/flavor/actuator.go Outdated
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).

Comment thread api/v1alpha1/common_types.go Outdated
// +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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should we add this validations as part of this PR? or we will be fine with MaxLength:=255 for now

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.

In this PR please. We'll tighten up the validation for the volumetype in a separate PR.

Comment thread api/v1alpha1/flavor_types.go Outdated
// +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"`

Comment thread api/v1alpha1/volumetype_types.go Outdated
// +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.

Comment thread internal/controllers/flavor/actuator.go Outdated
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

yes, if you look into the code create and update are basically the same.

@chess-knight chess-knight requested a review from mandre June 24, 2026 14:14

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

Nice work! just a small nit, then I believe this is ready to merge.

Comment thread internal/controllers/flavor/actuator.go Outdated
resource := obj.Spec.Resource
if resource == nil {
// Should have been caught by API validation
return progress.WrapError(

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 just return nil in this case, this would be more consistent with other reconcile functions:

if resource == nil {
return nil
}

"errors"
"fmt"
"iter"
reflect "reflect"

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 surprised this isn't flagged by the linter.

Suggested change
reflect "reflect"
"reflect"

Comment thread api/v1alpha1/flavor_types.go Outdated

type FlavorExtraSpecStatus struct {
// name is the name of the extraspec
// +kubebuilder:validation:Pattern="^[a-zA-Z0-9-_:. ]+$"

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 don't really care about enforcing a pattern on the status, we'll just return what OpenStack gives us. Not a big deal, though...

Signed-off-by: Roman Hros <roman.hros@dnation.cloud>
@mandre mandre added this pull request to the merge queue Jun 25, 2026
Merged via the queue into k-orc:main with commit 4c6aef7 Jun 25, 2026
10 checks passed
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

4 participants