Skip to content

[Fixes #14381] Relax Remote Service URL uniqueness#14404

Open
etj wants to merge 11 commits into
masterfrom
14381_url_unique
Open

[Fixes #14381] Relax Remote Service URL uniqueness#14404
etj wants to merge 11 commits into
masterfrom
14381_url_unique

Conversation

@etj

@etj etj commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

This PR changes the identity model of remote services from endpoint URL uniqueness to service record uniqueness.

Before:

  • A remote endpoint (base_url) implicitly identified one service.
  • Duplicate endpoint registrations were rejected/assumed invalid.
  • Some lookups and cache entries could collide when the same URL was reused.

After:

  • base_url is no longer a unique identifier.
  • Multiple Service objects can intentionally reference the same endpoint (e.g., different owners/use-cases/credentials).
  • Service handler caching is keyed by service context (including auth fingerprint), so same-URL services with different credentials are isolated.
  • Map layer dataset resolution is stricter: ambiguous URL-based fallback no longer fails silently.
  • Registering the same remote endpoint multiple times no longer risks name collisions in harvester/service records; naming is now uniqueness-safe across both models.

What this PR changes

  • Removes the uniqueness constraint from Service.base_url.
  • Allows registering multiple Service records pointing to the same remote endpoint.
  • Updates service registration/lookup logic and related tests to support duplicate base_url values.
  • Adds a DB migration to alter the Service.base_url field.
  • Makes service-handler cache keys credential-aware to avoid collisions across different auth contexts.
  • Makes geoserver_pre_save_maplayer fallback strict on ambiguous dataset matches (logs + raises on MultipleObjectsReturned).
  • Extracts service-handler caching into a ServiceHandlerCache class (serviceprocessors/cache.py), mirroring the existing ServiceTypeRegistry pattern, instead of loose module-level functions and a duplicated cache handle in views.py.
  • Fixes a pre-existing bug in GeoNodeServiceHandler.parsed_service (predates this PR) that read auth/type/id off a variable that is actually just the raw service= query-string value, not a Service model -- this would raise AttributeError the moment the property ran (e.g. registering or probing a GeoNode-type remote service). Auth now resolves the same way WmsServiceHandler.parsed_service does.

Solved Note: this PR may conflict with #14328

Checklist

For all pull requests:

  • Confirm you have read the contribution guidelines
  • You have sent a Contribution Licence Agreement (CLA) as necessary (not required for small changes, e.g., fixing typos in the documentation)
  • Make sure the first PR targets the master branch, eventual backports will be managed later. This can be ignored if the PR is fixing an issue that only happens in a specific branch, but not in newer ones.

The following are required only for core and extension modules (they are welcomed, but not required, for contrib modules):

  • There is a ticket in https://github.com/GeoNode/geonode/issues describing the issue/improvement/feature (a notable exemption is, changes not visible to end-users)
  • The issue connected to the PR must have Labels and Milestone assigned
  • PR for bug fixes and small new features are presented as a single commit
  • PR title must be in the form "[Fixes #<issue_number>] Title of the PR"
  • New unit tests have been added covering the changes, unless there is an explanation on why the tests are not necessary/implemented

Submitting the PR does not require you to check all items, but by the time it gets merged, they should be either satisfied or inapplicable.

@etj etj self-assigned this Jul 2, 2026
Copilot AI review requested due to automatic review settings July 2, 2026 14:41
@etj etj added the master label Jul 2, 2026
@etj etj added this to the 5.2.0 milestone Jul 2, 2026
Comment thread geonode/services/serviceprocessors/__init__.py Fixed
gemini-code-assist[bot]

This comment was marked as outdated.

This comment was marked as outdated.

@cla-bot cla-bot Bot added the cla-signed CLA Bot: community license agreement signed label Jul 2, 2026
@etj etj changed the title [Fixes #1438] Relax Remote Service URL uniqueness [Fixes #14381] Relax Remote Service URL uniqueness Jul 3, 2026
@etj etj linked an issue Jul 3, 2026 that may be closed by this pull request
@etj

etj commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Waiting for #14328 to be merged.

etj added 7 commits July 6, 2026 17:56
Resolves conflicts from PR #14328 (generalize remote service type
registration via ServiceTypeRegistry) against the #14381 work
(relax Service.base_url uniqueness).
…andler

Leftover from the earlier manual "Align to master" commit; harmless
but redundant.
_get_service_handler (views.py) and GeoNodeServiceHandler.parsed_service
(wms.py) build the request auth via get_request_auth(), which returns a
HashableAuthBase-wrapped requests.auth object, but only passed auth= to
get_service_handler, not auth_config=. Since HashableAuthBase exposes no
`.username` attribute, _build_auth_cache_fingerprint fell through to a
fingerprint that was constant for every HashableAuthBase instance
("auth:HashableAuthBase"), so changing a service's stored credentials
would not bust the cached service handler until it naturally expired.

Fix both call sites to also pass auth_config=service.auth_config,
consistent with every other get_service_handler/get_service_cache_key
call site. Also harden _build_auth_cache_fingerprint itself to unwrap
HashableAuthBase and fingerprint the full underlying credential dict,
so the auth= path is correct on its own rather than relying solely on
callers always supplying auth_config too.
parsed_service calls self.ows_endpoint() (a real HTTP request) before
get_cleaned_url_params, which wasn't mocked, causing a real DNS lookup
against the phony test hostname. Mock ows_endpoint too.

Copilot AI 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.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

geonode/services/serviceprocessors/init.py:61

  • logger.exception(msg=...) is an invalid call signature for the stdlib logger (the message must be the first positional argument). In the exception handler this will raise a TypeError and can mask the original parsing error. Log the message positionally (optionally with %s formatting) and keep the original exception context.
    except Exception as e:
        logger.exception(e)
        logger.exception(msg=f"Could not parse service {base_url}")
        raise

Comment thread geonode/services/serviceprocessors/cache.py
Comment thread geonode/services/views.py
Comment thread geonode/services/serviceprocessors/base.py Outdated

Copilot AI 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.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Comment thread geonode/services/serviceprocessors/cache.py
Comment thread geonode/services/serviceprocessors/cache.py Outdated
Comment thread geonode/services/serviceprocessors/base.py
Comment thread geonode/services/serviceprocessors/cache.py Outdated
@etj etj requested a review from Copilot July 8, 2026 11:29

Copilot AI 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.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Comment thread geonode/utils.py
Comment on lines 1418 to 1424
from geonode.services.serviceprocessors import get_service_handler

handler = get_service_handler(
instance.remote_service.service_url, service_type=instance.remote_service.type
instance.remote_service.service_url,
service_type=instance.remote_service.type,
service_id=instance.remote_service.id,
)
Comment on lines +43 to +57
cache_key = get_service_cache_key(
base_url,
service_type=service_type,
service_id=service_id,
auth=kwargs.get("auth"),
auth_config=kwargs.get("auth_config"),
)

if entry := service_handler_cache.get(cache_key):
return entry

handler = service_type_registry.get_handler_class(service_type)
handler = service_type_registry.get_handler_class(service_type)
try:
service_handler = handler(base_url, service_id, *args, **kwargs)
service_cache.set(service_handler.url, service_handler, settings.SERVICE_CACHE_EXPIRATION_TIME)
service_handler_cache.set(cache_key, service_handler)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed CLA Bot: community license agreement signed master

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Relax Remote Service URL uniqueness

3 participants