diff --git a/README.md b/README.md index db9e059..ff119cc 100644 --- a/README.md +++ b/README.md @@ -172,8 +172,9 @@ Time window: `opened_at − 30 min`. On empty result, retries once with a 60-min **Implemented connectors:** - `SSHLogConnector` (`implementations/clusters/onprem/`) — provider-agnostic SSH connector for any on-premise cluster (CDP, HDP, Oracle RAC, MapR, etc.). Log dirs and SSH credentials are constructor params. - `GCPLogConnector` (`implementations/clusters/cloud/gcp/`) — Cloud Logging API with vault-backed service account. +- `AzureLogConnector` (`implementations/clusters/cloud/azure/`) — Azure Monitor Log Analytics workspace, KQL-based queries, `DefaultAzureCredential` auth. Workspace ID resolved from vault at query time. -**Cloud stubs:** Databricks, AWS EMR, Azure Monitor — raise `NotImplementedError`, full implementations planned. +**Cloud stubs:** Databricks, AWS EMR — raise `NotImplementedError`, full implementations planned. ### Agent 3 — Classifier ✅ Implemented @@ -484,9 +485,9 @@ aria/ │ │ ├── onprem/ # SSHLogConnector — any bare-metal/VM cluster (CDP, HDP, Oracle RAC, MapR, etc.) │ │ └── cloud/ │ │ ├── gcp/ # GCPLogConnector — Cloud Logging API +│ │ ├── azure/ # ✅ AzureLogConnector — Log Analytics workspace (Azure Monitor) │ │ ├── databricks/ # stub — planned -│ │ ├── aws/ # stub — planned -│ │ └── azure/ # stub — planned +│ │ └── aws/ # stub — planned │ ├── itsm/ │ │ └── servicenow/ # ServiceNowConnector │ ├── coms/ @@ -508,7 +509,9 @@ aria/ ├── documentation/ # MkDocs site source (mkdocs serve) ├── infra/ │ └── terraform/ -│ └── uc_testing/ # UC1 (Hadoop VMs) · UC2 (Dataproc) · UC3 (GCP native) +│ └── uc_testing/ +│ ├── gcp/ # UC1 (Hadoop VMs) · UC2 (Dataproc) · UC3 (GCP native) +│ └── azure/ # UC1 (Hadoop VMs) · UC2 (HDInsight) · UC3 (Azure native) ├── ml/ # Datasets, few-shot prompt assets, evaluation scripts ├── tests/acceptance/ # ground_truth.json · round results · AC reports ├── Dockerfile # P1.5 S3 — python:3.11-slim, non-root, single stage @@ -627,7 +630,7 @@ Phase 1 is complete when all of the following pass on 10 consecutive test incide | Phase 1.5 | S1: Structured logging — structlog, `run_id`, lifecycle events, RunRecord | ✅ Done | | Phase 1.5 | S2: Monitoring foundation — run store, REST API, Alpine.js dashboard, mode scaffold | ✅ Done | | Phase 1.5 | S3: Docker + `ARIA_CONFIG_PATH` + `VertexAILLMClient` + LLM provider DI (incl. #84 security fix) | ✅ Done | -| Phase 1.5 | S4: Testing infrastructure — UC1/UC2/UC3 cluster wiring, KB runbooks, CMDB validation | 🔄 In progress | +| Phase 1.5 | S4: Testing infrastructure — UC1/UC2/UC3 cluster wiring (GCP + Azure), KB runbooks, AzureLogConnector wired | 🔄 In progress | | Phase 1.5 | S5: Round 2 acceptance testing — 30 incidents on UC1 + UC2 real infrastructure | 🔜 Planned | | Phase 1.5 | S6: GCP native connectors — BQ, Cloud Functions, Pub/Sub, GCS | 🔜 Planned | | Phase 2 | Human validation gate + write-back to ServiceNow | 💡 Planned | diff --git a/api/dependencies.py b/api/dependencies.py index 240035e..ad2df5b 100644 --- a/api/dependencies.py +++ b/api/dependencies.py @@ -21,6 +21,7 @@ from core.interfaces.vault import VaultInterface from core.models import PlatformTag from core.orchestrator.pipeline import ARIAPipeline +from implementations.clusters.cloud.azure.log_connector import AzureLogConnector from implementations.clusters.cloud.gcp.log_connector import GCPLogConnector from implementations.clusters.onprem.log_connector import SSHLogConnector from implementations.itsm.servicenow.connector import ServiceNowConnector @@ -75,7 +76,11 @@ def _get_vault() -> VaultInterface: from implementations.vault.gcp_secret_manager import GCPSecretManagerVault return GCPSecretManagerVault.from_env() - # hashicorp, aws, azure already have implementations — wire them here as they get used. + if backend == "azure": + from implementations.vault.azure_kv import AzureKeyVaultClient + + return AzureKeyVaultClient.from_env() + # hashicorp, aws — implementations exist, wire when needed. return EnvVarVault() @@ -231,9 +236,9 @@ def get_pipeline() -> "ARIAPipeline": def get_agent2() -> LogExtractorAgent: """Build and cache the Agent 2 (Log Extractor) instance. - Registers CDP (SSH) and GCP (Cloud Logging) connectors. Missing credentials - are non-fatal at construction — connectors resolve secrets at query time - and return empty results gracefully if credentials are absent. + Registers CDP (SSH), GCP (Cloud Logging), and Azure (Log Analytics) connectors. + Missing credentials are non-fatal at construction — connectors resolve secrets + at query time and return empty results gracefully if credentials are absent. Injects an LLM client for query planning if ARIA_AGENT2_MODEL is set. """ vault = _get_vault() @@ -254,6 +259,12 @@ def get_agent2() -> LogExtractorAgent: vault, resource_types=["cloud_dataproc_cluster", "cloud_dataproc_job"], ), + # Azure Log Analytics workspace — workspace ID resolved from AZURE_LOG_WORKSPACE_ID + # secret at query time. Covers UC2 (HDInsight) and UC3 (Azure-native) incidents. + PlatformTag.AZURE: AzureLogConnector( + vault, + workspace_id_secret="AZURE_LOG_WORKSPACE_ID", + ), } llm = None model = _resolve_model("2") diff --git a/infra/terraform/uc_testing/azure/.gitkeep b/infra/terraform/uc_testing/azure/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/infra/terraform/uc_testing/azure/uc1-hadoop-onprem/main.tf b/infra/terraform/uc_testing/azure/uc1-hadoop-onprem/main.tf new file mode 100644 index 0000000..ec7de39 --- /dev/null +++ b/infra/terraform/uc_testing/azure/uc1-hadoop-onprem/main.tf @@ -0,0 +1,190 @@ +terraform { + required_version = ">= 1.5" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.0" + } + } +} + +provider "azurerm" { + subscription_id = var.subscription_id + features {} +} + +# ── Resource Group ───────────────────────────────────────────────────────────── +resource "azurerm_resource_group" "uc1" { + name = var.resource_group_name + location = var.location +} + +# ── Virtual Network ──────────────────────────────────────────────────────────── +resource "azurerm_virtual_network" "uc1" { + name = "aria-uc1-vnet" + location = azurerm_resource_group.uc1.location + resource_group_name = azurerm_resource_group.uc1.name + address_space = ["10.10.0.0/16"] +} + +resource "azurerm_subnet" "uc1" { + name = "aria-uc1-subnet" + resource_group_name = azurerm_resource_group.uc1.name + virtual_network_name = azurerm_virtual_network.uc1.name + address_prefixes = ["10.10.0.0/24"] +} + +# ── Network Security Group ───────────────────────────────────────────────────── +resource "azurerm_network_security_group" "uc1" { + name = "aria-uc1-nsg" + location = azurerm_resource_group.uc1.location + resource_group_name = azurerm_resource_group.uc1.name + + # Allow SSH from operator workstation (needed for key validation and log injection) + security_rule { + name = "allow-ssh-from-operator" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "22" + source_address_prefix = var.allowed_ssh_cidr + destination_address_prefix = "*" + } + + # Allow all inbound traffic within the subnet (inter-node Hadoop communication) + security_rule { + name = "allow-internal-subnet" + priority = 200 + direction = "Inbound" + access = "Allow" + protocol = "*" + source_port_range = "*" + destination_port_range = "*" + source_address_prefix = "10.10.0.0/24" + destination_address_prefix = "10.10.0.0/24" + } +} + +resource "azurerm_subnet_network_security_group_association" "uc1" { + subnet_id = azurerm_subnet.uc1.id + network_security_group_id = azurerm_network_security_group.uc1.id +} + +# ── Public IP for master node only ───────────────────────────────────────────── +resource "azurerm_public_ip" "master" { + name = "aria-uc1-master-pip" + location = azurerm_resource_group.uc1.location + resource_group_name = azurerm_resource_group.uc1.name + allocation_method = "Static" + sku = "Standard" +} + +# ── Network Interfaces ───────────────────────────────────────────────────────── +# Azure separates NIC from VM — one NIC per node. +resource "azurerm_network_interface" "nodes" { + for_each = local.nodes + name = "aria-uc1-${each.key}-nic" + location = azurerm_resource_group.uc1.location + resource_group_name = azurerm_resource_group.uc1.name + + ip_configuration { + name = "internal" + subnet_id = azurerm_subnet.uc1.id + private_ip_address_allocation = "Dynamic" + # Only the master node gets a public IP + public_ip_address_id = each.key == "cdp-master-01" ? azurerm_public_ip.master.id : null + } +} + +# ── Node definitions ──────────────────────────────────────────────────────────── +locals { + nodes = { + "cdp-master-01" = { role = "hdfs-namenode,yarn-resourcemanager,hiveserver2", disk_gb = 64 } + "cdp-data-01" = { role = "hdfs-datanode,yarn-nodemanager", disk_gb = 128 } + "cdp-data-02" = { role = "hdfs-datanode,yarn-nodemanager", disk_gb = 128 } + "cdp-utility-01" = { role = "hive-metastore,spark-history,oozie,hue", disk_gb = 64 } + "cdp-bus-01" = { role = "kafka,zookeeper,nifi", disk_gb = 64 } + } + + # cloud-init script mirrors the GCP startup script logic: + # installs Java 11, Hadoop 3.3.6, creates CDP-compatible log directory structure. + cloud_init = <<-CLOUDINIT + #cloud-config + package_update: true + packages: + - openjdk-11-jdk + - python3 + - python3-pip + - wget + - curl + - rsyslog + - openssh-server + + runcmd: + - systemctl enable ssh + - systemctl start ssh + + # Hadoop binaries — for authentic log format + - HADOOP_VERSION=3.3.6 + - wget -q "https://downloads.apache.org/hadoop/common/hadoop-$HADOOP_VERSION/hadoop-$HADOOP_VERSION.tar.gz" -O /tmp/hadoop.tar.gz + - tar -xzf /tmp/hadoop.tar.gz -C /opt/ + - ln -s /opt/hadoop-$HADOOP_VERSION /opt/hadoop + - rm /tmp/hadoop.tar.gz + + # Log directory structure — mirrors real CDP layout exactly + - mkdir -p /var/log/hadoop/hdfs /var/log/hadoop/yarn + - mkdir -p /var/log/hive /var/log/spark /var/log/kafka + - mkdir -p /var/log/zookeeper /var/log/oozie /var/log/nifi + - chmod -R 755 /var/log/hadoop /var/log/hive /var/log/spark + - chmod -R 755 /var/log/kafka /var/log/zookeeper /var/log/oozie /var/log/nifi + + - echo "ARIA UC1 Azure node ready $(hostname) at $(date)" >> /var/log/aria-setup.log + CLOUDINIT +} + +# ── Virtual Machines ─────────────────────────────────────────────────────────── +resource "azurerm_linux_virtual_machine" "nodes" { + for_each = local.nodes + name = each.key + location = azurerm_resource_group.uc1.location + resource_group_name = azurerm_resource_group.uc1.name + size = "Standard_B2ms" # 2 vCPU, 8 GB RAM — equivalent to GCP e2-standard-2 + + admin_username = "aria" + # SSH key auth only — no password + disable_password_authentication = true + + admin_ssh_key { + username = "aria" + public_key = var.aria_ssh_public_key + } + + network_interface_ids = [azurerm_network_interface.nodes[each.key].id] + + os_disk { + name = "aria-uc1-${each.key}-osdisk" + caching = "ReadWrite" + storage_account_type = "StandardSSD_LRS" + disk_size_gb = each.value.disk_gb + } + + source_image_reference { + publisher = "Debian" + offer = "debian-11" + sku = "11" + version = "latest" + } + + # cloud-init runs on first boot — same Hadoop setup as GCP startup script + custom_data = base64encode(local.cloud_init) + + tags = { + aria-role = each.value.role + aria-uc = "uc1" + aria-env = "testing" + } + + depends_on = [azurerm_network_interface.nodes] +} diff --git a/infra/terraform/uc_testing/azure/uc1-hadoop-onprem/outputs.tf b/infra/terraform/uc_testing/azure/uc1-hadoop-onprem/outputs.tf new file mode 100644 index 0000000..6f70a6a --- /dev/null +++ b/infra/terraform/uc_testing/azure/uc1-hadoop-onprem/outputs.tf @@ -0,0 +1,17 @@ +output "master_external_ip" { + description = "Public IP of cdp-master-01 — use for SSH access and ServiceNow CMDB" + value = azurerm_public_ip.master.ip_address +} + +output "node_internal_ips" { + description = "Map of node name to private IP — populate ServiceNow CMDB member CI ip_address fields" + value = { + for name, _ in local.nodes : + name => azurerm_network_interface.nodes[name].private_ip_address + } +} + +output "resource_group_name" { + description = "Resource group containing all UC1 resources — use for az CLI commands and cleanup" + value = azurerm_resource_group.uc1.name +} diff --git a/infra/terraform/uc_testing/azure/uc1-hadoop-onprem/terraform.tfvars.example b/infra/terraform/uc_testing/azure/uc1-hadoop-onprem/terraform.tfvars.example new file mode 100644 index 0000000..1f1646e --- /dev/null +++ b/infra/terraform/uc_testing/azure/uc1-hadoop-onprem/terraform.tfvars.example @@ -0,0 +1,10 @@ +subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # az account show --query id +resource_group_name = "aria-uc1-rg" +location = "West Europe" + +# Your workstation public IP — curl ifconfig.me +allowed_ssh_cidr = "YOUR_IP/32" + +# Generate with: ssh-keygen -t ed25519 -f ~/.ssh/aria_uc1_key -C aria -N "" +# Then: cat ~/.ssh/aria_uc1_key.pub +aria_ssh_public_key = "ssh-ed25519 AAAA... aria" diff --git a/infra/terraform/uc_testing/azure/uc1-hadoop-onprem/variables.tf b/infra/terraform/uc_testing/azure/uc1-hadoop-onprem/variables.tf new file mode 100644 index 0000000..807ca12 --- /dev/null +++ b/infra/terraform/uc_testing/azure/uc1-hadoop-onprem/variables.tf @@ -0,0 +1,26 @@ +variable "subscription_id" { + type = string + description = "Azure subscription ID — get from: az account show --query id" +} + +variable "resource_group_name" { + type = string + default = "aria-uc1-rg" + description = "Name of the Azure resource group to create for UC1" +} + +variable "location" { + type = string + default = "West Europe" + description = "Azure region for all UC1 resources" +} + +variable "allowed_ssh_cidr" { + type = string + description = "Your workstation IP/32 for SSH access — get from: curl ifconfig.me" +} + +variable "aria_ssh_public_key" { + type = string + description = "ED25519 public key for ARIA SSH access — generate with: ssh-keygen -t ed25519 -f ~/.ssh/aria_uc1_key -C aria" +} diff --git a/infra/terraform/uc_testing/azure/uc2-hdinsight/main.tf b/infra/terraform/uc_testing/azure/uc2-hdinsight/main.tf new file mode 100644 index 0000000..9736c22 --- /dev/null +++ b/infra/terraform/uc_testing/azure/uc2-hdinsight/main.tf @@ -0,0 +1,127 @@ +terraform { + required_version = ">= 1.5" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.0" + } + } +} + +provider "azurerm" { + subscription_id = var.subscription_id + features {} +} + +# ── Resource Group ───────────────────────────────────────────────────────────── +resource "azurerm_resource_group" "uc2" { + name = var.resource_group_name + location = var.location +} + +# ── Storage Account (required by HDInsight) ──────────────────────────────────── +resource "azurerm_storage_account" "uc2" { + name = var.storage_account_name + resource_group_name = azurerm_resource_group.uc2.name + location = azurerm_resource_group.uc2.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_container" "hdinsight" { + name = "aria-uc2-hdinsight" + storage_account_name = azurerm_storage_account.uc2.name + container_access_type = "private" +} + +# ── Log Analytics Workspace ──────────────────────────────────────────────────── +# Receives Syslog entries from the HDInsight cluster via Diagnostic Settings. +# AzureLogConnector queries this workspace using AZURE_LOG_WORKSPACE_ID secret. +resource "azurerm_log_analytics_workspace" "aria" { + name = "aria-logs-workspace" + location = azurerm_resource_group.uc2.location + resource_group_name = azurerm_resource_group.uc2.name + sku = "PerGB2018" + retention_in_days = 30 +} + +# Grant the operator/runner identity Log Analytics Reader so AzureLogConnector +# can query from a local development machine (az login credential). +resource "azurerm_role_assignment" "log_reader" { + scope = azurerm_log_analytics_workspace.aria.id + role_definition_name = "Log Analytics Reader" + principal_id = var.aria_runner_object_id +} + +# ── HDInsight Spark Cluster ──────────────────────────────────────────────────── +# Named aria-uc2-cluster to match the GCP Dataproc cluster name in +# the KB and CMDB. AzureLogConnector queries logs by hostname/computer name +# which will match the cluster's head node hostname. +resource "azurerm_hdinsight_spark_cluster" "uc2" { + name = "aria-uc2-cluster" + resource_group_name = azurerm_resource_group.uc2.name + location = azurerm_resource_group.uc2.location + cluster_version = "5.0" + tier = "Standard" + + component_version { + spark = "3.3" + } + + gateway { + username = "ariagw" + password = var.hdinsight_gateway_password + } + + storage_account { + storage_container_id = azurerm_storage_container.hdinsight.id + storage_account_key = azurerm_storage_account.uc2.primary_access_key + is_default = true + } + + roles { + head_node { + vm_size = "Standard_D3_V2" # 4 vCPU, 14 GB RAM + username = "aria" + ssh_keys = [var.aria_ssh_public_key] + } + + worker_node { + vm_size = "Standard_D3_V2" + username = "aria" + ssh_keys = [var.aria_ssh_public_key] + target_instance_count = 2 + } + + zookeeper_node { + vm_size = "Standard_A2_V2" # minimum ZK size + username = "aria" + ssh_keys = [var.aria_ssh_public_key] + } + } + + # Auto-delete after 1 hour idle — cost safety net (mirrors GCP Dataproc lifecycle_config) + # Note: HDInsight does not have native idle-delete; destroy via terraform after smoke test. + + depends_on = [ + azurerm_storage_container.hdinsight, + azurerm_log_analytics_workspace.aria, + ] +} + +# ── Diagnostic Settings — route HDInsight Syslog → Log Analytics ────────────── +# This is what feeds the Syslog table that AzureLogConnector queries. +resource "azurerm_monitor_diagnostic_setting" "hdinsight_logs" { + name = "aria-uc2-diag" + target_resource_id = azurerm_hdinsight_spark_cluster.uc2.id + log_analytics_workspace_id = azurerm_log_analytics_workspace.aria.id + + enabled_log { + category = "GatewayAuditLogs" + } + + metric { + category = "AllMetrics" + enabled = false + } +} diff --git a/infra/terraform/uc_testing/azure/uc2-hdinsight/outputs.tf b/infra/terraform/uc_testing/azure/uc2-hdinsight/outputs.tf new file mode 100644 index 0000000..4c1fd31 --- /dev/null +++ b/infra/terraform/uc_testing/azure/uc2-hdinsight/outputs.tf @@ -0,0 +1,24 @@ +output "log_analytics_workspace_id" { + description = "Log Analytics workspace GUID — store as AZURE_LOG_WORKSPACE_ID in Infisical for AzureLogConnector" + value = azurerm_log_analytics_workspace.aria.workspace_id +} + +output "log_analytics_workspace_resource_id" { + description = "Full ARM resource ID of the workspace — used by UC3 to share the same workspace" + value = azurerm_log_analytics_workspace.aria.id +} + +output "cluster_name" { + description = "HDInsight cluster name — use for ServiceNow CMDB CI and smoke test incident" + value = azurerm_hdinsight_spark_cluster.uc2.name +} + +output "storage_account_name" { + description = "Storage account backing the HDInsight cluster" + value = azurerm_storage_account.uc2.name +} + +output "resource_group_name" { + description = "Resource group — use for az CLI teardown" + value = azurerm_resource_group.uc2.name +} diff --git a/infra/terraform/uc_testing/azure/uc2-hdinsight/terraform.tfvars.example b/infra/terraform/uc_testing/azure/uc2-hdinsight/terraform.tfvars.example new file mode 100644 index 0000000..cadfeed --- /dev/null +++ b/infra/terraform/uc_testing/azure/uc2-hdinsight/terraform.tfvars.example @@ -0,0 +1,13 @@ +subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # az account show --query id +resource_group_name = "aria-uc2-rg" +location = "West Europe" +storage_account_name = "ariauc2logs" # must be globally unique — add a suffix if taken + +# az ad signed-in-user show --query id -o tsv +aria_runner_object_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + +# Reuse the UC1 key or generate a new one +aria_ssh_public_key = "ssh-ed25519 AAAA... aria" + +# Minimum 10 chars, mixed case + digit + special char required by HDInsight +hdinsight_gateway_password = "REPLACE_ME_Str0ng!" diff --git a/infra/terraform/uc_testing/azure/uc2-hdinsight/variables.tf b/infra/terraform/uc_testing/azure/uc2-hdinsight/variables.tf new file mode 100644 index 0000000..1dc9040 --- /dev/null +++ b/infra/terraform/uc_testing/azure/uc2-hdinsight/variables.tf @@ -0,0 +1,38 @@ +variable "subscription_id" { + type = string + description = "Azure subscription ID — get from: az account show --query id" +} + +variable "resource_group_name" { + type = string + default = "aria-uc2-rg" + description = "Name of the Azure resource group for UC2" +} + +variable "location" { + type = string + default = "West Europe" + description = "Azure region for all UC2 resources" +} + +variable "storage_account_name" { + type = string + default = "ariauc2logs" + description = "Storage account name for HDInsight (must be globally unique, 3-24 chars, lowercase)" +} + +variable "aria_runner_object_id" { + type = string + description = "Object ID of the identity running ARIA locally — grants Log Analytics Reader. Get from: az ad signed-in-user show --query id -o tsv" +} + +variable "aria_ssh_public_key" { + type = string + description = "ED25519 public key for SSH access to cluster nodes — can reuse the UC1 key" +} + +variable "hdinsight_gateway_password" { + type = string + sensitive = true + description = "Password for the HDInsight Ambari gateway UI (must be 10+ chars with mixed case, digit, and special char)" +} diff --git a/infra/terraform/uc_testing/azure/uc3-azure-native/main.tf b/infra/terraform/uc_testing/azure/uc3-azure-native/main.tf new file mode 100644 index 0000000..37b319b --- /dev/null +++ b/infra/terraform/uc_testing/azure/uc3-azure-native/main.tf @@ -0,0 +1,106 @@ +terraform { + required_version = ">= 1.5" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.0" + } + } +} + +provider "azurerm" { + subscription_id = var.subscription_id + features {} +} + +# ── Resource Group ───────────────────────────────────────────────────────────── +resource "azurerm_resource_group" "uc3" { + name = var.resource_group_name + location = var.location +} + +# ── Storage Account (→ GCS equivalent) ──────────────────────────────────────── +resource "azurerm_storage_account" "uc3" { + name = var.storage_account_name + resource_group_name = azurerm_resource_group.uc3.name + location = azurerm_resource_group.uc3.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +# GCS folder equivalents for simulated service types +resource "azurerm_storage_container" "log_buckets" { + for_each = toset(["gcp-pubsub-sim", "gcp-bigquery-sim", "gcp-dataflow-sim", "gcp-cloudrun-sim"]) + name = each.value + storage_account_name = azurerm_storage_account.uc3.name +} + +# ── Event Hubs (→ Pub/Sub equivalent) ───────────────────────────────────────── +resource "azurerm_eventhub_namespace" "uc3" { + name = "aria-uc3-events" + location = azurerm_resource_group.uc3.location + resource_group_name = azurerm_resource_group.uc3.name + sku = "Basic" + capacity = 1 +} + +resource "azurerm_eventhub" "uc3" { + name = "aria-uc3-topic" + namespace_name = azurerm_eventhub_namespace.uc3.name + resource_group_name = azurerm_resource_group.uc3.name + partition_count = 2 + message_retention = 1 +} + +# ── Synapse Analytics workspace (→ BigQuery equivalent) ─────────────────────── +resource "azurerm_synapse_workspace" "uc3" { + name = "aria-uc3-synapse" + resource_group_name = azurerm_resource_group.uc3.name + location = azurerm_resource_group.uc3.location + storage_data_lake_gen2_filesystem_id = azurerm_storage_data_lake_gen2_filesystem.uc3.id + sql_administrator_login = "ariaadmin" + sql_administrator_login_password = var.synapse_sql_password + + identity { + type = "SystemAssigned" + } +} + +resource "azurerm_storage_data_lake_gen2_filesystem" "uc3" { + name = "aria-uc3-datalake" + storage_account_id = azurerm_storage_account.uc3.id +} + +# ── Diagnostic Settings → shared Log Analytics workspace ───────────────────── +# Routes Event Hubs operational logs to the workspace created by UC2. +# No application-level log data is seeded — UC3 is designed to return empty +# log evidence, driving confidence_band=LOW in the classifier. +resource "azurerm_monitor_diagnostic_setting" "eventhub_logs" { + name = "aria-uc3-eventhub-diag" + target_resource_id = azurerm_eventhub_namespace.uc3.id + log_analytics_workspace_id = var.log_analytics_workspace_resource_id + + enabled_log { + category = "OperationalLogs" + } + + metric { + category = "AllMetrics" + enabled = false + } +} + +resource "azurerm_monitor_diagnostic_setting" "synapse_logs" { + name = "aria-uc3-synapse-diag" + target_resource_id = azurerm_synapse_workspace.uc3.id + log_analytics_workspace_id = var.log_analytics_workspace_resource_id + + enabled_log { + category = "SynapseRbacOperations" + } + + metric { + category = "AllMetrics" + enabled = false + } +} diff --git a/infra/terraform/uc_testing/azure/uc3-azure-native/outputs.tf b/infra/terraform/uc_testing/azure/uc3-azure-native/outputs.tf new file mode 100644 index 0000000..5637785 --- /dev/null +++ b/infra/terraform/uc_testing/azure/uc3-azure-native/outputs.tf @@ -0,0 +1,14 @@ +output "eventhub_namespace_name" { + description = "Event Hubs namespace name — use as ServiceNow CMDB CI name for UC3 smoke incident" + value = azurerm_eventhub_namespace.uc3.name +} + +output "synapse_workspace_name" { + description = "Synapse Analytics workspace name" + value = azurerm_synapse_workspace.uc3.name +} + +output "resource_group_name" { + description = "Resource group — use for az CLI teardown" + value = azurerm_resource_group.uc3.name +} diff --git a/infra/terraform/uc_testing/azure/uc3-azure-native/terraform.tfvars.example b/infra/terraform/uc_testing/azure/uc3-azure-native/terraform.tfvars.example new file mode 100644 index 0000000..2921252 --- /dev/null +++ b/infra/terraform/uc_testing/azure/uc3-azure-native/terraform.tfvars.example @@ -0,0 +1,11 @@ +subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # az account show --query id +resource_group_name = "aria-uc3-rg" +location = "West Europe" +storage_account_name = "ariauc3native" # must be globally unique — add a suffix if taken + +# Full ARM resource ID from UC2 output: +# cd ../uc2-hdinsight && terraform output log_analytics_workspace_resource_id +log_analytics_workspace_resource_id = "/subscriptions/xxxx.../resourceGroups/aria-uc2-rg/providers/Microsoft.OperationalInsights/workspaces/aria-logs-workspace" + +# 8+ chars, mixed case + digit + special char +synapse_sql_password = "REPLACE_ME_Str0ng!" diff --git a/infra/terraform/uc_testing/azure/uc3-azure-native/variables.tf b/infra/terraform/uc_testing/azure/uc3-azure-native/variables.tf new file mode 100644 index 0000000..d04b66c --- /dev/null +++ b/infra/terraform/uc_testing/azure/uc3-azure-native/variables.tf @@ -0,0 +1,33 @@ +variable "subscription_id" { + type = string + description = "Azure subscription ID — get from: az account show --query id" +} + +variable "resource_group_name" { + type = string + default = "aria-uc3-rg" + description = "Name of the Azure resource group for UC3" +} + +variable "location" { + type = string + default = "West Europe" + description = "Azure region for all UC3 resources" +} + +variable "storage_account_name" { + type = string + default = "ariauc3native" + description = "Storage account name (must be globally unique, 3-24 chars, lowercase alphanumeric)" +} + +variable "log_analytics_workspace_resource_id" { + type = string + description = "Full ARM resource ID of the Log Analytics workspace created in UC2. Get from: cd ../uc2-hdinsight && terraform output log_analytics_workspace_resource_id" +} + +variable "synapse_sql_password" { + type = string + sensitive = true + description = "SQL admin password for the Synapse workspace (8+ chars, mixed case, digit, special char)" +} diff --git a/infra/terraform/uc_testing/shared/modules/secrets/main.tf b/infra/terraform/uc_testing/gcp/shared/modules/secrets/main.tf similarity index 100% rename from infra/terraform/uc_testing/shared/modules/secrets/main.tf rename to infra/terraform/uc_testing/gcp/shared/modules/secrets/main.tf diff --git a/infra/terraform/uc_testing/shared/modules/serviceaccount/main.tf b/infra/terraform/uc_testing/gcp/shared/modules/serviceaccount/main.tf similarity index 100% rename from infra/terraform/uc_testing/shared/modules/serviceaccount/main.tf rename to infra/terraform/uc_testing/gcp/shared/modules/serviceaccount/main.tf diff --git a/infra/terraform/uc_testing/shared/modules/vpc/main.tf b/infra/terraform/uc_testing/gcp/shared/modules/vpc/main.tf similarity index 100% rename from infra/terraform/uc_testing/shared/modules/vpc/main.tf rename to infra/terraform/uc_testing/gcp/shared/modules/vpc/main.tf diff --git a/infra/terraform/uc_testing/uc1-hadoop-onprem/main.tf b/infra/terraform/uc_testing/gcp/uc1-hadoop-onprem/main.tf similarity index 100% rename from infra/terraform/uc_testing/uc1-hadoop-onprem/main.tf rename to infra/terraform/uc_testing/gcp/uc1-hadoop-onprem/main.tf diff --git a/infra/terraform/uc_testing/uc1-hadoop-onprem/outputs.tf b/infra/terraform/uc_testing/gcp/uc1-hadoop-onprem/outputs.tf similarity index 100% rename from infra/terraform/uc_testing/uc1-hadoop-onprem/outputs.tf rename to infra/terraform/uc_testing/gcp/uc1-hadoop-onprem/outputs.tf diff --git a/infra/terraform/uc_testing/uc1-hadoop-onprem/terraform.tfvars.example b/infra/terraform/uc_testing/gcp/uc1-hadoop-onprem/terraform.tfvars.example similarity index 100% rename from infra/terraform/uc_testing/uc1-hadoop-onprem/terraform.tfvars.example rename to infra/terraform/uc_testing/gcp/uc1-hadoop-onprem/terraform.tfvars.example diff --git a/infra/terraform/uc_testing/uc1-hadoop-onprem/variables.tf b/infra/terraform/uc_testing/gcp/uc1-hadoop-onprem/variables.tf similarity index 100% rename from infra/terraform/uc_testing/uc1-hadoop-onprem/variables.tf rename to infra/terraform/uc_testing/gcp/uc1-hadoop-onprem/variables.tf diff --git a/infra/terraform/uc_testing/uc2-dataproc/main.tf b/infra/terraform/uc_testing/gcp/uc2-dataproc/main.tf similarity index 100% rename from infra/terraform/uc_testing/uc2-dataproc/main.tf rename to infra/terraform/uc_testing/gcp/uc2-dataproc/main.tf diff --git a/infra/terraform/uc_testing/uc2-dataproc/outputs.tf b/infra/terraform/uc_testing/gcp/uc2-dataproc/outputs.tf similarity index 100% rename from infra/terraform/uc_testing/uc2-dataproc/outputs.tf rename to infra/terraform/uc_testing/gcp/uc2-dataproc/outputs.tf diff --git a/infra/terraform/uc_testing/uc2-dataproc/terraform.tfvars.example b/infra/terraform/uc_testing/gcp/uc2-dataproc/terraform.tfvars.example similarity index 100% rename from infra/terraform/uc_testing/uc2-dataproc/terraform.tfvars.example rename to infra/terraform/uc_testing/gcp/uc2-dataproc/terraform.tfvars.example diff --git a/infra/terraform/uc_testing/uc2-dataproc/variables.tf b/infra/terraform/uc_testing/gcp/uc2-dataproc/variables.tf similarity index 100% rename from infra/terraform/uc_testing/uc2-dataproc/variables.tf rename to infra/terraform/uc_testing/gcp/uc2-dataproc/variables.tf diff --git a/infra/terraform/uc_testing/uc3-gcp-native/main.tf b/infra/terraform/uc_testing/gcp/uc3-gcp-native/main.tf similarity index 100% rename from infra/terraform/uc_testing/uc3-gcp-native/main.tf rename to infra/terraform/uc_testing/gcp/uc3-gcp-native/main.tf diff --git a/infra/terraform/uc_testing/uc3-gcp-native/outputs.tf b/infra/terraform/uc_testing/gcp/uc3-gcp-native/outputs.tf similarity index 100% rename from infra/terraform/uc_testing/uc3-gcp-native/outputs.tf rename to infra/terraform/uc_testing/gcp/uc3-gcp-native/outputs.tf diff --git a/infra/terraform/uc_testing/uc3-gcp-native/variables.tf b/infra/terraform/uc_testing/gcp/uc3-gcp-native/variables.tf similarity index 100% rename from infra/terraform/uc_testing/uc3-gcp-native/variables.tf rename to infra/terraform/uc_testing/gcp/uc3-gcp-native/variables.tf