Skip to content

Add Azure Analysis Services hook, operator, sensor, and trigger - #65879

Open
coleheflin wants to merge 9 commits into
apache:mainfrom
coleheflin:add-azure-analysis-services-provider
Open

coleheflin wants to merge 9 commits into
apache:mainfrom
coleheflin:add-azure-analysis-services-provider

Conversation

@coleheflin

@coleheflin coleheflin commented Apr 26, 2026 •

Copy link
Copy Markdown
Contributor

Adds support for triggering and monitoring Azure Analysis Services model refreshes via the Analysis Services REST API, without requiring Azure Data Factory as an intermediary.

New components

  • AzureAnalysisServicesHook: authenticates with client secret or managed identity and wraps the REST API for triggering and polling refreshes
  • AzureAnalysisServicesRefreshOperator: triggers a model refresh and optionally waits for completion (sync or deferrable)
  • AzureAnalysisServicesSensor: polls an in-progress refresh by refresh ID
  • AzureAnalysisServicesRefreshTrigger: async trigger for deferrable mode

Connection type

A new azure_analysis_services connection type is registered with:

  • host: Region endpoint (e.g. eastus.asazure.windows.net)
  • login: Client ID
  • password: Client Secret
  • tenantId (extra): Azure Tenant ID

Managed identity and workload identity auth are also supported.

closes: #51377


Was generative AI tooling used to co-author this PR?
  • Yes — Claude Sonnet 4.6 (Claude Code)

Generated-by: Claude Sonnet 4.6 (Claude Code) following the guidelines


Important

🛠️ Maintainer triage note for @coleheflin · by @potiuk · 2026-06-18 13:57 UTC

Paused pending your next update — this PR has been inactive for ~38 days, so it's been moved to draft to keep the review queue clear:

  • Rebase on the latest main, address any new failures, and mark it Ready for review when you pick it back up — no rush.
  • See the Pull Request quality criteria.

The ball is in your court — you've been assigned to this PR.

Automated triage — may be imperfect; a maintainer takes the next look.

@coleheflin

Copy link
Copy Markdown
Contributor Author

Manual Testing Completed

The following manual tests were run locally against the Breeze development environment:

Import sanity check ✅
All four new classes import cleanly:

from airflow.providers.microsoft.azure.hooks.analysis_services import AzureAnalysisServicesHook
from airflow.providers.microsoft.azure.operators.analysis_services import AzureAnalysisServicesRefreshOperator
from airflow.providers.microsoft.azure.sensors.analysis_services import AzureAnalysisServicesSensor
from airflow.providers.microsoft.azure.triggers.analysis_services import AzureAnalysisServicesRefreshTrigger
# All imports OK

DAG authoring ✅
A DAG using AzureAnalysisServicesRefreshOperator parses without errors inside Breeze.

Connection type UI ✅
The azure_analysis_services connection type appears in Admin → Connections with the correct custom field labels (Region Endpoint, Client ID, Client Secret, Tenant ID) and hidden fields (Schema, Port, Extra).

Unit tests ✅ (40/40 passing)
All hook, operator, sensor, and trigger tests pass with no live Azure credentials required.

The only remaining test is the end-to-end system test against a live Azure Analysis Services instance, which requires real credentials.

@coleheflin
coleheflin force-pushed the add-azure-analysis-services-provider branch from 4d7193d to e37435c Compare April 29, 2026 03:08
@coleheflin
coleheflin marked this pull request as ready for review April 29, 2026 03:36
@coleheflin

coleheflin commented May 1, 2026 •

Copy link
Copy Markdown
Contributor Author

The remaining CI failure (Static checks / ruff) is unrelated to this PR.

The error is ASYNC110 flagged in providers/edge3/src/airflow/providers/edge3/cli/worker.py:557 — a pre-existing lint issue in the edge3 provider. None of the Azure Analysis Services files introduced here appear in the ruff output.

The previous Compat 2.11.1 failure was also unrelated — all 50 errors were Timeout >60.0s in Amazon provider tests (providers/amazon/tests/unit/...), again nothing touching Azure.


Drafted-by: Claude Code (claude-sonnet-4-6)

@potiuk potiuk added the ready for maintainer review Set after triaging when all criteria pass. label May 11, 2026
@potiuk
potiuk marked this pull request as draft June 18, 2026 13:58
@coleheflin

Copy link
Copy Markdown
Contributor Author

@potiuk Is there anything else I need to test prior to this PR being ready for a review?

I don't have access to an Azure account so I didn't run any integration tests, but I can set one up if that's what is blocking this PR from getting reviewed.

@eladkal

eladkal commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

I don't have access to an Azure account so I didn't run any integration tests, but I can set one up if that's what is blocking this PR from getting reviewed.

Ideally PRs should be tested against a real live evnviroment.
Providing evdance that it does helps with review proccess. If Azure provide free temproary accounts maybe you can use it?

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 5 days if no further activity occurs. Thank you for your contributions.

@github-actions github-actions Bot added the stale Stale PRs per the .github/workflows/stale.yml policy file label Aug 10, 2026
@coleheflin

Copy link
Copy Markdown
Contributor Author

Live end-to-end testing completed

In response to the request for live evidence against a real Azure environment, I provisioned a temporary Azure Analysis Services server (Developer tier) and service principal in a sandbox subscription, deployed a minimal tabular model, and ran the system test DAG (example_azure_analysis_services_refresh) end-to-end.

Result: passed.

  • AzureAnalysisServicesRefreshOperator (sync): authenticated via ClientSecretCredential, triggered a refresh through the REST API, polled status, completed with succeeded.
  • AzureAnalysisServicesRefreshOperator (deferrable=True): triggered a refresh, deferred to the trigger, resumed, completed with succeeded.
  • DagRun finished in success state.
  • pytest --system providers/microsoft/azure/tests/system/microsoft/azure/example_azure_analysis_services.py → 1 passed, 1 warning in 14.95s

The temporary AAS server, database, and app registration used for this test have all been deleted.

cc @eladkal — this should address the live-testing ask above.


Drafted-by: Claude Code (Sonnet 5); reviewed by @coleheflin before posting

@coleheflin
coleheflin force-pushed the add-azure-analysis-services-provider branch from 0c3b712 to 44436e3 Compare August 10, 2026 23:46
@coleheflin
coleheflin marked this pull request as ready for review August 10, 2026 23:46
@github-actions github-actions Bot removed the stale Stale PRs per the .github/workflows/stale.yml policy file label Aug 11, 2026

@potiuk potiuk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry this sat without an answer for so long. Taking your earlier question at face value — here is what it needs, including the exact cause of the red CI.

First, the parts that are right, because the file count makes this look bigger and rougher than it is (most of the +4000 is uv.lock):

  • the poll loop measures elapsed time with time.monotonic(), not time.time(), so a clock adjustment can't break the timeout;
  • deferrable defaults from conf.getboolean("operators", "default_deferrable", fallback=False), matching every other deferrable operator;
  • failures raise a dedicated AzureAnalysisServicesRefreshException rather than a bare AirflowException;
  • the trigger wraps the synchronous hook call in loop.run_in_executor(...) instead of calling requests directly inside run(). That is the mistake most new triggers make, and it degrades the whole triggerer process rather than just one task — good that it isn't here;
  • docs, connection docs and a system test all landed with the code.

The CI failure

Static checks fails on exactly one check — "Checking that conn-fields in provider.yaml match get_connection_form_widgets() of the hook class":

Mismatch between `conn-fields` in providers/microsoft/azure/provider.yaml and
`...AzureAnalysisServicesHook.get_connection_form_widgets()` for connection-type 'azure_analysis_services':
  Fields in get_connection_form_widgets() but NOT in provider.yaml conn-fields:
    managed_identity_client_id, workload_identity_tenant_id

Those two come from the @add_managed_identity_connection_widgets decorator on your get_connection_form_widgets() — it injects them on top of the tenantId you return, so the YAML has to declare all three. Add the two missing keys to conn-fields (the other Azure connection-type entries in the same file show the shape) and that check goes green.

uv.lock was regenerated with the wrong uv version

The lock diff is 4,686 lines, and almost none of it is your dependency. It is platform-marker churn — every entry re-split along sys_platform == 'emscripten' and friends — which happens when uv lock runs with a newer uv than the one the repo pins. This repo is on uv 0.11.29; regenerating with a different version rewrites the file wholesale and will conflict with main continuously.

rm uv.lock && uvx --from uv==0.11.29 uv lock

should bring it back to the handful of lines your change actually needs.

requests calls have no timeout

_start_refresh() and get_refresh_status() both call requests.post / requests.get without timeout=. Python's default is to wait forever, so a connection that stalls never returns. In the synchronous path that holds a worker slot indefinitely; in the deferrable path it pins a thread inside the triggerer that nothing reclaims, and the triggerer is shared by every deferred task on the deployment. Passing an explicit timeout (and surfacing it as a hook argument, if you want it tunable) closes both.

Smaller observations

  • RefreshType and VALID_REFRESH_TYPES spell out the same six values twice, so they can drift apart silently. frozenset(get_args(RefreshType)) derives one from the other.

This review was drafted by an AI-assisted tool and
confirmed by an Airflow maintainer. The findings
below are observations, not blockers; an Airflow
maintainer — a real person — will take the next look at the
PR. If you think a finding is mis-applied, please reply on
the PR and a maintainer will weigh in.

More on how Airflow handles maintainer review:
contributing-docs/05_pull_requests.rst.


Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

uv.lock on main just moved via #73561 ("Allow a Job to be associated with several teams"), commit 02dff07 and this PR currently conflicts.

Quickest fix:

git fetch upstream main && git rebase upstream/main
rm uv.lock && uv lock
git add uv.lock && git rebase --continue
git push --force-with-lease

Automated nudge — ignore if you're not ready to rebase. This comment is updated in place on future uv.lock bumps.

@coleheflin
coleheflin force-pushed the add-azure-analysis-services-provider branch from 44436e3 to ab9b8ff Compare August 13, 2026 15:00
coleheflin and others added 9 commits August 13, 2026 08:18
Adds support for triggering and monitoring Azure Analysis Services model
refreshes via the Analysis Services REST API, without requiring Azure
Data Factory as an intermediary.

New components:
- AzureAnalysisServicesHook: authenticates with client secret or managed
  identity and wraps the REST API for triggering and polling refreshes
- AzureAnalysisServicesRefreshOperator: triggers a model refresh and
  optionally waits for completion (sync or deferrable)
- AzureAnalysisServicesSensor: polls an in-progress refresh by ID
- AzureAnalysisServicesRefreshTrigger: async trigger for deferrable mode

closes: apache#51377
…on type

The get_provider_info.py file is auto-generated from provider.yaml. Running
breeze release-management prepare-provider-documentation --reapply-templates-only
to include the new azure_analysis_services connection type registration.
…s provider

- Add docs/connections/aas.rst with required howto/connection anchor
- Add docs/operators/analysis_services.rst how-to guide referenced in provider.yaml
- Guard settings.Session None check to fix mypy error in system test

Co-Authored-By: Cole Heflin <cole.heflin@astronomer.io>
…ysis Services

Use AzureAnalysisServicesRefreshException (already defined in the hook) instead
of the broad AirflowException in the operator and sensor deferrable callbacks,
satisfying the check-no-new-airflow-exceptions static check.

Co-Authored-By: Cole Heflin <cole.heflin@astronomer.io>
- Fix status values to match REST API: notStarted (was notProcessed),
  add timedOut as terminal failure status
- Remove .lower() on status response that broke camelCase comparisons
- Add RefreshType Literal and runtime validation with clear error message
- Fix trigger serialize() to use dynamic class path
- Fix asyncio.get_event_loop() -> get_running_loop() in trigger
- Add refresh types table and exampleinclude to operator docs
- Fix connection doc link to point to service principal setup page
The hook's get_connection_form_widgets() adds managed_identity_client_id
and workload_identity_tenant_id via the shared managed-identity widget
decorator, but provider.yaml's conn-fields never listed them, so those
fields would be invisible in the connection UI and CI's provider.yaml
validation failed.
requests.post/get in the hook had no timeout, so a stalled connection
would block a worker slot indefinitely (or, in the deferrable path, a
shared triggerer thread that nothing reclaims). Derive
VALID_REFRESH_TYPES from the RefreshType literal instead of duplicating
the six values, so they can't drift apart.

Also regenerate uv.lock with the uv version this repo pins (0.11.29)
instead of a newer local one, which had rewritten the file wholesale
via unrelated platform-marker churn.
@coleheflin
coleheflin force-pushed the add-azure-analysis-services-provider branch from a9ddf67 to 4065785 Compare August 13, 2026 15:19
@coleheflin

coleheflin commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor Author

@potiuk Thanks for the thorough review! I've addressed your feedback:

  • conn-fields mismatch: added managed_identity_client_id and workload_identity_tenant_id to provider.yaml's conn-fields for azure_analysis_services, matching what @add_managed_identity_connection_widgets injects. Static checks is green on this now.
  • uv.lock churn: regenerated with uv==0.11.29 (the pinned version) instead of my newer local one — down from ~4,686 lines of platform-marker noise to 176/172.
  • Missing timeout=: added a REQUEST_TIMEOUT constant and passed it to both requests.post and requests.get in the hook.
  • RefreshType/VALID_REFRESH_TYPES duplication: VALID_REFRESH_TYPES now derives from RefreshType via frozenset(get_args(RefreshType)).

Rebased onto current main as well. The only remaining CI failure (Generate Datamodels for TaskSDK client/AirflowCTL) reproduces identically on a clean main checkout — a uv deprecation that's become a hard error in CI's version, unrelated to this PR.


Drafted-by: Claude Code (Sonnet 5); reviewed by @coleheflin before posting

@coleheflin
coleheflin requested a review from potiuk August 13, 2026 18:29

@aaron-y-chen aaron-y-chen 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.

This PR can be closed since the original issue has been resolved.

This branch has not been deployed

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

Labels

area:providers provider:microsoft-azure Azure-related issues ready for maintainer review Set after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Azure Virtual Analysis Services Operator

4 participants