-
-
Save y0zg/6fc9f39fb0222aa4b0fed73a88af02aa to your computer and use it in GitHub Desktop.
ec2 vbd and NVMe udev rules and helpers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ensure any xen virtual block devices named xvdN get a sdN symlink for consistency | |
KERNEL=="xvd*", PROGRAM="/sbin/ec2udev-vbd %k", SYMLINK+="%c" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright (C) 2006-2016 Amazon.com, Inc. or its affiliates. | |
# All Rights Reserved. | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"). | |
# You may not use this file except in compliance with the License. | |
# A copy of the License is located at | |
# | |
# http://aws.amazon.com/apache2.0/ | |
# | |
# or in the "license" file accompanying this file. This file is | |
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS | |
# OF ANY KIND, either express or implied. See the License for the | |
# specific language governing permissions and limitations under the | |
# License. | |
#nvme-ns-* devices | |
KERNEL=="nvme[0-9]*n[0-9]*", ENV{DEVTYPE}=="disk", ATTRS{serial}=="?*", ATTRS{model}=="?*", SYMLINK+="disk/by-id/nvme-$attr{model}_$attr{serial}-ns-%n", OPTIONS+="string_escape=replace" | |
#nvme partitions | |
KERNEL=="nvme[0-9]*n[0-9]*p[0-9]*", ENV{DEVTYPE}=="partition", ATTRS{serial}=="?*", ATTRS{model}=="?*", IMPORT{program}="ec2nvme-nsid %k" | |
KERNEL=="nvme[0-9]*n[0-9]*p[0-9]*", ENV{DEVTYPE}=="partition", ATTRS{serial}=="?*", ATTRS{model}=="?*", ENV{_NS_ID}=="?*", SYMLINK+="disk/by-id/nvme-$attr{model}_$attr{serial}-ns-$env{_NS_ID}-part%n", OPTIONS+="string_escape=replace" | |
# ebs nvme devices | |
KERNEL=="nvme[0-9]*n[0-9]*", ENV{DEVTYPE}=="disk", ATTRS{model}=="Amazon Elastic Block Store", IMPORT{program}="ec2nvme-id -u %k" | |
KERNEL=="nvme[0-9]*n[0-9]*", ENV{DEVTYPE}=="disk", ATTRS{model}=="Amazon Elastic Block Store", ENV{_EBS_ALIAS_SD}=="?*", SYMLINK+="$env{_EBS_ALIAS_SD}" | |
KERNEL=="nvme[0-9]*n[0-9]*", ENV{DEVTYPE}=="disk", ATTRS{model}=="Amazon Elastic Block Store", ENV{_EBS_ALIAS_XVD}=="?*", SYMLINK+="$env{_EBS_ALIAS_XVD}" | |
# ebs nvme device partitions | |
KERNEL=="nvme[0-9]*n[0-9]*p[0-9]*", ENV{DEVTYPE}=="partition", ATTRS{model}=="Amazon Elastic Block Store", IMPORT{program}="ec2nvme-id -u /dev/%k" | |
KERNEL=="nvme[0-9]*n[0-9]*p[0-9]*", ENV{DEVTYPE}=="partition", ATTRS{model}=="Amazon Elastic Block Store", ENV{_EBS_ALIAS_SD}=="?*", SYMLINK+="$env{_EBS_ALIAS_SD}%n" | |
KERNEL=="nvme[0-9]*n[0-9]*p[0-9]*", ENV{DEVTYPE}=="partition", ATTRS{model}=="Amazon Elastic Block Store", ENV{_EBS_ALIAS_XVD}=="?*", SYMLINK+="$env{_EBS_ALIAS_XVD}%n" | |
# instance-store nvme devices | |
KERNEL=="nvme[0-9]*n[0-9]*", ENV{DEVTYPE}=="disk", ATTRS{model}=="Amazon EC2 NVMe Instance Storage", IMPORT{program}="ec2nvme-id -u /dev/%k" | |
KERNEL=="nvme[0-9]*n[0-9]*", ENV{DEVTYPE}=="disk", ATTRS{model}=="Amazon EC2 NVMe Instance Storage", ENV{_ISTORE_ALIAS}=="?*", SYMLINK+="$env{_ISTORE_ALIAS}" | |
# instance-store nvme device partitions | |
KERNEL=="nvme[0-9]*n[0-9]*p[0-9]*", ENV{DEVTYPE}=="partition", ATTRS{model}=="Amazon EC2 NVMe Instance Storage", IMPORT{program}="ec2nvme-id -u /dev/%k" | |
KERNEL=="nvme[0-9]*n[0-9]*p[0-9]*", ENV{DEVTYPE}=="partition", ATTRS{model}=="Amazon EC2 NVMe Instance Storage", ENV{_ISTORE_ALIAS}=="?*", SYMLINK+="$env{_ISTORE_ALIAS}p%n" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# Copyright (C) 2017 Amazon.com, Inc. or its affiliates. | |
# All Rights Reserved. | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"). | |
# You may not use this file except in compliance with the License. | |
# A copy of the License is located at | |
# | |
# http://aws.amazon.com/apache2.0/ | |
# | |
# or in the "license" file accompanying this file. This file is | |
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS | |
# OF ANY KIND, either express or implied. See the License for the | |
# specific language governing permissions and limitations under the | |
# License. | |
""" | |
Usage: | |
Read EBS device information and provide information about | |
the volume. | |
Modified from original Amazon source to support python3 and flesh out | |
structures in case this becomes useful in other contexts. | |
""" | |
import argparse | |
import sys | |
from collections import namedtuple | |
from ctypes import c_uint8, c_uint16, c_uint32, c_uint64, c_char | |
from ctypes import addressof, sizeof | |
from ctypes import Structure | |
from enum import Enum, unique | |
from fcntl import ioctl | |
from pathlib import Path | |
NVME_ADMIN_IDENTIFY = 0x06 | |
NVME_IOCTL_ADMIN_CMD = 0xC0484E41 | |
AMZN_NVME_VID = 0x1D0F | |
AMZN_NVME_EBS_MN = 'Amazon Elastic Block Store' | |
AMZN_NVME_ISTORE_MN = 'Amazon EC2 NVMe Instance Storage' | |
NVME_FILENAME_PATTERN = 'nvme[0-9]*n[0-9]*' | |
ISTORE_ALIAS_PREFIX = 'ec2_ephemeral_nvme' | |
# linux/nvme_ioctl.h | |
class NvmePassthruCmd(Structure): | |
_pack_ = 1 | |
_fields_ = [ | |
('opcode', c_uint8), | |
('flags', c_uint8), | |
('rsvd1', c_uint16), | |
('nsid', c_uint32), | |
('cdw2', c_uint32), | |
('cdw3', c_uint32), | |
('metadata', c_uint64), | |
('addr', c_uint64), | |
('metadata_len', c_uint32), | |
('data_len', c_uint32), | |
('cdw10', c_uint32), | |
('cdw11', c_uint32), | |
('cdw12', c_uint32), | |
('cdw13', c_uint32), | |
('cdw14', c_uint32), | |
('cdw15', c_uint32), | |
('timeout_ms', c_uint32), | |
('result', c_uint32), | |
] | |
# /usr/src/<linux headers>/linux/nvme.h | |
class NvmeIdPowerState(Structure): | |
_pack_ = 1 | |
_fields_ = [ | |
('max_power', c_uint16), | |
('rsvd2', c_uint8), | |
('flags', c_uint8), | |
('entry_lat', c_uint32), | |
('exit_lat', c_uint32), | |
('read_tput', c_uint8), | |
('read_lat', c_uint8), | |
('write_tput', c_uint8), | |
('write_lat', c_uint8), | |
('idle_power', c_uint16), | |
('idle_scale', c_uint8), | |
('rsvd19', c_uint8), | |
('active_power', c_uint16), | |
('active_work_scale', c_uint8), | |
('rsvd23', c_uint8 * 9), | |
] | |
class NvmeIdVsAmzn(Structure): | |
_pack_ = 1 | |
_fields_ = [ | |
('bdev', c_char * 32), # block device name | |
('reserved0', c_char * (1024 - 32)), | |
] | |
# /usr/src/<linux headers>/linux/nvme.h | |
class NvmeIdCtrl(Structure): | |
_pack_ = 1 | |
_fields_ = [ | |
('vid', c_uint16), | |
('ssvid', c_uint16), | |
('sn', c_char * 20), | |
('mn', c_char * 40), | |
('fr', c_char * 8), | |
('rab', c_uint8), | |
('ieee', c_uint8 * 3), | |
('cmic', c_uint8), | |
('mdts', c_uint8), | |
('cntlid', c_uint16), | |
('ver', c_uint32), | |
('rtd3r', c_uint32), | |
('rtd3e', c_uint32), | |
('oaes', c_uint32), | |
('ctratt', c_uint32), | |
('rsvd100', c_uint8 * 156), | |
('oacs', c_uint16), | |
('acl', c_uint8), | |
('aerl', c_uint8), | |
('frmw', c_uint8), | |
('lpa', c_uint8), | |
('elpe', c_uint8), | |
('npss', c_uint8), | |
('avscc', c_uint8), | |
('apsta', c_uint8), | |
('wctemp', c_uint16), | |
('cctemp', c_uint16), | |
('mtfa', c_uint16), | |
('hmpre', c_uint32), | |
('hmmin', c_uint32), | |
('tnvmcap', c_uint8 * 16), | |
('unvmcap', c_uint8 * 16), | |
('rpmbs', c_uint32), | |
('edstt', c_uint16), | |
('dsto', c_uint8), | |
('fwug', c_uint8), | |
('kas', c_uint16), | |
('hctma', c_uint16), | |
('mntmt', c_uint16), | |
('mxtmt', c_uint16), | |
('sanicap', c_uint32), | |
('hmminds', c_uint32), | |
('hmmaxd', c_uint16), | |
('rsvd338', c_uint8 * 174), | |
('sqes', c_uint8), | |
('cqes', c_uint8), | |
('maxcmd', c_uint16), | |
('nn', c_uint32), | |
('oncs', c_uint16), | |
('fuses', c_uint16), | |
('fna', c_uint8), | |
('vwc', c_uint8), | |
('awun', c_uint16), | |
('awupf', c_uint16), | |
('nvscc', c_uint8), | |
('rsvd531', c_uint8), | |
('acwu', c_uint16), | |
('rsvd534', c_uint8 * 2), | |
('sgls', c_uint32), | |
('rsvd540', c_uint8 * 228), | |
('subnqn', c_char * 256), | |
('rsvd1024', c_uint8 * 768), | |
('ioccsz', c_uint32), | |
('iorcsz', c_uint32), | |
('icdoff', c_uint16), | |
('ctrattr', c_uint8), | |
('msdbd', c_uint8), | |
('rsvd1804', c_uint8 * 244), | |
('psd', NvmeIdPowerState * 32), | |
('vs', NvmeIdVsAmzn), | |
] | |
def nvme_id_ctrl(device): | |
id_ctrl = NvmeIdCtrl() | |
id_ctrl_addr = addressof(id_ctrl) | |
id_ctrl_len = sizeof(id_ctrl) | |
command = NvmePassthruCmd( | |
opcode=NVME_ADMIN_IDENTIFY, | |
addr=id_ctrl_addr, | |
data_len=id_ctrl_len, | |
cdw10=1 | |
) | |
with device.open('r+b') as blockdev: | |
ioctl(blockdev, NVME_IOCTL_ADMIN_CMD, command) | |
return id_ctrl | |
@unique | |
class Ec2NvmeDeviceType(Enum): | |
instance_store = 1 | |
ebs = 2 | |
invalid = -1 | |
Ec2NvmeEbsAliases = namedtuple('Ec2NvmeEbsAliases', ['sd', 'xvd']) | |
class NvmeDevice: | |
def __init__(self, device): | |
self._controller = None | |
self._device = Path(device) | |
self._model = None | |
self._vendor_id = None | |
self._serial_number = None | |
self._kind = None | |
self._volume_id = None | |
self._ebs_block_device = None | |
self._instance_store_alias = None | |
self._udev_aliases = None | |
@property | |
def device(self): | |
return str(self._device) | |
@property | |
def path(self): | |
return self._device | |
@property | |
def controller(self): | |
if self._controller is None: | |
self._controller = nvme_id_ctrl(self.path) | |
return self._controller | |
@property | |
def model(self): | |
if self._model is None: | |
self._model = self.controller.mn.decode().strip() | |
return self._model | |
@property | |
def vendor_id(self): | |
if self._vendor_id is None: | |
self._vendor_id = self.controller.vid | |
return self._vendor_id | |
@property | |
def serial_number(self): | |
if self._serial_number is None: | |
self._serial_number = self.controller.sn.decode().strip() | |
return self._serial_number | |
@property | |
def kind(self): | |
if self._kind is None: | |
kind = Ec2NvmeDeviceType.invalid | |
if self.vendor_id == AMZN_NVME_VID: | |
if self.model == AMZN_NVME_EBS_MN: | |
kind = Ec2NvmeDeviceType.ebs | |
elif self.model == AMZN_NVME_ISTORE_MN: | |
kind = Ec2NvmeDeviceType.instance_store | |
self._kind = kind | |
return self._kind | |
@property | |
def volume_id(self): | |
if self.kind is not Ec2NvmeDeviceType.ebs: | |
return None | |
if self._volume_id is None: | |
vol = self.serial_number | |
if vol.startswith('vol') and vol[3] != '-': | |
vol = 'vol-' + vol[3:] | |
self._volume_id = vol | |
return self._volume_id | |
@property | |
def ebs_block_device(self): | |
if self.kind is not Ec2NvmeDeviceType.ebs: | |
return None | |
if self._ebs_block_device is None: | |
bdev = Path(self.controller.vs.bdev.decode().strip()) | |
self._ebs_block_device = bdev.name | |
return self._ebs_block_device | |
@property | |
def instance_store_alias(self): | |
# For instance-store devices, nvmeXn1 -> {ISTORE_ALIAS_PREFIX}X | |
if self.kind is not Ec2NvmeDeviceType.instance_store: | |
return None | |
if self._instance_store_alias is None: | |
devname = self.path.name | |
devnum = devname[4] | |
self._instance_store_alias = [ISTORE_ALIAS_PREFIX + devnum] | |
return self._instance_store_alias | |
@property | |
def udev_aliases(self): | |
if self._udev_aliases is None: | |
if self.kind is Ec2NvmeDeviceType.instance_store: | |
self._udev_aliases = self.instance_store_alias | |
elif self.kind is Ec2NvmeDeviceType.ebs: | |
if self.ebs_block_device.startswith('xvd'): | |
self._udev_aliases = Ec2NvmeEbsAliases( | |
xvd=self.ebs_block_device.replace('xvd', 'sd', 1), | |
sd=self.ebs_block_device | |
) | |
elif self.ebs_block_device.startswith('sd'): | |
self._udev_aliases = Ec2NvmeEbsAliases( | |
sd=self.ebs_block_device, | |
xvd=self.ebs_block_device.replace('sd', 'xvd', 1) | |
) | |
else: | |
return None | |
else: | |
return None | |
return self._udev_aliases | |
@property | |
def block_device_aliases(self): | |
return (None if self.udev_aliases is None | |
else ['/dev/' + dev for dev in self.udev_aliases]) | |
def handle_args(): | |
parser = argparse.ArgumentParser( | |
description='Reads EBS or instance-store information from NVMe devices.') | |
parser.add_argument('device', type=Path, help='Device to query') | |
display = parser.add_argument_group( | |
title='Output Options', | |
description=('Options may not be combined. ' | |
'Default is to return both volume and block device')) | |
dgrp = display.add_mutually_exclusive_group() | |
dgrp.add_argument('-v', '--volume', action='store_true', | |
help='Return volume-id') | |
dgrp.add_argument('-b', '--block-dev-aliases', action='store_true', | |
help='Return block device aliases') | |
dgrp.add_argument('-u', '--udev', action='store_true', | |
help='Output block device aliases in format suitable for udev rules') | |
args = parser.parse_args() | |
if args.device.parent != Path('/dev/'): | |
args.device = Path('/dev', args.device) | |
device = args.device | |
devstr = str(device) | |
if not device.exists(): | |
err = "No such file or directory: '{}'" | |
print(err.format(devstr), file=sys.stderr) | |
return None | |
if not device.is_block_device(): | |
err = "Provided device is not a block device: '{}'" | |
print(err.format(devstr), file=sys.stderr) | |
return None | |
if not device.resolve().match(NVME_FILENAME_PATTERN): | |
err = "Device (or symlink target) is not a nvme device: '{}'" | |
print(err.format(devstr), file=sys.stderr) | |
return None | |
return args | |
def main(): | |
args = handle_args() | |
if args is None: | |
sys.exit(1) | |
device = NvmeDevice(args.device) | |
if device.kind is Ec2NvmeDeviceType.invalid: | |
err = "Device is NVMe but is neither EBS nor instance-store: '{}'" | |
print(err.format(str(device.path)), file=sys.stderr) | |
sys.exit(1) | |
show_both = not any([args.volume, args.block_dev_aliases, args.udev]) | |
if show_both or args.volume: | |
volume_id = device.volume_id | |
if volume_id is None: | |
print('Device {} is instance-store NVMe, ' | |
'no volume ID available'.format(str(device.path))) | |
else: | |
print('Volume ID: {}'.format(volume_id)) | |
if show_both or args.block_dev_aliases: | |
aliases = ' '.join(device.block_device_aliases) | |
print('Block Device Aliases: {}'.format(aliases)) | |
if args.udev: | |
if device.kind is Ec2NvmeDeviceType.instance_store: | |
dev = device.udev_aliases[0] | |
print('_ISTORE_ALIAS={}'.format(dev)) | |
elif device.kind is Ec2NvmeDeviceType.ebs: | |
print('_EBS_ALIAS_SD={}'.format(device.udev_aliases.sd)) | |
print('_EBS_ALIAS_XVD={}'.format(device.udev_aliases.xvd)) | |
if __name__ == '__main__': | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Copyright (C) 2006-2016 Amazon.com, Inc. or its affiliates. | |
# All Rights Reserved. | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"). | |
# You may not use this file except in compliance with the License. | |
# A copy of the License is located at | |
# | |
# http://aws.amazon.com/apache2.0/ | |
# | |
# or in the "license" file accompanying this file. This file is | |
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS | |
# OF ANY KIND, either express or implied. See the License for the | |
# specific language governing permissions and limitations under the | |
# License. | |
#Expected input if partition's kernel name like nvme0n1p2 | |
if (( $# == 0 )); then | |
exit 1 | |
fi | |
# extract ns id from partition's kernel name and export it | |
NSID=$(echo -n "$@" | cut -f 3 -d 'n' | cut -f 1 -d 'p') | |
echo "_NS_ID=${NSID}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Maintain consistent naming scheme with current EC2 devices | |
if (( $# != 1 )); then | |
echo "$0 <device>" >&2 | |
exit 1 | |
fi | |
if [[ $1 =~ xvd[a-z][0-9]? ]] | |
then | |
echo "${1/xvd/sd}" | |
else | |
echo "$1" | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment