From 638d2701c517c8712b4d3c9809b778a724f628a0 Mon Sep 17 00:00:00 2001 From: "John H. Pierce" Date: Tue, 10 Jul 2018 01:20:28 -0500 Subject: [PATCH 1/2] NVMe support for ec2 provider Fixes: #452 Adds support for building on EC2 hosts that have NVMe EBS devices. Introduces the DescribeInstances permission requirement for the calling role. Changes the device naming logic on the host during build time. - The new target volume will be mounted with the highest available DeviceName in the BlockDeviceMapping object, taking care to avoid assignments that also are allocated at launch to ephemeral devices (C5d, I3, F1, and M5d currently). - The system device name will be identifed by the difference between the existing block devices prior to and after the AttachVolume call has finished. This does not address the race condition identified in #459. --- bootstrapvz/providers/ec2/ebsvolume.py | 64 ++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/bootstrapvz/providers/ec2/ebsvolume.py b/bootstrapvz/providers/ec2/ebsvolume.py index 376cb7bce..2da30e4d2 100644 --- a/bootstrapvz/providers/ec2/ebsvolume.py +++ b/bootstrapvz/providers/ec2/ebsvolume.py @@ -1,5 +1,6 @@ from bootstrapvz.base.fs.volume import Volume from bootstrapvz.base.fs.exceptions import VolumeError +from bootstrapvz.common.tools import log_check_call class EBSVolume(Volume): @@ -33,19 +34,49 @@ def attach(self, instance_id): self.fsm.attach(instance_id=instance_id) def _before_attach(self, e): - import os.path + import os import string + import urllib2 + + def name_mapped(path): + return path.split('/')[-1].replace('xvd', 'sd')[:3] self.instance_id = e.instance_id - for letter in string.ascii_lowercase[5:]: - dev_path = os.path.join('/dev', 'xvd' + letter) - if not os.path.exists(dev_path): - self.device_path = dev_path - self.ec2_device_path = os.path.join('/dev', 'sd' + letter) + + dev_map_names = set() + launch_map_url = 'http://169.254.169.254/latest/meta-data/block-device-mapping/' + launch_map_response = urllib2.urlopen(url=launch_map_url, timeout=5) + for map_name in [d.strip() for d in launch_map_response.readlines()]: + dev_url = launch_map_url + map_name + dev_response = urllib2.urlopen(url=dev_url, timeout=5) + dev_map_names.add(name_mapped(dev_response.read().strip())) + + try: + instance = self.conn.describe_instances( + Filters=[ + {'Name': 'instance-id', 'Values': [self.instance_id]} + ] + )['Reservations'][0]['Instances'][0] + except (IndexError, KeyError): + raise VolumeError('Unable to fetch EC2 instance volume data') + + for mapped_dev in instance.get('BlockDeviceMappings', list()): + dev_map_names.add(name_mapped(mapped_dev['DeviceName'])) + + for letter in reversed(string.ascii_lowercase[1:]): + if 'sd' + letter not in dev_map_names: + self.ec2_device_path = '/dev/sd' + letter break - if self.device_path is None: - raise VolumeError('Unable to find a free block device path for mounting the bootstrap volume') + if self.ec2_device_path is None: + raise VolumeError('Unable to find a free block device mapping for bootstrap volume') + + self.device_path = None + + lsblk_command = ['lsblk', '--noheadings', '--list', '--nodeps', '--output', 'NAME'] + + lsblk_start = log_check_call(lsblk_command) + start_dev_names = set(lsblk_start) self.conn.attach_volume(VolumeId=self.vol_id, InstanceId=self.instance_id, @@ -54,6 +85,23 @@ def _before_attach(self, e): waiter.wait(VolumeIds=[self.vol_id], Filters=[{'Name': 'attachment.status', 'Values': ['attached']}]) + log_check_call(['udevadm', 'settle']) + + lsblk_end = log_check_call(lsblk_command) + end_dev_names = set(lsblk_end) + + if len(start_dev_names ^ end_dev_names) != 1: + raise VolumeError('Could not determine the device name for bootstrap volume') + + udev_name = (start_dev_names ^ end_dev_names).pop() + udev_path = log_check_call(['udevadm', 'info', + '--root', '--query=name', '--name', + udev_name]) + if len(udev_path) != 1 or not os.path.exists(udev_path[0]): + raise VolumeError('Could not find device path for bootstrap volume') + + self.device_path = udev_path[0] + def _before_detach(self, e): self.conn.detach_volume(VolumeId=self.vol_id, InstanceId=self.instance_id, From 0550e1e51f261e9caccc717bf23e6dee501f6693 Mon Sep 17 00:00:00 2001 From: John Pierce Date: Mon, 16 Jul 2018 23:35:07 -0500 Subject: [PATCH 2/2] Remove accidental import change --- bootstrapvz/providers/ec2/ebsvolume.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrapvz/providers/ec2/ebsvolume.py b/bootstrapvz/providers/ec2/ebsvolume.py index 2da30e4d2..6e7299872 100644 --- a/bootstrapvz/providers/ec2/ebsvolume.py +++ b/bootstrapvz/providers/ec2/ebsvolume.py @@ -34,7 +34,7 @@ def attach(self, instance_id): self.fsm.attach(instance_id=instance_id) def _before_attach(self, e): - import os + import os.path import string import urllib2