Managing Power Constraints

Managing Power Constraints

Overview

This use case demonstrates how to schedule and manage datacenter power constraints using the NvGrid API. Grid integrators can schedule future power changes, query scheduled targets, and retrieve feed metadata to respond to grid signals.

Goal

Schedule datacenter power constraint changes in response to grid signals such as curtailment events, demand response programs, or dynamic pricing signals.

Persona: Grid Integration Engineer

Role: Developer integrating commercial grid solutions with DPS for datacenter power management.

Objective: Implement grid signal response by scheduling power constraints and monitoring load target achievement.

Use Case Workflow

Scenario: Grid solution receives a curtailment signal and needs to reduce datacenter power consumption

Workflow Steps:

  1. Discover Feed Configuration - Query feed metadata to understand power thresholds and current defaults
  2. Calculate Target Load - Determine appropriate power constraint based on grid signal
  3. Schedule Load Target - Set power constraint with start/end times
  4. Query Schedule - Retrieve current and future scheduled targets
  5. Monitor Status - Track actual power consumption vs target

Theory of Operation

Load Target Scheduling

Load targets define time-based power constraints for datacenter feeds. Each target specifies:

  • Interval: Start and end times for the constraint
  • Load Constraint: Target power limit (watts, kilowatts, or megawatts), or default (zero) to reset
  • Strategy: Algorithm for achieving the target (currently best_effort)
  • Feed Tags: Specific feeds to constrain (or all if omitted)
  • Correlation ID: Unique identifier for tracking

Setting Constraints:

  • Use --value and --unit to set a specific power limit
  • Use --default to reset to the default (zero) constraint
  • Alternatively, --value=0 with any unit also sets the default constraint
  • --default and --value are mutually exclusive

Schedule Conflict Resolution

gantt
    title Load Target Schedule Resolution
    dateFormat HH:mm
    axisFormat %H:%M

    section Default
    10MW Default    :default1, 12:00, 480m

    section Scheduled Targets
    6MW Target      :active, target1, 16:00, 180m
    8MW Target      :crit, target2, 17:00, 60m

    section Effective Power
    10MW (Default)  :done, eff1, 12:00, 240m
    6MW (Target 1)  :done, eff2, 16:00, 60m
    8MW (Target 2)  :done, eff3, 17:00, 60m
    6MW (Target 1)  :done, eff4, 18:00, 60m
    10MW (Default)  :done, eff5, 19:00, 60m

Resolution Rule: For overlapping intervals, the most recently scheduled target takes precedence.

  • Default constraint: 10MW
  • Set 6MW from 4pm-7pm → Active 4pm-7pm
  • Set 8MW from 5pm-6pm → Overrides to 8MW from 5pm-6pm
  • Set 5MW from 6pm-7pm → Reverts to 6MW from 6pm-7pm
  • After 7pm → Reverts to 10MW default

Power Reduction Strategy

The best_effort strategy:

  • Reduces power from DPM-enabled workloads
  • Does not terminate running jobs
  • Target may not be achieved if constraint requires job termination
  • Restores power when constraints are relaxed

Python Client Setup

This section shows one-time setup required for all Python examples below. Complete these steps once before using any Python code examples.

1. Install the DPS Python API

# Navigate to the Python API directory
cd /path/to/dcpower/api/python3

# Install the package
pip3 install -e .

# Verify installation
python3 -c "from dpsapi.v1 import nvgrid_pb2, nvgrid_pb2_grpc; print('Success')"

2. Extract the CA Certificate

# Extract CA certificate from your DPS deployment
kubectl get secret -n <namespace> dps-api-tls -o jsonpath='{.data.ca\.crt}' | base64 -d > /tmp/dps-ca.crt

# Security Note: In production, store certificates in a secure location
# with appropriate file permissions (e.g., 0600) and consider using
# a dedicated certificate directory like /etc/ssl/certs/dps/

# Verify the certificate
ls -lh /tmp/dps-ca.crt

# Example for dps-test namespace:
kubectl get secret -n dps-test dps-api-tls -o jsonpath='{.data.ca\.crt}' | base64 -d > /tmp/dps-ca.crt

3. Setup Authentication Helper

