Skip to content

Conversation

@hoffmaen
Copy link
Contributor

@hoffmaen hoffmaen commented Dec 9, 2025

This Pull-Request adds support for hash-based routing to Cloud Controller.

Summary:
The routes model is enhanced as follows:

  • hash is added as a valid loadbalancing option
  • hash_header is added as a per-route option. The option is mandatory when loadbalancing=hash
  • hash_balance is added as a per-route option. The option is optional when loadbalancing=hash

Validation of these options is added when creating and updating both via API and via manifest. When hash_balance is provided as an option, its value must be a float or a string representing a float.

The options are still stored as a raw JSON string in the routes table. The value for hash_balance is always stored as a string inside the JSON for consistency.

Logic is introduced to remove hash_balance and hash_header when switching from hash to another load-balancing algorithm. Incremental updates on per-route options are allowed, e.g. only updating hash_header or hash_balance while keeping other previously set per-route options is possible.

Links:

@hoffmaen hoffmaen force-pushed the hash-based-routing branch 2 times, most recently from f181a31 to 6ae8cb9 Compare December 9, 2025 09:33
Copy link
Member

@philippthun philippthun left a comment

Choose a reason for hiding this comment

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

Did not look at the spec files so far...

Route.db.transaction do
route.options = route.options.symbolize_keys.merge(message.options).compact if message.requested?(:options)
if message.requested?(:options)
merged_options = message.options.compact
Copy link
Member

Choose a reason for hiding this comment

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

Isn't the merge missing, i.e. route.options.merge(message.options)?


def update
message = RouteUpdateMessage.new(hashed_params[:body])
params = hashed_params[:body]
Copy link
Member

Choose a reason for hiding this comment

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

The controller should not manipulate the params and the message should reflect what has been provided by the user. Isn't this redundant with RouteUpdate.update anyway?

elsif manifest_route[:options] && route[:options] != manifest_route[:options]
# remove nil values from options
manifest_route[:options] = manifest_route[:options].compact
# Merge existing route options with manifest options for partial updates
Copy link
Member

Choose a reason for hiding this comment

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

I would move the merge to RouteUpdate.update to have it in a single place.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On route updates, we need to validate after merging, as we need to allow incremental route option updates. E.g., only setting hash_balance would be invalid for a new route, but is valid for a route that is already properly setup with hash-based routing. RouteUpdate.update is the last step in this chain, so we would also need to migrate the validation logic to RouteUpdate. I think the OptionsValidator is the better place to have the validation, and thus also the merging. Would you agree?

Comment on lines +123 to +125
return if options[:hash_balance].blank?

errors.add(:base, message: "Route '#{route[:route]}': hash_balance can only be set when loadbalancing is hash")
Copy link
Member

Choose a reason for hiding this comment

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

Why not errors.add(...) if options[:hash_balance].present? as above?

Comment on lines +7 to +8
VALID_LOADBALANCING_ALGORITHMS_WITH_HASH = %w[round-robin least-connection hash].freeze
VALID_LOADBALANCING_ALGORITHMS_WITHOUT_HASH = %w[round-robin least-connection].freeze
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to define VALID_LOADBALANCING_ALGORITHMS_WITH_HASH as VALID_LOADBALANCING_ALGORITHMS_WITHOUT_HASH + 'hash'?

Comment on lines +228 to +233
if hash_balance_value.present?
# Always convert to string for consistent storage in JSON
normalized[hash_balance_key] = hash_balance_value.to_s
end

normalized
Copy link
Member

Choose a reason for hiding this comment

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

Suggestion:

return opts if hash_balance_value.blank?

normalized = opts.dup
normalized[hash_balance_key] = hash_balance_value.to_s

normalized

if route_mapping.route.options
opts = route_mapping.route.options

route_hash[:options] = {}
Copy link
Member

Choose a reason for hiding this comment

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

Why not route_hash[:options] = route_mapping.route.options?

Comment on lines +66 to +68
return if hash_balance.blank?

errors.add(:hash_balance, 'can only be set when loadbalancing is hash')
Copy link
Member

Choose a reason for hiding this comment

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

I would prefer errors.add(...) if hash_balance.present?.

end

def validate_hash_balance_format
return if hash_balance.nil?
Copy link
Member

Choose a reason for hiding this comment

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

Should we use blank? instead of nil? as in other places?

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