EC2 provider can now bootstrap EBS volumes again

Use tasksets in EC2 provider
This commit is contained in:
Anders Ingemann 2013-10-09 00:09:34 +02:00
parent 754e414742
commit e1ab4dc1ae
13 changed files with 119 additions and 111 deletions

View file

@ -2,7 +2,7 @@
def load_volume(data): def load_volume(data):
from common.fs.loopbackvolume import LoopbackVolume from common.fs.loopbackvolume import LoopbackVolume
from providers.ec2.volume import EBSVolume from providers.ec2.ebsvolume import EBSVolume
from providers.virtualbox.volume import VirtualBoxVolume from providers.virtualbox.volume import VirtualBoxVolume
from partitionmaps.gpt import GPTPartitionMap from partitionmaps.gpt import GPTPartitionMap
from partitionmaps.mbr import MBRPartitionMap from partitionmaps.mbr import MBRPartitionMap

View file

@ -1,5 +1,4 @@
from abc import ABCMeta from abc import ABCMeta
from abc import abstractmethod
from common.tools import log_check_call from common.tools import log_check_call
from common.fsm_proxy import FSMProxy from common.fsm_proxy import FSMProxy
@ -26,13 +25,6 @@ class AbstractPartition(FSMProxy):
[uuid] = log_check_call(['/sbin/blkid', '-s', 'UUID', '-o', 'value', self.device_path]) [uuid] = log_check_call(['/sbin/blkid', '-s', 'UUID', '-o', 'value', self.device_path])
return uuid return uuid
def create(self, volume):
self.fsm.create(volume=volume)
@abstractmethod
def _before_create(self, e):
pass
def _before_format(self, e): def _before_format(self, e):
mkfs = '/sbin/mkfs.{fs}'.format(fs=self.filesystem) mkfs = '/sbin/mkfs.{fs}'.format(fs=self.filesystem)
log_check_call([mkfs, self.device_path]) log_check_call([mkfs, self.device_path])

View file

@ -18,6 +18,9 @@ class BasePartition(AbstractPartition):
self.previous = previous self.previous = previous
super(BasePartition, self).__init__(size, filesystem) super(BasePartition, self).__init__(size, filesystem)
def create(self, volume):
self.fsm.create(volume=volume)
def get_index(self): def get_index(self):
if self.previous is None: if self.previous is None:
return 1 return 1

View file

@ -2,6 +2,4 @@ from abstract import AbstractPartition
class SinglePartition(AbstractPartition): class SinglePartition(AbstractPartition):
def _before_create(self, e):
pass pass

View file

@ -1,6 +1,7 @@
from common.tasks import workspace from common.tasks import workspace
from common.tasks import packages from common.tasks import packages
from common.tasks import host from common.tasks import host
from common.tasks import bootstrap
from common.tasks import volume from common.tasks import volume
from common.tasks import filesystem from common.tasks import filesystem
from common.tasks import partitioning from common.tasks import partitioning
@ -13,11 +14,14 @@ base_set = [workspace.CreateWorkspace,
packages.HostPackages, packages.HostPackages,
packages.ImagePackages, packages.ImagePackages,
host.CheckPackages, host.CheckPackages,
bootstrap.Bootstrap,
workspace.DeleteWorkspace, workspace.DeleteWorkspace,
] ]
volume_set = [volume.Attach, volume_set = [volume.Attach,
volume.Detach, volume.Detach,
filesystem.Format,
filesystem.FStab,
] ]
partitioning_set = [partitioning.PartitionVolume, partitioning_set = [partitioning.PartitionVolume,

View file

@ -1,18 +1,15 @@
from manifest import Manifest from manifest import Manifest
import logging import logging
from tasks import packages from tasks import packages
from common.tasks import packages as common_packages
from tasks import connection from tasks import connection
from tasks import host from tasks import host
from common.tasks import host as common_host
from tasks import ami from tasks import ami
from common.tasks import volume from common.tasks import volume as volume_tasks
from tasks import ebs from tasks import ebs
from common.tasks import partitioning
from common.tasks import loopback from common.tasks import loopback
from common.tasks import filesystem from common.tasks import filesystem
from common.tasks import bootstrap from common.tasks import bootstrap
from common.tasks import locale
from common.tasks import apt
from tasks import boot from tasks import boot
from common.tasks import boot as common_boot from common.tasks import boot as common_boot
from common.tasks import security from common.tasks import security
@ -21,6 +18,7 @@ from common.tasks import network as common_network
from tasks import initd from tasks import initd
from common.tasks import initd as common_initd from common.tasks import initd as common_initd
from common.tasks import cleanup from common.tasks import cleanup
from common.tasks import workspace
def initialize(): def initialize():
@ -29,74 +27,66 @@ def initialize():
def tasks(tasklist, manifest): def tasks(tasklist, manifest):
tasklist.add(packages.HostPackages(), from common.task_sets import base_set
common_packages.HostPackages(), from common.task_sets import mounting_set
packages.ImagePackages(), from common.task_sets import apt_set
common_packages.ImagePackages(), from common.task_sets import locale_set
common_host.CheckPackages(), from common.task_sets import ssh_set
connection.GetCredentials(), tasklist.add(*base_set)
host.GetInfo(), tasklist.add(*mounting_set)
ami.AMIName(), tasklist.add(*apt_set)
connection.Connect(), tasklist.add(*locale_set)
tasklist.add(*ssh_set)
filesystem.FormatVolume(), if manifest.volume['partitions']['type'] != 'none':
filesystem.CreateMountDir(), from common.task_sets import partitioning_set
filesystem.MountVolume(), tasklist.add(*partitioning_set)
bootstrap.Bootstrap(), tasklist.add(packages.HostPackages,
filesystem.MountSpecials(), packages.ImagePackages,
locale.GenerateLocale(), connection.GetCredentials,
locale.SetTimezone(), host.GetInfo,
apt.DisableDaemonAutostart(), ami.AMIName,
apt.AptSources(), connection.Connect,
apt.AptUpgrade(),
boot.ConfigureGrub(),
filesystem.FStab(),
common_boot.BlackListModules(),
common_boot.DisableGetTTYs(),
security.EnableShadowConfig(),
security.DisableSSHPasswordAuthentication(),
security.DisableSSHDNSLookup(),
common_network.RemoveDNSInfo(),
common_network.ConfigureNetworkIF(),
network.EnableDHCPCDDNS(),
common_initd.ResolveInitScripts(),
initd.AddEC2InitScripts(),
common_initd.InstallInitScripts(),
cleanup.ClearMOTD(),
cleanup.ShredHostkeys(),
cleanup.CleanTMP(),
apt.PurgeUnusedPackages(),
apt.AptClean(),
apt.EnableDaemonAutostart(),
filesystem.UnmountSpecials(),
filesystem.UnmountVolume(), boot.ConfigureGrub,
filesystem.DeleteMountDir(), common_boot.BlackListModules,
ami.RegisterAMI()) common_boot.DisableGetTTYs,
security.EnableShadowConfig,
common_network.RemoveDNSInfo,
common_network.ConfigureNetworkIF,
network.EnableDHCPCDDNS,
common_initd.ResolveInitScripts,
initd.AddEC2InitScripts,
common_initd.InstallInitScripts,
cleanup.ClearMOTD,
cleanup.CleanTMP,
ami.RegisterAMI)
backing_specific_tasks = {'ebs': [ebs.Create,
ebs.Attach,
ebs.Snapshot],
's3': [loopback.Create,
volume_tasks.Attach,
ami.BundleImage,
ami.UploadImage,
ami.RemoveBundle]}
tasklist.add(*backing_specific_tasks.get(manifest.volume['backing'].lower()))
tasklist.add(filesystem.Format,
filesystem.FStab,
volume_tasks.Detach,
volume_tasks.Delete)
if manifest.bootstrapper.get('tarball', False): if manifest.bootstrapper.get('tarball', False):
tasklist.add(bootstrap.MakeTarball()) tasklist.add(bootstrap.MakeTarball)
backing_specific_tasks = {'ebs': [ebs.Create(), from common.task_sets import get_fs_specific_set
volume.Attach(), tasklist.add(*get_fs_specific_set(manifest.volume['partitions']))
volume.Detach(),
ebs.Snapshot(),
volume.Delete()],
's3': [loopback.Create(),
volume.Attach(),
volume.Detach(),
ami.BundleImage(),
ami.UploadImage(),
volume.Delete(),
ami.RemoveBundle()]}
tasklist.add(*backing_specific_tasks.get(manifest.volume['backing'].lower()))
filesystem_specific_tasks = {'xfs': [filesystem.AddXFSProgs()], if 'boot' in manifest.volume['partitions']:
'ext2': [filesystem.TuneVolumeFS()], from common.task_sets import boot_partition_set
'ext3': [filesystem.TuneVolumeFS()], tasklist.add(*boot_partition_set)
'ext4': [filesystem.TuneVolumeFS()]}
tasklist.add(*filesystem_specific_tasks.get(manifest.volume['filesystem'].lower()))
def rollback_tasks(tasklist, tasks_completed, manifest): def rollback_tasks(tasklist, tasks_completed, manifest):
@ -104,14 +94,19 @@ def rollback_tasks(tasklist, tasks_completed, manifest):
def counter_task(task, counter): def counter_task(task, counter):
if task in completed and counter not in completed: if task in completed and counter not in completed:
tasklist.add(counter()) tasklist.add(counter)
if manifest.volume['backing'].lower() == 'ebs': counter_task(ebs.Create, volume_tasks.Delete)
counter_task(ebs.Create, volume.Delete) counter_task(ebs.Attach, volume_tasks.Detach)
counter_task(volume.Attach, volume.Detach)
if manifest.volume['backing'].lower() == 's3': counter_task(loopback.Create, volume_tasks.Delete)
counter_task(loopback.Create, volume.Delete) counter_task(volume_tasks.Attach, volume_tasks.Detach)
counter_task(volume.Attach, volume.Detach)
counter_task(partitioning.MapPartitions, partitioning.UnmapPartitions)
counter_task(filesystem.CreateMountDir, filesystem.DeleteMountDir) counter_task(filesystem.CreateMountDir, filesystem.DeleteMountDir)
counter_task(filesystem.MountVolume, filesystem.UnmountVolume)
counter_task(filesystem.MountSpecials, filesystem.UnmountSpecials) counter_task(filesystem.MountSpecials, filesystem.UnmountSpecials)
counter_task(filesystem.MountRoot, filesystem.UnmountRoot)
counter_task(filesystem.MountBoot, filesystem.UnmountBoot)
counter_task(volume_tasks.Attach, volume_tasks.Detach)
counter_task(workspace.CreateWorkspace, workspace.DeleteWorkspace)

View file

@ -5,18 +5,18 @@ import time
class EBSVolume(Volume): class EBSVolume(Volume):
volume = None
def create(self, conn, zone): def create(self, conn, zone):
super(EBSVolume, self).create(self) self.fsm.create(connection=conn, zone=zone)
def _before_create(self, e):
conn = e.connection
zone = e.zone
import math import math
# TODO: Warn if volume size is not a multiple of 1024 size = int(math.ceil(self.partition_map.get_total_size() / 1024))
size = int(math.ceil(self.partition_map.get_volume_size() / 1024))
self.volume = conn.create_volume(size, zone) self.volume = conn.create_volume(size, zone)
while self.volume.volume_state() != 'available': while self.volume.volume_state() != 'available':
time.sleep(5) time.sleep(5)
self.volume.update() self.volume.update()
self.created = True
def attach(self, instance_id): def attach(self, instance_id):
self.fsm.attach(instance_id=instance_id) self.fsm.attach(instance_id=instance_id)
@ -25,7 +25,7 @@ class EBSVolume(Volume):
instance_id = e.instance_id instance_id = e.instance_id
import os.path import os.path
import string import string
for letter in string.ascii_lowercase: for letter in string.ascii_lowercase[5:]:
dev_path = os.path.join('/dev', 'xvd' + letter) dev_path = os.path.join('/dev', 'xvd' + letter)
if not os.path.exists(dev_path): if not os.path.exists(dev_path):
self.device_path = dev_path self.device_path = dev_path
@ -47,7 +47,6 @@ class EBSVolume(Volume):
self.volume.update() self.volume.update()
def _before_delete(self, e): def _before_delete(self, e):
super(EBSVolume, self).delete(self)
self.volume.delete() self.volume.delete()
def snapshot(self): def snapshot(self):

View file

@ -20,13 +20,9 @@
"backing": { "backing": {
"type": "string", "type": "string",
"enum": ["ebs", "s3"] "enum": ["ebs", "s3"]
},
"filesystem": {
"type": "string",
"enum": ["ext2", "ext3", "ext4", "xfs"]
} }
}, },
"required": ["backing", "filesystem"] "required": ["backing"]
} }
}, },
"required": ["volume"] "required": ["volume"]

View file

@ -9,7 +9,8 @@ class Manifest(base.Manifest):
schema_path = path.join(path.dirname(__file__), 'manifest-schema.json') schema_path = path.join(path.dirname(__file__), 'manifest-schema.json')
self.schema_validate(data, schema_path) self.schema_validate(data, schema_path)
if data['volume']['backing'] == 'ebs': if data['volume']['backing'] == 'ebs':
if data['volume']['size'] % 1024 != 0: volume_size = self._calculate_volume_size(data['volume']['partitions'])
if volume_size % 1024 != 0:
msg = 'The volume size must be a multiple of 1024 when using EBS backing' msg = 'The volume size must be a multiple of 1024 when using EBS backing'
raise ManifestError(msg, self) raise ManifestError(msg, self)
else: else:
@ -21,5 +22,15 @@ class Manifest(base.Manifest):
self.credentials = data['credentials'] self.credentials = data['credentials']
self.virtualization = data['virtualization'] self.virtualization = data['virtualization']
self.image = data['image'] self.image = data['image']
if data['volume']['backing'] == 'ebs':
self.ebs_volume_size = data['volume']['size'] / 1024 def _calculate_volume_size(self, partitions):
if partitions['type'] == 'mbr':
size = 1
else:
size = 0
if 'boot' in partitions:
size += partitions['boot']['size']
size += partitions['root']['size']
if 'swap' in partitions:
size += partitions['swap']['size']
return size

View file

@ -117,11 +117,14 @@ class RegisterAMI(Task):
arch = {'i386': 'i386', 'amd64': 'x86_64'}.get(info.manifest.system['architecture']) arch = {'i386': 'i386', 'amd64': 'x86_64'}.get(info.manifest.system['architecture'])
kernel_id = self.kernel_mapping.get(info.host['region']).get(info.manifest.system['architecture']) kernel_id = self.kernel_mapping.get(info.host['region']).get(info.manifest.system['architecture'])
if info.manifest.volume['backing'] == 'ebs': from providers.ec2.ebsvolume import EBSVolume
from common.fs.loopbackvolume import LoopbackVolume
if isinstance(info.volume, EBSVolume):
from boto.ec2.blockdevicemapping import BlockDeviceType from boto.ec2.blockdevicemapping import BlockDeviceType
from boto.ec2.blockdevicemapping import BlockDeviceMapping from boto.ec2.blockdevicemapping import BlockDeviceMapping
block_device = BlockDeviceType(snapshot_id=info.snapshot.id, delete_on_termination=True, block_device = BlockDeviceType(snapshot_id=info.snapshot.id, delete_on_termination=True,
size=info.manifest.ebs_volume_size) size=info.volume.partition_map.get_total_size()/1024)
block_device_map = BlockDeviceMapping() block_device_map = BlockDeviceMapping()
block_device_map['/dev/sda1'] = block_device block_device_map['/dev/sda1'] = block_device
@ -129,7 +132,7 @@ class RegisterAMI(Task):
architecture=arch, kernel_id=kernel_id, architecture=arch, kernel_id=kernel_id,
root_device_name='/dev/sda1', root_device_name='/dev/sda1',
block_device_map=block_device_map) block_device_map=block_device_map)
if info.manifest.volume['backing'] == 's3': if isinstance(info.volume, LoopbackVolume):
image_location = ('{bucket}/{ami_name}.manifest.xml' image_location = ('{bucket}/{ami_name}.manifest.xml'
.format(bucket=info.manifest.image['bucket'], .format(bucket=info.manifest.image['bucket'],
ami_name=info.ami_name)) ami_name=info.ami_name))

View file

@ -1,17 +1,24 @@
from base import Task from base import Task
from common import phases from common import phases
from common.tasks import volume
class Create(Task): class Create(Task):
description = 'Creating the EBS volume' description = 'Creating the EBS volume'
phase = phases.volume_creation phase = phases.volume_creation
before = [volume.Attach]
def run(self, info): def run(self, info):
info.volume.create(info.connection, info.host['availabilityZone']) info.volume.create(info.connection, info.host['availabilityZone'])
class Attach(Task):
description = 'Attaching the volume'
phase = phases.volume_creation
after = [Create]
def run(self, info):
info.volume.attach(info.host['instanceId'])
class Snapshot(Task): class Snapshot(Task):
description = 'Creating a snapshot of the EBS volume' description = 'Creating a snapshot of the EBS volume'
phase = phases.image_registration phase = phases.image_registration

View file

@ -11,8 +11,10 @@ class HostPackages(Task):
after = [packages.HostPackages] after = [packages.HostPackages]
def run(self, info): def run(self, info):
if info.manifest.volume['filesystem'] == 'xfs': for partition in info.volume.partition_map.partitions:
info.host_packages.add('xfsprogs') if partition.filesystem == 'xfs':
info.host_packages.add('xfsprogs')
break
if info.manifest.volume['backing'] == 's3': if info.manifest.volume['backing'] == 's3':
info.host_packages.add('euca2ools') info.host_packages.add('euca2ools')
@ -25,6 +27,7 @@ class ImagePackages(Task):
def run(self, info): def run(self, info):
manifest = info.manifest manifest = info.manifest
include, exclude = info.img_packages include, exclude = info.img_packages
include.add('openssh-server')
include.add('file') # Needed for the init scripts include.add('file') # Needed for the init scripts
include.add('dhcpcd') # isc-dhcp-client doesn't work properly with ec2 include.add('dhcpcd') # isc-dhcp-client doesn't work properly with ec2
if manifest.virtualization == 'pvm': if manifest.virtualization == 'pvm':

View file

@ -38,11 +38,8 @@ def tasks(tasklist, manifest):
packages.ImagePackages, packages.ImagePackages,
loopback.Create, loopback.Create,
filesystem.Format,
bootstrap.Bootstrap,
boot.ConfigureGrub, boot.ConfigureGrub,
filesystem.FStab,
common_boot.BlackListModules, common_boot.BlackListModules,
common_boot.DisableGetTTYs, common_boot.DisableGetTTYs,
security.EnableShadowConfig, security.EnableShadowConfig,