sangfor-scp

Pythonic REST API client for Sangfor Cloud Platform (SCP).
Single login · Auto pagination · Async task polling · EC2 & Token auth.

PyPI version Python versions MIT License Stable
$ pip install sangfor-scp
View on PyPI GitHub

Features

Single Login

Authenticate once in __init__. Session reused for all calls — no per-request delays.

Auto Pagination

list_all() lazily iterates every page. No manual page tracking needed.

Async Task Polling

tasks.wait() polls until completion, raises on failure or timeout.

Dual Auth

EC2 (AWS4-HMAC-SHA256) signature or Token-based (RSA encrypted) authentication.

Idempotency

Automatic X-Client-Token headers on POST/PUT requests.

Token Refresh

24-hour tokens renewed transparently. Your long-running scripts stay connected.

Quick Start

EC2 Authentication (Recommended)

Python
from sangfor_scp import SCPClient

client = SCPClient(
    host="10.10.10.10",
    access_key="AK5F3B2A1D9E7C4082F1",
    secret_key="sK9mXpL2rN8vQwZdTfUcOb4jYhAeIgBk",
    region="cn-south-1",
    verify_ssl=False,   # SCP uses self-signed certs by default
)

Token Authentication

Python
from sangfor_scp import SCPClient

client = SCPClient(
    host="10.10.10.10",
    username="admin",
    password="your_password",
    verify_ssl=False,
)

Virtual Machines

List all VMs (auto-paginated)

Code
for vm in client.servers.list_all():
    print(vm["id"], vm["name"], vm["status"])

# Filter by resource pool
for vm in client.servers.list_all(az_id="878b7070-6d87-409a-a141-7224fc7bcedd"):
    print(vm["name"])

# Count without fetching all
total = client.servers.count()
print(f"Total VMs: {total}")
Response
// vm object example
{
  "id": "f14b3195-653e-41b0-a2a9-235e38770c1a",
  "name": "web-server-01",
  "status": "running",
  "cores": 4,
  "memory_mb": 8192,
  "az_id": "878b7070-6d87-409a-a141-7224fc7bcedd",
  "host_id": "host-90e2bad3dfa4",
  "disks": [{ "id": "ide0", "size_mb": 51200 }],
  "networks": [{ "ip": "192.168.1.101", "mac": "fa:16:3e:ab:cd:ef" }]
}

Get VM detail

Code
vm = client.servers.get("f14b3195-653e-41b0-a2a9-235e38770c1a")
print(vm["name"], vm["status"])
print(f"CPU: {vm['cores']}  RAM: {vm['memory_mb']}MB")
Response
{
  "id": "f14b3195-653e-41b0-a2a9-235e38770c1a",
  "name": "web-server-01",
  "status": "running",
  "cores": 4,
  "memory_mb": 8192
}

Create a VM and wait for completion

Code
result = client.servers.create(
    az_id="878b7070-6d87-409a-a141-7224fc7bcedd",
    image_id="98a5f5a0-665a-4d66-ba9e-985fc860b7eb",
    storage_tag_id="00000000-0000-0000-0000-000000000000",
    cores=4,
    memory_mb=8192,
    name="web-server-01",
    networks=[{
        "vif_id": "net0",
        "vpc_id": "vpc-3a1f9c2d",
        "subnet_id": "sub-7b4e1a09",
        "connect": 1,
        "model": "virtio",
    }],
    power_on=True,
)

# Wait up to 5 minutes
task = client.tasks.wait(result["task_id"], timeout=300)
vm_id = result["uuids"][0]
print(f"VM created: {vm_id}")
Response
{
  "task_id": "task-a3f91bc2-4d8e-11ef-b123-0050568a1234",
  "uuids": [
    "f14b3195-653e-41b0-a2a9-235e38770c1a"
  ]
}

// After tasks.wait():
{
  "id": "task-a3f91bc2-4d8e-11ef-b123",
  "status": "finish",
  "progress": 100,
  "description": "Create VM success"
}

Power operations

Code
vm_id = "f14b3195-653e-41b0-a2a9-235e38770c1a"

client.servers.power_on(vm_id)
client.servers.power_off(vm_id)
client.servers.power_off(vm_id, force=True)   # hard power off
client.servers.reboot(vm_id)
client.servers.reboot(vm_id, force=True)       # force reboot
client.servers.suspend(vm_id)
client.servers.resume(vm_id)

