| #!/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 | |
| parser = argparse.ArgumentParser( | |
| description="Get Alibaba Cloud disk console output" | |
| ) | |
| 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) |