Save this reusable helper for all examples:

# nvgrid_client.py - Reusable authentication helper
import grpc
from dpsapi.v1 import auth_pb2, auth_pb2_grpc

def create_tls_channel(target, authority, ca_cert_path):
    """Create TLS channel with CA certificate"""
    with open(ca_cert_path, 'rb') as f:
        ca_cert = f.read()

    credentials = grpc.ssl_channel_credentials(root_certificates=ca_cert)
    options = [
        ('grpc.default_authority', authority),
        ('grpc.ssl_target_name_override', authority.split(':')[0]),
    ]

    return grpc.secure_channel(target, credentials, options=options)

def get_auth_token(username, password, host='api.dps', port=443, resolve_to=None, ca_cert_path='/tmp/dps-ca.crt'):
    """Login and get access token"""
    target = f'{resolve_to or host}:{port}'
    authority = f'{host}:{port}'

    channel = create_tls_channel(target, authority, ca_cert_path)
    stub = auth_pb2_grpc.AuthServiceStub(channel)

    try:
        req = auth_pb2.TokenRequest(
            password_credential=auth_pb2.PasswordCredential(
                username=username,
                password=password
            )
        )
        resp = stub.Token(req)
        return resp.access_token
    finally:
        channel.close()

def create_nvgrid_client(token, host='api.dps', port=443, resolve_to=None, ca_cert_path='/tmp/dps-ca.crt'):
    """Create authenticated NvGrid client stub"""
    from dpsapi.v1 import nvgrid_pb2_grpc

    target = f'{resolve_to or host}:{port}'
    authority = f'{host}:{port}'

    channel = create_tls_channel(target, authority, ca_cert_path)
    stub = nvgrid_pb2_grpc.NvGridStub(channel)

    # Return both channel and stub so caller can close channel
    return channel, stub

def create_auth_metadata(token):
    """Create gRPC metadata with auth token"""
    return [('authorization', f'Bearer {token}')]

def format_load_value(load_value):
    """Format LoadValue for display"""
    from dpsapi.v1 import nvgrid_pb2

    if not load_value or not load_value.value:
        return "not set"

    unit_names = {
        nvgrid_pb2.LoadValue.UNIT_WATT: 'W',
        nvgrid_pb2.LoadValue.UNIT_KILOWATT: 'kW',
        nvgrid_pb2.LoadValue.UNIT_MEGAWATT: 'MW'
    }
    unit = unit_names.get(load_value.unit, 'W')
    return f"{load_value.value:.2f} {unit}"

Usage in all examples below:

from nvgrid_client import get_auth_token, create_nvgrid_client, create_auth_metadata

# Login once
token = get_auth_token('admin', 'password', host='api.dps-test', resolve_to='127.0.0.1')

# Create client
channel, stub = create_nvgrid_client(token, host='api.dps-test', resolve_to='127.0.0.1')
metadata = create_auth_metadata(token)

# Use stub with metadata in API calls
# ... your code here ...

# Close when done
channel.close()

Step-by-Step Integration Guide

Step 1: Discover Feed Configuration

Query feed metadata to understand available feeds and their power thresholds.

Python Example

#!/usr/bin/env python3
"""
Step 1: Discover feed configuration and power thresholds
"""
from google.protobuf import empty_pb2
from dpsapi.v1 import nvgrid_pb2
from nvgrid_client import get_auth_token, create_nvgrid_client, create_auth_metadata

def discover_feeds(username, password):
    """Discover feed configuration from NvGrid API"""

    # Login
    token = get_auth_token(username, password, host='api.dps', port=443)

    # Create NvGrid client
    channel, stub = create_nvgrid_client(token, host='api.dps', port=443)
    metadata = create_auth_metadata(token)

    try:
        # Get feed metadata
        response = stub.GetMetadata(empty_pb2.Empty(), metadata=metadata)

        print(f"Found {len(response.metadata)} power feed(s):\n")

        feeds = {}
        for feed_tag, entry in response.metadata.items():
            feeds[feed_tag] = {
                'power_minimum': format_load_value(entry.power_minimum),
                'power_maximum': format_load_value(entry.power_maximum),
                'default_constraint': format_load_value(entry.default_constraint)
            }

            print(f"Feed: {feed_tag}")
            print(f"  Minimum: {feeds[feed_tag]['power_minimum']}")
            print(f"  Maximum: {feeds[feed_tag]['power_maximum']}")
            print(f"  Default: {feeds[feed_tag]['default_constraint']}\n")

        return feeds

    finally:
        channel.close()

def format_load_value(load_value):
    """Format LoadValue for display"""
    if not load_value or not load_value.value:
        return "not set"

    unit_names = {
        nvgrid_pb2.LoadValue.UNIT_WATT: 'W',
        nvgrid_pb2.LoadValue.UNIT_KILOWATT: 'kW',
        nvgrid_pb2.LoadValue.UNIT_MEGAWATT: 'MW'
    }
    unit = unit_names.get(load_value.unit, 'W')
    return f"{load_value.value:.2f} {unit}"

# Usage
if __name__ == '__main__':
    # Use environment variables for credentials in production
    # e.g.
    # username = os.getenv('DPS_USERNAME', 'admin')
    # password = os.getenv('DPS_PASSWORD')
    feeds = discover_feeds('admin', 'password')

Step 2: Calculate Target Load

Determine the appropriate power constraint based on the grid signal received.

Python Example

#!/usr/bin/env python3
"""
Step 2: Calculate target load based on grid signal
"""

def calculate_target_load(target_mw, feeds):
    """Calculate target load for all feeds

    Args:
        target_mw: Target load in megawatts (e.g., 5.0 for 5MW)
        feeds: Dict of feed metadata from Step 1

    Returns:
        Dict with target load per feed in watts
    """
    targets = {}

    for feed_tag in feeds.keys():
        target_watts = target_mw * 1_000_000
        targets[feed_tag] = target_watts
        print(f"Feed {feed_tag}: {target_mw:.2f} MW")

    return targets

# Usage
target_mw = 5.0  # Set load target to 5 megawatts
targets = calculate_target_load(target_mw, feeds)

Step 3: Schedule Load Target

Set the power constraint with start and end times.

Python Example

#!/usr/bin/env python3
"""
Step 3: Schedule load target
"""
from datetime import datetime, timedelta
from google.protobuf.timestamp_pb2 import Timestamp
from dpsapi.v1 import nvgrid_pb2
from nvgrid_client import get_auth_token, create_nvgrid_client, create_auth_metadata

def schedule_load_target(stub, metadata, targets, start_time, duration_minutes, correlation_id=None):
    """Schedule load target on NvGrid

    Args:
        stub: NvGrid client stub
        metadata: Auth metadata
        targets: Dict of feed_tag -> target_watts
        start_time: datetime when constraint starts
        duration_minutes: How long constraint is active
        correlation_id: Optional tracking ID
    """
    end_time = start_time + timedelta(minutes=duration_minutes)
    request = nvgrid_pb2.LoadTargetRequest()

    for feed_tag, target_watts in targets.items():
        target = request.targets.add()
        target.interval.start_time.CopyFrom(Timestamp(seconds=int(start_time.timestamp())))
        target.interval.end_time.CopyFrom(Timestamp(seconds=int(end_time.timestamp())))
        target.load_constraint.value = target_watts / 1_000_000  # Convert to MW
        target.load_constraint.unit = nvgrid_pb2.LoadValue.UNIT_MEGAWATT
        target.load_target_strategy.best_effort = True
        target.feed_tags.append(feed_tag)

        if correlation_id:
            target.correlation_id = f"{correlation_id}-{feed_tag}"

        print(f"Feed {feed_tag}: {target_watts/1_000_000:.2f}MW "
              f"({start_time.strftime('%H:%M')} - {end_time.strftime('%H:%M')})")

    response = stub.SetLoadTarget(request, metadata=metadata)

    if response.code == nvgrid_pb2.LoadTargetResponse.STATUS_CODE_SUCCESS:
        print("✅ Load target scheduled successfully")
        for detail in response.details:
            print(f"  Correlation ID: {detail.correlation_id}")
        return response
    else:
        print(f"❌ Failed: {response.diag_msg}")
        return None

