Skip to content

[BUG] Account resolved only from the path; Host-header (host-style) addressing returns 501 — blocks Functions AzureWebJobsStorage #267

Description

@cmcconomyfwig

Service

Blob Storage (account resolution / request routing) — impact lands on Azure Functions host AzureWebJobsStorage

Azure API Action

Create Container (PUT /{container}?restype=container), and every blob operation addressed through a host-style endpoint.

Expected behavior

Real Azure addresses storage host-style: the account is in the hostname and the container is the first path segment.

PUT https://devstoreaccount1.blob.core.windows.net/azure-webjobs-hosts?restype=container

AzureRoutingFilter should resolve accountName from the Host header when the host carries one, and only fall back to the first path segment otherwise. That would make both addressing styles work:

Style URL account container
host-style (real Azure) Host: devstoreaccount1.… + /azure-webjobs-hosts devstoreaccount1 azure-webjobs-hosts
path-style (floci today) /devstoreaccount1/azure-webjobs-hosts devstoreaccount1 azure-webjobs-hosts

Actual behavior

The account is resolved only from the first path segment; the Host header is ignored. A host-style request therefore has its container name consumed as the account, leaving no container, and Create Container answers 501 NotImplemented:

$ curl -X PUT 'http://localhost:4577/awjh2?restype=container' \
       -H 'Host: devstoreaccount1.blob.localhost:4577' -H 'x-ms-version: 2024-11-04'
HTTP/1.1 501 Not Implemented
<Error><Code>NotImplemented</Code><Message>The requested operation is not implemented.</Message></Error>

# floci log:
INFO [io.flo.az.cor.AzureRoutingFilter] Resolved accountName: awjh2, serviceType: blob, resourcePath:

accountName: awjh2 — the container name was taken as the account.

Why this blocks Durable/timer Functions (the #136 / #183 scenario)

With the connection string the README documents for path-style access:

DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8…;
BlobEndpoint=http://floci-az:4577/devstoreaccount1;
QueueEndpoint=http://floci-az:4577/devstoreaccount1-queue;
TableEndpoint=http://floci-az:4577/devstoreaccount1-table;

the .NET Azure Functions host (Microsoft.Azure.WebJobs.Host.BlobLeaseDistributedLockManager) emits requests with no container segment at all — the container azure-webjobs-hosts never appears:

PUT  /devstoreaccount1?restype=container                        -> 501   # create azure-webjobs-hosts
HEAD /devstoreaccount1/locks/<hostid>/host                      -> 404
PUT  /devstoreaccount1/locks/<hostid>/host                      -> 404
PUT  /devstoreaccount1/locks/<hostid>/host?comp=lease           -> 404
GET  /devstoreaccount1/?comp=list&maxresults=1                  -> 200   # reachability probe passes

Azure.Storage.Blobs appears to treat a BlobEndpoint that already carries a path as already pointing at a container, so the requested container name is discarded rather than appended. (That inference is from the wire traffic above — I haven't traced the SDK source, so the exact mechanism may differ; the observable behavior is reproducible.) Whatever the cause, the SDK produces host-style-shaped requests that floci cannot route, and there is no connection-string shape that satisfies both sides today:

  • path-style (BlobEndpoint=.../devstoreaccount1) — floci routes it, .NET drops the container name.
  • host-style (BlobEndpoint=http://devstoreaccount1.floci-az:4577) — .NET builds it correctly, floci reads the container as the account → 501.
  • UseDevelopmentStorage=true;DevelopmentStorageProxyUri=… — the emulator mode .NET handles natively, but it requires ports 10000/10001/10002 with per-service paths that floci (single port 4577, -queue/-table path suffixes) doesn't serve.

The 200 on the reachability probe makes this notably hard to diagnose: the host concludes storage is healthy, then fails in the listener-start retry loop with NotImplemented rather than reporting a storage-configuration problem.

Net effect: the Table error-envelope fix (#182) and blob-lease support (#183) both work correctly on 0.12.0 — I verified each directly — but Functions still can't use floci-az as AzureWebJobsStorage, because the lock blobs are never addressed to the right container.

Verification that this is the only remaining blocker

Both 0.12.0 fixes confirmed working against floci/floci-az:0.12.0:

# #182 — Table OData error envelope
POST /devstoreaccount1-table/Tables {"TableName":"probeTbl"}  -> 201
POST (again)                                                  -> 409
  content-type: application/json;odata=minimalmetadata
  {"odata.error":{"code":"TableAlreadyExists","message":{"value":"…","lang":"en-US"}}}

# #183 — blob leases
PUT /devstoreaccount1/probe-locks/lock1?comp=lease (acquire, 15s) -> 201  x-ms-lease-id: aae3afe3-…
PUT /devstoreaccount1/probe-locks/lock1 (no lease id)             -> 412  <Code>LeaseIdMissing</Code>
PUT /devstoreaccount1/probe-locks/lock1 (with lease id)           -> 201
GET /devstoreaccount1/probe-locks/lock1  -> x-ms-lease-state: leased / status: locked

Control: the identical Azure Functions container, same image and same app code, pointed at Azurite via UseDevelopmentStorage=true;DevelopmentStorageProxyUri=http://azurite starts its listener and runs its timer:

Executed 'Functions.azf-timer-run' (Succeeded, Duration=77ms)

Pointed at floci-az, the same host loops on The listener for function 'Functions.azf-timer-run' was unable to start / NotImplemented.

Reproduction

  1. docker run -d -p 4577:4577 floci/floci-az:0.12.0
  2. Run an Azure Functions container (mcr.microsoft.com/azure-functions/python:4-python3.12) holding any timer-triggered function, with AzureWebJobsStorage set to the path-style connection string above.
  3. The listener never starts. Set QUARKUS_HTTP_ACCESS_LOG_ENABLED=true on floci to see PUT /devstoreaccount1?restype=container -> 501.

A timer trigger is sufficient — it takes a WebJobs singleton lease through the same code path Durable uses for partition management, so it reproduces this in seconds without a durable orchestration.

Environment

  • floci-az 0.12.0 (floci/floci-az:0.12.0, native linux/arm64)
  • Azure Functions host image mcr.microsoft.com/azure-functions/python:4-python3.12
  • macOS 15 / Apple Silicon, Docker via colima
  • Reference: Azurite 3.35.0 (working control)

Are you willing to contribute a PR?

  • Yes
  • No

Happy to implement the Host-header account resolution in AzureRoutingFilter (preferring the header, falling back to path segment 1 so every existing path-style caller is unaffected) if that's the direction you'd like — same approach as #182/#183.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions