|
| 1 | +#!/usr/bin/env python2.7 |
| 2 | + |
| 3 | +# Copyright 2017 Amazon.com, Inc. and its affiliates. All Rights Reserved. |
| 4 | +# |
| 5 | +# Licensed under the MIT License. See the LICENSE accompanying this file |
| 6 | +# for the specific language governing permissions and limitations under |
| 7 | +# the License. |
| 8 | + |
| 9 | +""" |
| 10 | +Usage: |
| 11 | +Read EBS device information and provide information about |
| 12 | +the volume. |
| 13 | +""" |
| 14 | + |
| 15 | +import argparse |
| 16 | +from ctypes import * |
| 17 | +from fcntl import ioctl |
| 18 | +import sys |
| 19 | + |
| 20 | +NVME_ADMIN_IDENTIFY = 0x06 |
| 21 | +NVME_IOCTL_ADMIN_CMD = 0xC0484E41 |
| 22 | +AMZN_NVME_VID = 0x1D0F |
| 23 | +AMZN_NVME_EBS_MN = "Amazon Elastic Block Store" |
| 24 | + |
| 25 | +class nvme_admin_command(Structure): |
| 26 | + _pack_ = 1 |
| 27 | + _fields_ = [("opcode", c_uint8), # op code |
| 28 | + ("flags", c_uint8), # fused operation |
| 29 | + ("cid", c_uint16), # command id |
| 30 | + ("nsid", c_uint32), # namespace id |
| 31 | + ("reserved0", c_uint64), |
| 32 | + ("mptr", c_uint64), # metadata pointer |
| 33 | + ("addr", c_uint64), # data pointer |
| 34 | + ("mlen", c_uint32), # metadata length |
| 35 | + ("alen", c_uint32), # data length |
| 36 | + ("cdw10", c_uint32), |
| 37 | + ("cdw11", c_uint32), |
| 38 | + ("cdw12", c_uint32), |
| 39 | + ("cdw13", c_uint32), |
| 40 | + ("cdw14", c_uint32), |
| 41 | + ("cdw15", c_uint32), |
| 42 | + ("reserved1", c_uint64)] |
| 43 | + |
| 44 | +class nvme_identify_controller_amzn_vs(Structure): |
| 45 | + _pack_ = 1 |
| 46 | + _fields_ = [("bdev", c_char * 32), # block device name |
| 47 | + ("reserved0", c_char * (1024 - 32))] |
| 48 | + |
| 49 | +class nvme_identify_controller_psd(Structure): |
| 50 | + _pack_ = 1 |
| 51 | + _fields_ = [("mp", c_uint16), # maximum power |
| 52 | + ("reserved0", c_uint16), |
| 53 | + ("enlat", c_uint32), # entry latency |
| 54 | + ("exlat", c_uint32), # exit latency |
| 55 | + ("rrt", c_uint8), # relative read throughput |
| 56 | + ("rrl", c_uint8), # relative read latency |
| 57 | + ("rwt", c_uint8), # relative write throughput |
| 58 | + ("rwl", c_uint8), # relative write latency |
| 59 | + ("reserved1", c_char * 16)] |
| 60 | + |
| 61 | +class nvme_identify_controller(Structure): |
| 62 | + _pack_ = 1 |
| 63 | + _fields_ = [("vid", c_uint16), # PCI Vendor ID |
| 64 | + ("ssvid", c_uint16), # PCI Subsystem Vendor ID |
| 65 | + ("sn", c_char * 20), # Serial Number |
| 66 | + ("mn", c_char * 40), # Module Number |
| 67 | + ("fr", c_char * 8), # Firmware Revision |
| 68 | + ("rab", c_uint8), # Recommend Arbitration Burst |
| 69 | + ("ieee", c_uint8 * 3), # IEEE OUI Identifier |
| 70 | + ("mic", c_uint8), # Multi-Interface Capabilities |
| 71 | + ("mdts", c_uint8), # Maximum Data Transfer Size |
| 72 | + ("reserved0", c_uint8 * (256 - 78)), |
| 73 | + ("oacs", c_uint16), # Optional Admin Command Support |
| 74 | + ("acl", c_uint8), # Abort Command Limit |
| 75 | + ("aerl", c_uint8), # Asynchronous Event Request Limit |
| 76 | + ("frmw", c_uint8), # Firmware Updates |
| 77 | + ("lpa", c_uint8), # Log Page Attributes |
| 78 | + ("elpe", c_uint8), # Error Log Page Entries |
| 79 | + ("npss", c_uint8), # Number of Power States Support |
| 80 | + ("avscc", c_uint8), # Admin Vendor Specific Command Configuration |
| 81 | + ("reserved1", c_uint8 * (512 - 265)), |
| 82 | + ("sqes", c_uint8), # Submission Queue Entry Size |
| 83 | + ("cqes", c_uint8), # Completion Queue Entry Size |
| 84 | + ("reserved2", c_uint16), |
| 85 | + ("nn", c_uint32), # Number of Namespaces |
| 86 | + ("oncs", c_uint16), # Optional NVM Command Support |
| 87 | + ("fuses", c_uint16), # Fused Operation Support |
| 88 | + ("fna", c_uint8), # Format NVM Attributes |
| 89 | + ("vwc", c_uint8), # Volatile Write Cache |
| 90 | + ("awun", c_uint16), # Atomic Write Unit Normal |
| 91 | + ("awupf", c_uint16), # Atomic Write Unit Power Fail |
| 92 | + ("nvscc", c_uint8), # NVM Vendor Specific Command Configuration |
| 93 | + ("reserved3", c_uint8 * (704 - 531)), |
| 94 | + ("reserved4", c_uint8 * (2048 - 704)), |
| 95 | + ("psd", nvme_identify_controller_psd * 32), # Power State Descriptor |
| 96 | + ("vs", nvme_identify_controller_amzn_vs)] # Vendor Specific |
| 97 | + |
| 98 | +class ebs_nvme_device: |
| 99 | + def __init__(self, device): |
| 100 | + self.device = device |
| 101 | + self.ctrl_identify() |
| 102 | + |
| 103 | + def _nvme_ioctl(self, id_response, id_len): |
| 104 | + admin_cmd = nvme_admin_command(opcode = NVME_ADMIN_IDENTIFY, |
| 105 | + addr = id_response, |
| 106 | + alen = id_len, |
| 107 | + cdw10 = 1) |
| 108 | + |
| 109 | + with open(self.device, "rw") as nvme: |
| 110 | + ioctl(nvme, NVME_IOCTL_ADMIN_CMD, admin_cmd) |
| 111 | + |
| 112 | + def ctrl_identify(self): |
| 113 | + self.id_ctrl = nvme_identify_controller() |
| 114 | + self._nvme_ioctl(addressof(self.id_ctrl), sizeof(self.id_ctrl)) |
| 115 | + |
| 116 | + if self.id_ctrl.vid != AMZN_NVME_VID or self.id_ctrl.mn.strip() != AMZN_NVME_EBS_MN: |
| 117 | + raise TypeError("[ERROR] Not an EBS device: '{0}'".format(self.device)) |
| 118 | + |
| 119 | + def get_volume_id(self): |
| 120 | + vol = self.id_ctrl.sn |
| 121 | + |
| 122 | + if vol.startswith("vol") and vol[3] != "-": |
| 123 | + vol = "vol-" + vol[3:] |
| 124 | + |
| 125 | + return vol |
| 126 | + |
| 127 | + def get_block_device(self, stripped=False): |
| 128 | + dev = self.id_ctrl.vs.bdev.strip() |
| 129 | + |
| 130 | + if stripped and dev.startswith("/dev/"): |
| 131 | + dev = dev[5:] |
| 132 | + |
| 133 | + return dev |
| 134 | + |
| 135 | +if __name__ == "__main__": |
| 136 | + parser = argparse.ArgumentParser(description="Reads EBS information from NVMe devices.") |
| 137 | + parser.add_argument("device", nargs=1, help="Device to query") |
| 138 | + |
| 139 | + display = parser.add_argument_group("Display Options") |
| 140 | + display.add_argument("-v", "--volume", action="store_true", |
| 141 | + help="Return volume-id") |
| 142 | + display.add_argument("-b", "--block-dev", action="store_true", |
| 143 | + help="Return block device mapping") |
| 144 | + display.add_argument("-u", "--udev", action="store_true", |
| 145 | + help="Output data in format suitable for udev rules") |
| 146 | + |
| 147 | + if len(sys.argv) < 2: |
| 148 | + parser.print_help() |
| 149 | + sys.exit(1) |
| 150 | + |
| 151 | + args = parser.parse_args() |
| 152 | + |
| 153 | + get_all = not (args.udev or args.volume or args.block_dev) |
| 154 | + |
| 155 | + try: |
| 156 | + dev = ebs_nvme_device(args.device[0]) |
| 157 | + except (IOError, TypeError) as err: |
| 158 | + print >> sys.stderr, err |
| 159 | + sys.exit(1) |
| 160 | + |
| 161 | + if get_all or args.volume: |
| 162 | + print "Volume ID: {0}".format(dev.get_volume_id()) |
| 163 | + if get_all or args.block_dev or args.udev: |
| 164 | + print dev.get_block_device(args.udev) |
0 commit comments