# Usage
token = get_auth_token('admin', 'password', host='api.dps')
channel, stub = create_nvgrid_client(token, host='api.dps')
metadata = create_auth_metadata(token)

start_time = datetime.now() + timedelta(minutes=5)
schedule_load_target(stub, metadata, targets, start_time, 60, "curtailment-001")

channel.close()

Step 4: Query Schedule

Retrieve current and future scheduled load targets.

Python Example

#!/usr/bin/env python3
"""
Step 4: Query load schedule
"""
from datetime import datetime, timedelta
from google.protobuf.timestamp_pb2 import Timestamp
from dpsapi.v1 import nvgrid_pb2
from nvgrid_client import get_auth_token, create_nvgrid_client, create_auth_metadata

def get_load_schedule(stub, metadata, start_time, end_time, feed_tags=None):
    """Query load schedule for a time interval"""

    request = nvgrid_pb2.GetLoadScheduleRequest()
    request.interval.start_time.CopyFrom(Timestamp(seconds=int(start_time.timestamp())))
    request.interval.end_time.CopyFrom(Timestamp(seconds=int(end_time.timestamp())))

    if feed_tags:
        request.feed_tags.extend(feed_tags)

    schedule = stub.GetLoadSchedule(request, metadata=metadata)

    print(f"Found {len(schedule.targets)} scheduled targets:")
    for i, target in enumerate(schedule.targets, 1):
        start_dt = datetime.fromtimestamp(target.interval.start_time.seconds)
        end_dt = datetime.fromtimestamp(target.interval.end_time.seconds) if target.interval.HasField('end_time') else None

        print(f"\n{i}. {start_dt.strftime('%H:%M')} - {end_dt.strftime('%H:%M') if end_dt else '∞'}")
        print(f"   Load: {target.load_constraint.value:.2f} MW")
        print(f"   Feeds: {', '.join(target.feed_tags)}")
        print(f"   ID: {target.correlation_id}")

    return schedule

# Usage
token = get_auth_token('admin', 'password', host='api.dps')
channel, stub = create_nvgrid_client(token, host='api.dps')
metadata = create_auth_metadata(token)

start = datetime.now()
end = start + timedelta(hours=4)
schedule = get_load_schedule(stub, metadata, start, end)

channel.close()

Step 5: Monitor Status

Track current power status and target achievement.

Python Example

#!/usr/bin/env python3
"""
Step 5: Monitor current status
"""
from google.protobuf import empty_pb2
from dpsapi.v1 import nvgrid_pb2
from nvgrid_client import get_auth_token, create_nvgrid_client, create_auth_metadata

def get_current_status(stub, metadata):
    """Get current power status and target achievement"""

    # Get detailed status
    status_response = stub.GetStatus(empty_pb2.Empty(), metadata=metadata)

    print(f"Current status for {len(status_response.statuses)} feeds:\n")

    for feed_tag, feed_status in status_response.statuses.items():
        # Check if target is met
        target_watts = load_value_to_watts(feed_status.current_load_target)
        calculated_watts = load_value_to_watts(feed_status.current_calculated_load)
        target_met = calculated_watts <= target_watts if target_watts > 0 else True

        status_str = "ADJUSTING" if feed_status.in_flight else "STABLE"
        status_icon = "✅" if target_met else "⚠️"

        print(f"Feed: {feed_tag} - {status_str}")
        print(f"  Target: {target_watts/1_000_000:.2f} MW")
        print(f"  Actual: {calculated_watts/1_000_000:.2f} MW")
        print(f"  Status: {status_icon} {'Met' if target_met else 'Not Met'}")
        if feed_status.correlation_id:
            print(f"  ID: {feed_status.correlation_id}")
        print()

    return status_response.statuses

def load_value_to_watts(load_value):
    """Convert LoadValue to watts"""
    if not load_value or not load_value.value:
        return 0

    multipliers = {
        nvgrid_pb2.LoadValue.UNIT_MEGAWATT: 1_000_000,
        nvgrid_pb2.LoadValue.UNIT_KILOWATT: 1_000,
        nvgrid_pb2.LoadValue.UNIT_WATT: 1
    }
    return load_value.value * multipliers.get(load_value.unit, 1)

