2026-03-20 13:48:54 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import base64
|
|
|
|
|
from collections import namedtuple
|
|
|
|
|
import datetime
|
|
|
|
|
import json
|
|
|
|
|
import time
|
|
|
|
|
from uuid import uuid4
|
|
|
|
|
|
|
|
|
|
import alibabacloud_credentials as credentials
|
|
|
|
|
import alibabacloud_credentials.client
|
|
|
|
|
import alibabacloud_credentials.models
|
|
|
|
|
import alibabacloud_ecs20140526 as ecs
|
|
|
|
|
import alibabacloud_ecs20140526.client
|
|
|
|
|
import alibabacloud_ecs20140526.models
|
|
|
|
|
import alibabacloud_tea_openapi as openapi
|
|
|
|
|
import alibabacloud_tea_openapi.client
|
|
|
|
|
import alibabacloud_tea_openapi.models
|
|
|
|
|
|
|
|
|
|
IPXE_LOG_PREFIX = 'ipxe-log-temp-'
|
|
|
|
|
IPXE_LOG_TAG = 'ipxe-log-temp-snapshot'
|
|
|
|
|
IPXE_LOG_MAGIC = 'iPXE LOG'
|
|
|
|
|
IPXE_LOG_DISK_SIZE = 20
|
|
|
|
|
IPXE_LOG_DISK_CATEGORY = 'cloud_essd'
|
|
|
|
|
|
|
|
|
|
Clients = namedtuple('Clients', ['region', 'ecs'])
|
|
|
|
|
|
|
|
|
|
def all_clients(region):
|
|
|
|
|
"""Construct all per-region clients"""
|
|
|
|
|
cred = credentials.client.Client()
|
|
|
|
|
ecsconf = openapi.models.Config(credential=cred, region_id=region)
|
|
|
|
|
clients = Clients(
|
|
|
|
|
region=region,
|
|
|
|
|
ecs=ecs.client.Client(ecsconf),
|
|
|
|
|
)
|
|
|
|
|
return clients
|
|
|
|
|
|
|
|
|
|
def get_log_disk(clients, instance):
|
|
|
|
|
"""Get log disk ID"""
|
|
|
|
|
req = ecs.models.DescribeDisksRequest(
|
|
|
|
|
region_id=clients.region,
|
|
|
|
|
instance_id=instance,
|
|
|
|
|
)
|
|
|
|
|
rsp = clients.ecs.describe_disks(req)
|
|
|
|
|
return rsp.body.disks.disk[0].disk_id
|
|
|
|
|
|
|
|
|
|
def delete_temp_snapshot(clients, snapshot):
|
|
|
|
|
"""Remove temporary snapshot"""
|
|
|
|
|
req = ecs.models.DeleteSnapshotRequest(snapshot_id=snapshot, force=True)
|
|
|
|
|
clients.ecs.delete_snapshot(req)
|
|
|
|
|
|
|
|
|
|
def delete_temp_snapshots(clients):
|
|
|
|
|
"""Remove all old temporary snapshots"""
|
|
|
|
|
tag = ecs.models.DescribeSnapshotsRequestTag(
|
|
|
|
|
key=IPXE_LOG_TAG,
|
|
|
|
|
value=IPXE_LOG_TAG,
|
|
|
|
|
)
|
|
|
|
|
req = ecs.models.DescribeSnapshotsRequest(
|
|
|
|
|
region_id=clients.region,
|
|
|
|
|
tag=[tag],
|
|
|
|
|
)
|
|
|
|
|
rsp = clients.ecs.describe_snapshots(req)
|
|
|
|
|
for snapshot in rsp.body.snapshots.snapshot or []:
|
|
|
|
|
assert snapshot.snapshot_name.startswith(IPXE_LOG_PREFIX)
|
|
|
|
|
delete_temp_snapshot(clients, snapshot.snapshot_id)
|
|
|
|
|
|
|
|
|
|
def create_temp_snapshot(clients, disk):
|
|
|
|
|
"""Create temporary snapshot"""
|
|
|
|
|
name = '%s%s' % (IPXE_LOG_PREFIX, uuid4())
|
|
|
|
|
tag = ecs.models.CreateSnapshotRequestTag(
|
|
|
|
|
key=IPXE_LOG_TAG,
|
|
|
|
|
value=IPXE_LOG_TAG,
|
|
|
|
|
)
|
|
|
|
|
req = ecs.models.CreateSnapshotRequest(
|
|
|
|
|
disk_id=disk,
|
|
|
|
|
snapshot_name=name,
|
|
|
|
|
retention_days=1,
|
|
|
|
|
tag=[tag],
|
|
|
|
|
)
|
|
|
|
|
rsp = clients.ecs.create_snapshot(req)
|
|
|
|
|
snapshot = rsp.body.snapshot_id
|
|
|
|
|
while True:
|
|
|
|
|
time.sleep(1)
|
|
|
|
|
req = ecs.models.DescribeSnapshotsRequest(
|
|
|
|
|
region_id=clients.region,
|
|
|
|
|
snapshot_ids=json.dumps([snapshot]),
|
|
|
|
|
)
|
|
|
|
|
rsp = clients.ecs.describe_snapshots(req)
|
|
|
|
|
status = rsp.body.snapshots.snapshot[0].status
|
|
|
|
|
if status != 'progressing':
|
|
|
|
|
break
|
|
|
|
|
if status != 'accomplished':
|
|
|
|
|
raise RuntimeError(status)
|
|
|
|
|
return snapshot
|
|
|
|
|
|
|
|
|
|
def delete_temp_instance(clients, instance, retry=False):
|
|
|
|
|
"""Remove temporary log dumper instance"""
|
|
|
|
|
while True:
|
|
|
|
|
req = ecs.models.DeleteInstanceRequest(
|
|
|
|
|
instance_id=instance,
|
|
|
|
|
force=True,
|
|
|
|
|
force_stop=True,
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
rsp = clients.ecs.delete_instance(req)
|
|
|
|
|
except openapi.exceptions.ClientException:
|
|
|
|
|
# Very recently created instances often cannot be
|
|
|
|
|
# terminated until some undocumented part of the control
|
|
|
|
|
# plane decides that enough time has elapsed
|
|
|
|
|
if retry:
|
|
|
|
|
time.sleep(1)
|
|
|
|
|
continue
|
|
|
|
|
raise
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
def delete_temp_instances(clients):
|
|
|
|
|
"""Remove all old temporary log dumper instances"""
|
|
|
|
|
tag = ecs.models.DescribeInstancesRequestTag(
|
|
|
|
|
key=IPXE_LOG_TAG,
|
|
|
|
|
value=IPXE_LOG_TAG,
|
|
|
|
|
)
|
|
|
|
|
req = ecs.models.DescribeInstancesRequest(
|
|
|
|
|
region_id=clients.region,
|
|
|
|
|
tag=[tag],
|
|
|
|
|
)
|
|
|
|
|
rsp = clients.ecs.describe_instances(req)
|
|
|
|
|
for instance in rsp.body.instances.instance or []:
|
|
|
|
|
assert instance.instance_name.startswith(IPXE_LOG_PREFIX)
|
|
|
|
|
delete_temp_instance(clients, instance.instance_id)
|
|
|
|
|
|
|
|
|
|
def create_temp_instance(clients, reference, snapshot, family, machine):
|
|
|
|
|
"""Create temporary log dumper instance"""
|
|
|
|
|
req = ecs.models.DescribeInstancesRequest(
|
|
|
|
|
region_id=clients.region,
|
|
|
|
|
instance_ids=json.dumps([reference]),
|
|
|
|
|
)
|
|
|
|
|
rsp = clients.ecs.describe_instances(req)
|
|
|
|
|
instance = rsp.body.instances.instance[0]
|
|
|
|
|
secgroups = instance.security_group_ids.security_group_id
|
|
|
|
|
vswitch = instance.vpc_attributes.v_switch_id
|
|
|
|
|
name = '%s%s' % (IPXE_LOG_PREFIX, uuid4())
|
|
|
|
|
sysdisk = ecs.models.RunInstancesRequestSystemDisk(
|
|
|
|
|
category=IPXE_LOG_DISK_CATEGORY,
|
|
|
|
|
)
|
|
|
|
|
now = datetime.datetime.now(datetime.UTC)
|
|
|
|
|
lifetime = datetime.timedelta(hours=1)
|
|
|
|
|
release = (now + lifetime).strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
|
|
|
datadisk = ecs.models.RunInstancesRequestDataDisk(
|
|
|
|
|
delete_with_instance=True,
|
|
|
|
|
snapshot_id=snapshot,
|
|
|
|
|
disk_name='ipxelog',
|
|
|
|
|
size=IPXE_LOG_DISK_SIZE,
|
|
|
|
|
category=IPXE_LOG_DISK_CATEGORY,
|
|
|
|
|
)
|
|
|
|
|
tag = ecs.models.RunInstancesRequestTag(
|
|
|
|
|
key=IPXE_LOG_TAG,
|
|
|
|
|
value=IPXE_LOG_TAG,
|
|
|
|
|
)
|
|
|
|
|
req = ecs.models.RunInstancesRequest(
|
|
|
|
|
region_id=clients.region,
|
|
|
|
|
image_family=family,
|
|
|
|
|
instance_type=machine,
|
|
|
|
|
instance_name=name,
|
|
|
|
|
auto_release_time=release,
|
|
|
|
|
system_disk=sysdisk,
|
|
|
|
|
data_disk=[datadisk],
|
|
|
|
|
security_group_ids=secgroups,
|
|
|
|
|
v_switch_id=vswitch,
|
|
|
|
|
tag=[tag],
|
|
|
|
|
)
|
|
|
|
|
rsp = clients.ecs.run_instances(req)
|
|
|
|
|
return rsp.body.instance_id_sets.instance_id_set[0]
|
|
|
|
|
|
|
|
|
|
def run_command(clients, instance, command):
|
|
|
|
|
"""Run command on instance"""
|
|
|
|
|
req = ecs.models.RunCommandRequest(
|
|
|
|
|
region_id=clients.region,
|
|
|
|
|
instance_id=[instance],
|
|
|
|
|
type='RunShellScript',
|
|
|
|
|
command_content=command,
|
|
|
|
|
)
|
|
|
|
|
rsp = clients.ecs.run_command(req)
|
|
|
|
|
invocation = rsp.body.invoke_id
|
|
|
|
|
while True:
|
|
|
|
|
time.sleep(1)
|
|
|
|
|
req = ecs.models.DescribeInvocationResultsRequest(
|
|
|
|
|
region_id=clients.region,
|
|
|
|
|
invoke_id=invocation,
|
|
|
|
|
)
|
|
|
|
|
rsp = clients.ecs.describe_invocation_results(req)
|
|
|
|
|
result = rsp.body.invocation.invocation_results.invocation_result[0]
|
|
|
|
|
if result.invoke_record_status not in ('Pending', 'Running'):
|
|
|
|
|
break
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
def get_log_output(clients, instance):
|
|
|
|
|
"""Get iPXE log output"""
|
|
|
|
|
output = b''
|
|
|
|
|
while True:
|
|
|
|
|
command = " | ".join([
|
|
|
|
|
f"tr -d '\\000' < /dev/disk/by-diskseq/2-part3",
|
|
|
|
|
f"tail -c +{ len(output) + 1 }"
|
|
|
|
|
])
|
|
|
|
|
result = run_command(clients, instance, command)
|
|
|
|
|
output += base64.b64decode(result.output)
|
|
|
|
|
if not result.dropped:
|
|
|
|
|
break
|
|
|
|
|
log = output.decode()
|
|
|
|
|
if log.startswith(IPXE_LOG_MAGIC):
|
|
|
|
|
log = log[len(IPXE_LOG_MAGIC):]
|
|
|
|
|
return log
|
|
|
|
|
|
|
|
|
|
def force_power_off(clients, instance):
|
|
|
|
|
"""Forcibly power-off instance"""
|
|
|
|
|
command = " ; ".join([
|
|
|
|
|
"echo 1 > /proc/sys/kernel/sysrq",
|
|
|
|
|
"echo o > /proc/sysrq-trigger"
|
|
|
|
|
])
|
|
|
|
|
req = ecs.models.RunCommandRequest(
|
|
|
|
|
region_id=clients.region,
|
|
|
|
|
instance_id=[instance],
|
|
|
|
|
type='RunShellScript',
|
|
|
|
|
command_content=command,
|
|
|
|
|
)
|
|
|
|
|
rsp = clients.ecs.run_command(req)
|
|
|
|
|
|
|
|
|
|
# Parse command-line arguments
|
2026-04-09 10:47:55 +01:00
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
description="Get Alibaba Cloud disk console output"
|
|
|
|
|
)
|
2026-03-20 13:48:54 +00:00
|
|
|
parser.add_argument('--region', '-r', required=True,
|
|
|
|
|
help="AliCloud region")
|
|
|
|
|
parser.add_argument('--family', '-f',
|
|
|
|
|
default="acs:alibaba_cloud_linux_4_lts_x64",
|
|
|
|
|
help="Helper OS image family")
|
|
|
|
|
parser.add_argument('--machine', '-m', default="ecs.e-c4m1.large",
|
|
|
|
|
help="Helper machine type")
|
|
|
|
|
parser.add_argument('instance', help="Instance ID")
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
# Construct clients
|
|
|
|
|
clients = all_clients(args.region)
|
|
|
|
|
|
|
|
|
|
# Clean up old temporary objects
|
|
|
|
|
delete_temp_instances(clients)
|
|
|
|
|
delete_temp_snapshots(clients)
|
|
|
|
|
|
|
|
|
|
# Create log disk snapshot
|
|
|
|
|
logdisk = get_log_disk(clients, args.instance)
|
|
|
|
|
logsnap = create_temp_snapshot(clients, logdisk)
|
|
|
|
|
|
|
|
|
|
# Create log dumper instance
|
|
|
|
|
dumper = create_temp_instance(clients, args.instance, logsnap, args.family,
|
|
|
|
|
args.machine)
|
|
|
|
|
|
|
|
|
|
# Wait for log output
|
|
|
|
|
output = get_log_output(clients, dumper)
|
|
|
|
|
|
|
|
|
|
# Print log output
|
|
|
|
|
print(output)
|
|
|
|
|
|
|
|
|
|
# Clean up
|
|
|
|
|
force_power_off(clients, dumper)
|
|
|
|
|
delete_temp_instance(clients, dumper, retry=True)
|
|
|
|
|
delete_temp_snapshot(clients, logsnap)
|