Skip to content

Instantly share code, notes, and snippets.

@bmoyles
Created March 19, 2019 17:29
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bmoyles/755ffd13847bb6a6b50fbe014962595f to your computer and use it in GitHub Desktop.
Save bmoyles/755ffd13847bb6a6b50fbe014962595f to your computer and use it in GitHub Desktop.
ec2 vbd and NVMe udev rules and helpers
# ensure any xen virtual block devices named xvdN get a sdN symlink for consistency
KERNEL=="xvd*", PROGRAM="/sbin/ec2udev-vbd %k", SYMLINK+="%c"
# 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"
#!/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()
#!/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}"
#!/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
@123BLiN
Copy link

123BLiN commented Nov 12, 2020

Thanks a lot!
Confirmed working for both ephemeral and EBS nvme volumes
To save some time for others, in order to get it working you should place files to:

  • *.rules --> /etc/udev/rules.d/*
  • scripts --> /lib/udev/*

in my case next symlinks were created:

lrwxrwxrwx  1 root root           7 Nov 12 15:08 ec2_ephemeral_nvme1 -> nvme1n1 #instance store
lrwxrwxrwx  1 root root           7 Nov 12 15:08 xvda1 -> nvme0n1 # root EBS volume
lrwxrwxrwx  1 root root           9 Nov 12 15:08 xvda11 -> nvme0n1p1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment