Skip to content

Portal Example

This example demonstrates how to set up a hub/spoke portal between two Qumulo clusters using Cloud Data Fabric.

Overview

Only one portal relationship can exist between any two clusters. Within that relationship, you can add multiple root directory pairs using qumulo_portal_root.

The setup follows a two-resource flow:

  1. Portal relationship (qumulo_portal) creates the spoke and accepts on the hub
  2. Portal root mapping (qumulo_portal_root) proposes and authorizes a root pair

Architecture

                    SPOKE CLUSTER                          HUB CLUSTER
                    =============                          ===========

Step 1:  qumulo_portal                 Creates spoke + accepts hub

Step 2:  qumulo_portal_root            Proposes + authorizes root pair

Prerequisites

  1. Two Qumulo clusters with REST API access
  2. Network connectivity between clusters on the portal port (default: 3713)
  3. Admin credentials for both clusters

Usage

  1. Create a terraform.tfvars file:

    hub_cluster = {
      endpoint = "https://hub-cluster:8000"
      username = "admin"
      password = "hub-password"
      address  = "hub-cluster.example.com"
    }
    
    spoke_cluster = {
      endpoint = "https://spoke-cluster:8000"
      username = "admin"
      password = "spoke-password"
      address  = "spoke-cluster.example.com"
    }
    
    hub_root   = "/data"
    spoke_root = "/cache"
    portal_type = "PORTAL_READ_ONLY"
    portal_port = 3713
    insecure_skip_verify = true
    

  2. Initialize and apply:

    terraform init -upgrade
    terraform plan
    terraform apply
    

  3. Verify output values:

  4. spoke_state and hub_state should be ACCEPTED
  5. spoke_status and hub_status should be ACTIVE (or DEGRADED if connectivity is limited)

Variables

Name Description Type Default
hub_cluster Hub cluster connection details object -
spoke_cluster Spoke cluster connection details object -
hub_root Filesystem path on the hub containing source data string /data
spoke_root Filesystem path on the spoke where portal data appears string /cache
portal_type Portal access mode (PORTAL_READ_ONLY or PORTAL_READ_WRITE) string PORTAL_READ_ONLY
portal_port TCP port for portal communication number 3713
insecure_skip_verify Skip TLS certificate verification for self-signed certs bool true

Outputs

Name Description
spoke_portal_id ID of the spoke portal
spoke_cluster_uuid UUID of the spoke cluster
spoke_state State of the spoke portal
spoke_status Health status of the spoke portal
hub_portal_id ID of the hub portal
hub_state State of the hub portal
hub_status Health status of the hub portal
authorized_hub_root_id Authorized hub-side root file ID

Multi-Root Setup

To add multiple root directory pairs within the same portal:

resource "qumulo_portal_root" "data" {
  portal_id       = qumulo_portal.main.id
  spoke_root_path = "/cache/data"
  hub_root_path   = "/data"
}

resource "qumulo_portal_root" "exports" {
  portal_id       = qumulo_portal.main.id
  spoke_root_path = "/cache/exports"
  hub_root_path   = "/exports"
}

Troubleshooting

Spoke shows PENDING state

The hub has not accepted the relationship yet. Check qumulo_portal creation diagnostics.

Root exists but data is not accessible

The root pair must be authorized on the hub. Ensure qumulo_portal_root applied successfully and check authorized_hub_root_id.

Connection refused errors

Verify network connectivity between clusters on the portal port (default: 3713, configurable via portal_port).

Full Configuration

# Example: Setting Up a Portal Between Qumulo Clusters
#
# This example demonstrates how to set up a hub/spoke portal relationship
# between two Qumulo clusters using the Cloud Data Fabric.
#
# Flow:
#   1. Create portal relationship (spoke create + hub accept)
#   2. Add portal root mapping (spoke propose + hub authorize)

terraform {
  required_providers {
    qumulo = {
      source  = "qumulo-terraform-registry.s3.us-east-1.amazonaws.com/qumulo/qumulo"
      version = "~> 1.0"
    }
  }
}

variable "hub_cluster" {
  description = "Hub cluster connection details"
  type = object({
    endpoint = string
    username = string
    password = string
    address  = string # Hostname/IP reachable from spoke
  })
  sensitive = true
}

variable "spoke_cluster" {
  description = "Spoke cluster connection details"
  type = object({
    endpoint = string
    username = string
    password = string
    address  = string # Hostname/IP reachable from hub
  })
  sensitive = true
}

variable "hub_root" {
  description = "Filesystem path on the hub cluster containing source data"
  type        = string
  default     = "/data"

  validation {
    condition     = startswith(var.hub_root, "/")
    error_message = "hub_root must be an absolute path (must start with '/')."
  }
}

variable "spoke_root" {
  description = "Filesystem path on the spoke cluster where portal data appears"
  type        = string
  default     = "/cache"

  validation {
    condition     = startswith(var.spoke_root, "/")
    error_message = "spoke_root must be an absolute path (must start with '/')."
  }
}

variable "portal_type" {
  description = "Portal access mode. Use PORTAL_READ_ONLY for cache-only access, PORTAL_READ_WRITE for two-way writes."
  type        = string
  default     = "PORTAL_READ_ONLY"

  validation {
    condition     = contains(["PORTAL_READ_ONLY", "PORTAL_READ_WRITE"], var.portal_type)
    error_message = "portal_type must be PORTAL_READ_ONLY or PORTAL_READ_WRITE."
  }
}

variable "portal_port" {
  description = "TCP port used for portal communication between clusters."
  type        = number
  default     = 3713

  validation {
    condition     = var.portal_port >= 1 && var.portal_port <= 65535
    error_message = "portal_port must be between 1 and 65535."
  }
}

variable "insecure_skip_verify" {
  description = "Set true when clusters use self-signed certificates."
  type        = bool
  default     = true
}

provider "qumulo" {
  connection_profiles = [
    {
      name                 = "hub"
      endpoint             = var.hub_cluster.endpoint
      username             = var.hub_cluster.username
      password             = var.hub_cluster.password
      insecure_skip_verify = var.insecure_skip_verify
    },
    {
      name                 = "spoke"
      endpoint             = var.spoke_cluster.endpoint
      username             = var.spoke_cluster.username
      password             = var.spoke_cluster.password
      insecure_skip_verify = var.insecure_skip_verify
    }
  ]
}

# Step 1: Create the portal relationship.
# One qumulo_portal per hub/spoke cluster pair. Multiple qumulo_portal_root
# resources can share the same portal to replicate different directory trees.
resource "qumulo_portal" "main" {
  spoke_connection_profile = "spoke"
  hub_connection_profile   = "hub"

  type        = var.portal_type
  hub_hosts   = [var.hub_cluster.address]
  hub_port    = var.portal_port
  spoke_hosts = [var.spoke_cluster.address]
  spoke_port  = var.portal_port
}

# Step 2: Add and authorize a root directory pair.
# You can add multiple qumulo_portal_root resources on the same portal to
# replicate different directory trees between the hub and spoke clusters.
resource "qumulo_portal_root" "data" {
  portal_id       = qumulo_portal.main.id
  spoke_root_path = var.spoke_root
  hub_root_path   = var.hub_root
}

# Outputs
output "spoke_portal_id" {
  description = "ID of the spoke portal"
  value       = qumulo_portal.main.spoke_id
}

output "spoke_cluster_uuid" {
  description = "UUID of the spoke cluster"
  value       = qumulo_portal.main.spoke_cluster_uuid
}

output "spoke_state" {
  description = "State of the spoke portal"
  value       = qumulo_portal.main.spoke_state
}

output "spoke_status" {
  description = "Health status of the spoke portal"
  value       = qumulo_portal.main.spoke_status
}

output "hub_portal_id" {
  description = "ID of the hub portal"
  value       = qumulo_portal.main.hub_id
}

output "hub_state" {
  description = "State of the hub portal"
  value       = qumulo_portal.main.hub_state
}

output "hub_status" {
  description = "Health status of the hub portal"
  value       = qumulo_portal.main.hub_status
}

output "authorized_hub_root_id" {
  description = "Hub file ID authorized for spoke access"
  value       = qumulo_portal_root.data.remote_root
}