Resource Pools (AZ)

Code
for az in client.resource_pools.list_all():
    print(az["id"], az["name"])

# Platform-wide overview
ov = client.resource_pools.overview()
print(f"Total VMs : {ov['server']['total']}")
print(f"Running   : {ov['server']['running_count']}")
print(f"Total hosts: {ov['host']['total']}")

# Storage tags in a pool
tags = client.resource_pools.storage_tags(az_id="878b7070-...")
for t in tags:
    print(t["id"], t["name"])
Response
// list_all() item
{ "id": "878b7070-6d87-409a-a141-7224fc7bcedd", "name": "DATACENTER-A" }

// overview()
{
  "server": { "total": 1422, "running_count": 1098 },
  "host":   { "total": 32,   "running_count": 32 }
}

// storage_tags()
[
  { "id": "00000000-0000-0000-0000-000000000000", "name": "High Performance" },
  { "id": "22222222-2222-2222-2222-222222222222", "name": "Capacity Storage" }
]

Images

Code
# All images
for img in client.images.list_all():
    print(img["id"], img["name"])

# Filter by type
for img in client.images.list_iso():
    print(img["name"])

for img in client.images.list_acloud():
    print(img["name"])

# Find by name
img = client.images.find_by_name("Rocky-9.7-x86_64-minimal")
if img:
    print(img["id"])
Response
// image object
{
  "id": "98a5f5a0-665a-4d66-ba9e-985fc860b7eb",
  "name": "Rocky-9.7-x86_64-minimal",
  "disk_format": "ISO",
  "os_type": "linux",
  "size_mb": 1024,
  "status": "active",
  "az_id": "878b7070-6d87-409a-a141-7224fc7bcedd"
}

Volumes (Disks)

Code
# Create a standalone disk
result = client.volumes.create(
    az_id="878b7070-6d87-409a-a141-7224fc7bcedd",
    storage_tag_id="00000000-0000-0000-0000-000000000000",
    size_mb=102400,   # 100 GB
    name="data-disk-01",
)
client.tasks.wait(result["task_id"])
volume_id = result["volume_id"]

# Attach to a VM
task_id = client.servers.attach_volume("vm-uuid", volume_id)
client.tasks.wait(task_id)

# Detach
task_id = client.servers.detach_volume("vm-uuid", volume_id)
client.tasks.wait(task_id)

# List disks attached to a VM
disks = client.volumes.list_attached("vm-uuid")
for d in disks:
    print(d)
Response
// create()
{
  "volume_id": "vol-d4a19f3c-72b1-4e8a",
  "task_id": "task-b7e2ca10-5f91-11ef"
}

// list_all() item
{
  "id": "vol-d4a19f3c-72b1-4e8a",
  "name": "data-disk-01",
  "status": "available",
  "size_mb": 102400,
  "az_id": "878b7070-6d87-409a-a141-7224fc7bcedd"
}

Networks

Code
# List VPCs
for vpc in client.networks.list_vpcs():
    print(vpc["id"], vpc["name"])

# Create VPC
vpc = client.networks.create_vpc(
    az_id="878b7070-...",
    name="prod-vpc",
    cidr="10.0.0.0/16",
)

# Create Subnet
subnet = client.networks.create_subnet(
    vpc_id=vpc["id"],
    az_id="878b7070-...",
    cidr="10.0.1.0/24",
    name="prod-subnet-a",
    gateway_ip="10.0.1.1",
)

# List subnets in a VPC
for s in client.networks.list_subnets(vpc_id=vpc["id"]):
    print(s["cidr"], s["name"])
Response
// VPC object
{
  "id": "vpc-3a1f9c2d-8e74-4b12-a091",
  "name": "prod-vpc",
  "cidr": "10.0.0.0/16",
  "status": "active",
  "az_id": "878b7070-6d87-409a-a141"
}

// Subnet object
{
  "id": "sub-7b4e1a09-3c82-4f51-b207",
  "name": "prod-subnet-a",
  "cidr": "10.0.1.0/24",
  "gateway_ip": "10.0.1.1",
  "vpc_id": "vpc-3a1f9c2d-8e74-4b12"
}

Elastic IPs

Code
# Allocate
eip = client.eips.allocate(
    az_id="878b7070-...",
    bandwidth_mb=100,
    name="web-eip-01",
)
eip_id = eip["id"]

# Bind to a VM
task_id = client.eips.bind(eip_id, server_id="vm-uuid")
client.tasks.wait(task_id)

# Update bandwidth
client.eips.update_bandwidth(eip_id, bandwidth_mb=500)

# Unbind and release
client.eips.unbind(eip_id)
client.eips.release(eip_id)
Response
// allocate()
{
  "id": "eip-c9f3a217-4b8d-11ef-9021",
  "ip": "203.0.113.45",
  "status": "inactive",
  "bandwidth_mb": 100,
  "az_id": "878b7070-6d87-409a-a141"
}

// bind() — returns task_id
"task-f2b9e341-5c1d-11ef-b872-0050568a1234"

Async Tasks

Code
# Simple wait
task = client.tasks.wait("task-uuid", timeout=300)

# With progress callback
def on_progress(task_data):
    pct = task_data.get("progress", 0)
    desc = task_data.get("description", "")
    print(f"  {pct}%  {desc}")

task = client.tasks.wait(
    task_id="task-uuid",
    timeout=600,
    poll_interval=5,
    progress_callback=on_progress,
)

# Non-blocking check
if client.tasks.is_done("task-uuid"):
    print("done!")
Response
// task object
{
  "id": "task-a3f91bc2-4d8e-11ef-b123",
  "status": "finish",
  "progress": 100,
  "description": "Create VM success",
  "created_at": "2025-11-27T10:32:14Z",
  "finished_at": "2025-11-27T10:33:02Z"
}

// Possible statuses:
// waiting → doing → finish
//                 → failure
//                 → canceled

Physical Hosts

Code
# List all hosts
for host in client.system.list_all_hosts():
    print(host["id"], host["name"], host["status"])

# Filter by AZ or status
running = list(client.system.list_all_hosts(
    az_id="878b7070-...",
    status="running",
))
print(f"{len(running)} running hosts")

# Get specific host
host = client.system.get_host("host-90e2bad3dfa4")
print(host["name"], host["status"])

# Host network interfaces
ifaces = client.system.list_host_interfaces("host-90e2bad3dfa4")
for i in ifaces:
    print(i["name"], i["ip"])
Response
// host object
{
  "id": "host-90e2bad3dfa4",
  "name": "10.10.10.101",
  "status": "running",
  "az_id": "878b7070-6d87-409a-a141-7224fc7bcedd",
  "cpu_total": 64,
  "cpu_used": 28,
  "memory_total_mb": 196608,
  "memory_used_mb": 87040
}

// interface object
{
  "name": "bond0",
  "ip": "10.10.10.101",
  "function": "mgmt",
  "speed_mbps": 10000
}

Error Handling

Code
from sangfor_scp import (
    SCPError,          # base exception
    SCPAuthError,      # 401 Unauthorized
    SCPForbiddenError, # 403 Forbidden
    SCPNotFoundError,  # 404 Not Found
    SCPConflictError,  # 409 Conflict
    SCPRateLimitError, # 429 Too Many Requests
    SCPTaskError,      # task ended in failure/canceled
    SCPTimeoutError,   # tasks.wait() timed out
)

try:
    vm = client.servers.get("nonexistent-uuid")
except SCPNotFoundError:
    print("VM not found")

try:
    task = client.tasks.wait("task-uuid", timeout=60)
except SCPTaskError as e:
    print(f"Task failed: {e}")
except SCPTimeoutError as e:
    print(f"Timed out after {e.timeout}s")

API Coverage

ResourceOperations
client.serverslist_all, get, create, delete, power on/off/reboot, attach/detach volume
client.tasksget, wait (with timeout & progress callback), is_done
client.resource_poolslist_all, get, overview, storage_tags
client.tenantslist_all, get, find_by_name, list_by_resource_pool
client.imageslist_all, get, find_by_name, list_iso, list_acloud
client.volumeslist_all, get, create, delete, resize, list_available, list_attached
client.networksVPC: list, create, get, delete · Subnet: list, create, get, delete
client.eipsallocate, bind, unbind, release, update_bandwidth
client.systemversion, platform_info, list_all_hosts, get_host, list_host_interfaces