# Usage
token = get_auth_token('admin', 'password', host='api.dps')
channel, stub = create_nvgrid_client(token, host='api.dps')
metadata = create_auth_metadata(token)

status = get_current_status(stub, metadata)

channel.close()

Configuration and Setup

Choose the testing approach that fits your needs:

# Deploy environment and run grid simulation
task sdk                    # Creates cluster with simulated datacenter
task sim:grid END_AFTER=600 # Runs 10-minute grid integration test

Choose Your Simulation Type:

Command What It Tests Use When
task sim:grid Grid integration only Testing grid API and load scheduling in isolation
task sim Grid + resource groups Testing grid under realistic workload churn

Grid-only simulation:

task sim:grid END_AFTER=600  # 10 minutes, default load patterns

Schedules 2-5 concurrent load targets, randomizes patterns across power feeds.

Combined simulation:

task sim END_AFTER=600       # 10 minutes, grid + workload lifecycle

Tests grid integration while resource groups are continuously created and deleted.

Advanced Configuration:

# Extended test with high load range
task sim:grid END_AFTER=3600 MIN_LOAD_PERCENT=70 MAX_LOAD_PERCENT=95

# Frequent schedule changes for rapid testing
task sim:grid INTERVAL=60 MIN_DURATION=120 MAX_DURATION=300

# Combined test with aggressive workload churn
task sim END_AFTER=1800 MAX_RGS=25 MIN_DURATION=60 MAX_DURATION=180

Documentation: Grid SimulationCombined Simulation


Option 2: Manual Testing (Useful for Learning and Debugging)

Step-by-step setup with manual API control:

# 1. Deploy DPS development environment
task dev:up
task dev:deploy

# 2. Verify NvGrid API connection
dpsctl --host api.dps --port 443 --insecure-tls-skip-verify nvgrid get-metadata

# 3. Query current status
dpsctl --host api.dps --port 443 --insecure-tls-skip-verify nvgrid get-status

Manual API Commands:

# Get feed metadata
dpsctl nvgrid get-metadata

# Schedule a single load target (6MW for 1 hour)
dpsctl nvgrid set-load-target \
  --value=6 \
  --unit=megawatt \
  --start-time="2025-10-24T15:00:00Z" \
  --end-time="2025-10-24T16:00:00Z" \
  --best-effort=true

# Reset to default constraint (using --default)
dpsctl nvgrid set-load-target \
  --default \
  --start-time="2025-10-24T18:00:00Z" \
  --end-time="2025-10-24T20:00:00Z"

# Reset to default constraint (using --value=0, equivalent)
dpsctl nvgrid set-load-target \
  --value=0 \
  --unit=watt \
  --start-time="2025-10-24T18:00:00Z" \
  --end-time="2025-10-24T20:00:00Z"

# Query load schedule
dpsctl nvgrid get-schedule \
  --start-time="2025-10-24T14:00:00Z" \
  --end-time="2025-10-24T18:00:00Z"

# Get current load target
dpsctl nvgrid get-current

# Get current status
dpsctl nvgrid get-status

Observable Metrics

NvGrid exposes the following Prometheus metrics:

API Performance

Metric Type Labels Description
grpc_duration histogram (ms) method, status_code API response time
grpc_requests counter method, status_code Total API request count

Power Management

Metric Type Labels Description
nvgrid_system_status_load_target_watts gauge (W) feed_tag, load_unit Current load target
nvgrid_system_status_calculated_load_watts gauge (W) feed_tag, load_unit Actual power consumption
nvgrid_system_status_in_flight gauge feed_tag Power event status (0/1)
nvgrid_feed_schedule_load_target_watts gauge (W) feed_tag, start_time, end_time, load_unit, status Scheduled targets (status: active, scheduled, expired)

Derived Metrics: Compare target vs calculated load for constraint accuracy. Monitor schedule status transitions for adherence tracking.

Event Correlation: Query grid_feed_schedule and nvgrid_system_status database tables for correlation_id fields to track grid signals through the power management pipeline.


This power constraint management integration provides grid solutions with precise control over datacenter power consumption in response to utility grid signals.