Skip to main content

sangfor-scp

PyPI version Python License: MIT

📖 Live Examples & API Docs →

Sangfor Cloud Platform (SCP) Open-API Python Client Library

A clean, Pythonic wrapper for the Sangfor Cloud Platform REST API. Supports EC2 (AWS4-HMAC-SHA256) and Token-based authentication with a single login on initialization — no repeated auth delays between calls.


Features

  • Single login — authenticate once in __init__, session reused for all requests
  • Auto token refresh — 24-hour tokens renewed transparently
  • Automatic pagination — list_all() iterates every page lazily; no manual page tracking needed
  • Async task support — tasks.wait() polls until completion, raises on failure or timeout
  • Idempotency — automatic X-Client-Token headers on POST/PUT requests
  • Both auth methods — EC2 signature (recommended) and Token-based (RSA encrypted)
  • SSL flexibility — optional SSL verification disable (for self-signed certs)

Covered APIs (Top 20 Critical Features)

Resource Operations
Virtual Machines list, get, create, update/rename, delete, power on/off/reboot/suspend, clone, migrate, console URL, password reset, batch ops, soft delete & restore
Tasks get, wait (with timeout & progress callback)
Resource Pools (AZ) list, get, overview, storage tags
Tenants list, get, find by name
VPC Networks list, create, get, update, delete
Subnets list, create, get, delete
Images list (ISO/aCloud), get
Volumes (Disks) list, create, get, delete, resize, attach, detach
Elastic IPs allocate, bind, unbind, release, update bandwidth
Physical Hosts list, get, interfaces
System version, maintenance mode, platform info, license

Installation

pip install sangfor-scp

With Token-based authentication support (RSA encryption):

pip install sangfor-scp[token-auth]

Quick Start

from sangfor_scp import SCPClient

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

Token-Based Authentication

from sangfor_scp import SCPClient

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

Usage Examples

List All Virtual Machines

# Iterates all pages automatically — no pagination code needed
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="az-uuid", status="running"):
    print(vm["name"])

Create a VM and Wait for Completion

result = client.servers.create(
    az_id="az-uuid",
    image_id="image-uuid",
    storage_tag_id="storage-tag-uuid",
    cores=2,
    memory_mb=2048,
    name="my-vm",
    networks=[{
        "vif_id": "net0",
        "vpc_id": "vpc-uuid",
        "subnet_id": "subnet-uuid",
        "connect": 1,
        "model": "virtio",
    }],
    power_on=True,
)

# Wait up to 5 minutes for the VM to be ready
task = client.tasks.wait(result["task_id"], timeout=300)
vm_id = result["uuids"][0]
print(f"VM created: {vm_id}")

Declarative Provisioning (Terraform-style)

Describe the whole machine once — names are resolved to IDs, the spec is validated (image active in pool, IP not in use, VLAN exists, disk sizes), and apply waits until the VM is running.

# examples/specs/web-01.yaml
name: web-01
resource_pool: POOL-A
image: ubuntu-2404-template            # aCloud template with vmtools
storage_tag: High Performance Storage
cores: 2
memory_gb: 4
disks: [{size_gb: 150}, {size_gb: 20}]
networks:
  - {switch: edge-switch-1, vlan: vlan100, ip: 192.168.100.50, netmask: 255.255.255.0, gateway: 192.168.100.1}
hostname: web-01
power_on: true
python examples/provision.py plan    examples/specs/web-01.yaml
SCP_VM_PASSWORD=... python examples/provision.py apply examples/specs/web-01.yaml
python examples/provision.py destroy examples/specs/web-01.yaml
plan = client.provisioner.plan(spec)   # read-only; action: create | no-op | drift
print(plan.summary())
vm = client.provisioner.apply(spec, on_event=print)
client.provisioner.destroy(spec)

Re-running apply for an existing VM is a no-op; differences from the spec are reported as drift and never changed automatically.

Rename / Update a VM

# Rename
task_id = client.servers.rename("vm-uuid", "new-name")
client.tasks.wait(task_id)

# Update multiple properties at once (only passed fields are changed)
task_id = client.servers.update(
    "vm-uuid",
    name="renamed-vm",
    cores=4,
    memory_mb=8192,
    description="Updated description",
)
client.tasks.wait(task_id)

Power Operations

client.servers.stop("vm-uuid")
client.servers.stop("vm-uuid", force=True)     # force power off
client.servers.start("vm-uuid")
client.servers.reboot("vm-uuid")
client.servers.reboot("vm-uuid", force=True)   # force reboot
client.servers.suspend("vm-uuid")

Clone a VM

result = client.servers.clone(
    "vm-uuid",
    name="cloned-vm",
    az_id="az-uuid",           # optional: target resource pool
    storage_tag_id="tag-uuid", # optional: target storage
)
client.tasks.wait(result["task_id"])
new_vm_id = result["uuid"]

Migrate a VM

# Migrate to a specific host (None = auto-select)
task_id = client.servers.migrate("vm-uuid", host_id="host-uuid")
client.tasks.wait(task_id)

Get Console URL (VNC/SPICE/RDP)

info = client.servers.get_console("vm-uuid")
print(info["url"])   # Open in browser for noVNC console

# SPICE console
info = client.servers.get_console("vm-uuid", protocol="spice")

Batch Operations

vm_ids = ["vm-uuid-1", "vm-uuid-2", "vm-uuid-3"]

# Soft delete (move to recycle bin)
client.servers.soft_delete(vm_ids)

# Restore from recycle bin
task_id = client.servers.restore("vm-uuid-1")
client.tasks.wait(task_id)

# Other batch actions
client.servers.batch_action(vm_ids, "start_servers_action")
client.servers.batch_action(vm_ids, "stop_servers_action")
client.servers.batch_action(vm_ids, "reboot_servers_action")

Disk Management

# Create a standalone disk
result = client.volumes.create(
    az_id="az-uuid",
    storage_tag_id="tag-uuid",
    size_mb=102400,   # 100 GB
    name="data-disk",
)
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)

Network Management

# List VPCs
for vpc in client.networks.list_vpcs(az_id="az-uuid"):
    print(vpc["id"], vpc["name"])

# Create Subnet
result = client.networks.create_subnet(
    vpc_id="vpc-uuid",
    az_id="az-uuid",
    cidr="192.168.10.0/24",
    name="subnet-app",
    gateway_ip="192.168.10.1",
)

# List subnets in a VPC
for subnet in client.networks.list_subnets(vpc_id="vpc-uuid"):
    print(subnet["id"], subnet["cidr"])

Classic Network (non-VPC environments)

# Find the VLAN group (port) on an edge-connected virtual switch
group = client.networks.find_vlan_group("vlan100", az_id="az-uuid")

# Classic network NICs require port_id and ip_info
nic = client.networks.classic_nic(
    switch_id=group["switch_id"],
    vlan_group_id=group["id"],
    ip_address="192.168.100.50",
    netmask="255.255.255.0",
    gateway="192.168.100.1",
)
result = client.servers.create(..., networks=[nic])

Elastic IP

# List EIP pools
pools = client.eips.list_pools()

# Allocate (bandwidth in Mbps, converted to kbps for the API)
eip = client.eips.allocate(
    project_id="tenant-id",
    bandwidth_mb=100,
    floatingip_pool_id=pools[0]["id"],
)
eip_id = eip["id"]

# Bind to a VM NIC
task_id = client.eips.bind(eip_id, vpc_id="vpc-uuid", port_id="port-uuid")
client.tasks.wait(task_id)

# Bound / unbound EIPs
for e in client.eips.list_bound():
    print(e["floating_ip"], e["binding_info"]["ip_address"])

# Unbind and release
client.eips.unbind(eip_id)
client.eips.release(eip_id)

Platform Overview

# Resource utilization summary
overview = client.resource_pools.overview()
print(f"Total VMs: {overview['server']['total']}")
print(f"Running VMs: {overview['server']['running_count']}")

# SCP version
info = client.system.version()
print(info["build_version"])

Async Task with Progress Callback

def on_progress(task_data):
    print(f"Progress: {task_data['progress']}% — {task_data.get('description', '')}")

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

Manual Pagination

# Get a single page with full metadata
page = client.servers.list_page(page_num=0, page_size=10)
print(f"Total VMs: {page['total_size']}")
print(f"Next page: {page['next_page_num']}")

# Count without fetching all records
total = client.servers.count(az_id="az-uuid")
print(f"VM count in pool: {total}")

Error Handling

from sangfor_scp import (
    SCPError,
    SCPNotFoundError,
    SCPAuthError,
    SCPRateLimitError,
    SCPTaskError,
    SCPTimeoutError,
)

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

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

Requirements

  • Python >= 3.8
  • requests >= 2.28.0
  • urllib3 >= 1.26.0
  • pycryptodome >= 3.17 (only for Token-based auth)

SCP Version Compatibility

SCP Version Supported
5.8.8R6+ Basic APIs
6.3.0+ Full VM, Tenant, Network APIs
6.3.70+ AK/SK self-service, Tenant resource pool query
6.8.0+ Banner alerts, Email, G2H proxy, EIP APIs
6.10.0+ Extended host query APIs
6.11.1+ Latest API version (20240725)

License

MIT License — see LICENSE for details.


Special Thanks

Onur Çılbır

🏆 A Tribute to the Man Who Started It All

Thank you, Onur Çılbır.

Not every hero wears a cape. Some wear a smile while casually introducing their colleagues to a 200-page Chinese cloud platform API specification and saying "it shouldn't be that hard."

Onur Çılbır is that hero. Or villain. The line is blurry at this point.

It was Onur who, with the enthusiasm of someone who clearly would not be doing the actual implementation, introduced us to Sangfor Cloud Platform — a technology so full of surprises that we are still finding new ones. AWS4-HMAC-SHA256 signatures, RSA-encrypted passwords, version-specific endpoints, pagination quirks, and the eternal mystery of next_page_num: "" — none of this would have been possible without his generous recommendation.

"It has an Open API, it'll be straightforward," — Onur Çılbır, allegedly.

Thanks to Onur, we now intimately understand what it means to spend an afternoon reading about idempotency tokens, only to discover that the endpoint you need was added in "SCP 6.3.70 and later." We have read more XML-era authentication documentation than any living person should. We have emerged changed. Possibly for the worse.

We did not ask for this. But here we are, and we shipped it anyway.

— The grateful (and slightly traumatized) survivors of this project

Metadata

Release files for sangfor-scp 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sangfor-scp 0.3.0
File Size Uploaded Provenance
sangfor_scp-0.3.0.tar.gz 621.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for sangfor-scp 0.3.0
File Interpreter ABI Platform
sangfor_scp-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 667.7 kB

Release files / sangfor_scp-0.3.0.tar.gz

Download URL sangfor_scp-0.3.0.tar.gz
Size 621.6 kB
Tags Source
SHA-256 checksum
How to use checksums
c2440c26da858f73be95fd8c0d67ef4dc8e711cb2589d442fb4df0090ae59dde
BLAKE2b-256 checksum
How to use checksums
637ab123481f0709b260c5aa8131a29bfc18cc2540ed28fa23945821bb49d825
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.8

Release files / sangfor_scp-0.3.0-py3-none-any.whl

Download URL sangfor_scp-0.3.0-py3-none-any.whl
Size 46.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ea66722409e5708b943a56d0f370196ade139f06b5a216b036e033cc43125136
BLAKE2b-256 checksum
How to use checksums
882e67b44a08fd139a90199507b57f4c52c9d49b0d041d802daa9c64aaa2f224
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.8

Security for sangfor-scp 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page