| OLD | NEW |
| (Empty) | |
| 1 # Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/ |
| 2 # |
| 3 # Permission is hereby granted, free of charge, to any person obtaining a |
| 4 # copy of this software and associated documentation files (the |
| 5 # "Software"), to deal in the Software without restriction, including |
| 6 # without limitation the rights to use, copy, modify, merge, publish, dis- |
| 7 # tribute, sublicense, and/or sell copies of the Software, and to permit |
| 8 # persons to whom the Software is furnished to do so, subject to the fol- |
| 9 # lowing conditions: |
| 10 # |
| 11 # The above copyright notice and this permission notice shall be included |
| 12 # in all copies or substantial portions of the Software. |
| 13 # |
| 14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
| 16 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
| 17 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 18 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 20 # IN THE SOFTWARE. |
| 21 # |
| 22 """ |
| 23 Automated installer to attach, format and mount an EBS volume. |
| 24 This installer assumes that you want the volume formatted as |
| 25 an XFS file system. To drive this installer, you need the |
| 26 following section in the boto config passed to the new instance. |
| 27 You also need to install dateutil by listing python-dateutil |
| 28 in the list of packages to be installed in the Pyami seciont |
| 29 of your boto config file. |
| 30 |
| 31 If there is already a device mounted at the specified mount point, |
| 32 the installer assumes that it is the ephemeral drive and unmounts |
| 33 it, remounts it as /tmp and chmods it to 777. |
| 34 |
| 35 Config file section:: |
| 36 |
| 37 [EBS] |
| 38 volume_id = <the id of the EBS volume, should look like vol-xxxxxxxx> |
| 39 logical_volume_name = <the name of the logical volume that contaings |
| 40 a reference to the physical volume to be mounted. If this parameter |
| 41 is supplied, it overrides the volume_id setting.> |
| 42 device = <the linux device the EBS volume should be mounted on> |
| 43 mount_point = <directory to mount device, defaults to /ebs> |
| 44 |
| 45 """ |
| 46 import boto |
| 47 from boto.manage.volume import Volume |
| 48 from boto.exception import EC2ResponseError |
| 49 import os, time |
| 50 from boto.pyami.installers.ubuntu.installer import Installer |
| 51 from string import Template |
| 52 |
| 53 BackupScriptTemplate = """#!/usr/bin/env python |
| 54 # Backup EBS volume |
| 55 import boto |
| 56 from boto.pyami.scriptbase import ScriptBase |
| 57 import traceback |
| 58 |
| 59 class Backup(ScriptBase): |
| 60 |
| 61 def main(self): |
| 62 try: |
| 63 ec2 = boto.connect_ec2() |
| 64 self.run("/usr/sbin/xfs_freeze -f ${mount_point}", exit_on_error = T
rue) |
| 65 snapshot = ec2.create_snapshot('${volume_id}') |
| 66 boto.log.info("Snapshot created: %s " % snapshot) |
| 67 except Exception, e: |
| 68 self.notify(subject="${instance_id} Backup Failed", body=traceback.f
ormat_exc()) |
| 69 boto.log.info("Snapshot created: ${volume_id}") |
| 70 except Exception, e: |
| 71 self.notify(subject="${instance_id} Backup Failed", body=traceback.f
ormat_exc()) |
| 72 finally: |
| 73 self.run("/usr/sbin/xfs_freeze -u ${mount_point}") |
| 74 |
| 75 if __name__ == "__main__": |
| 76 b = Backup() |
| 77 b.main() |
| 78 """ |
| 79 |
| 80 BackupCleanupScript= """#!/usr/bin/env python |
| 81 import boto |
| 82 from boto.manage.volume import Volume |
| 83 |
| 84 # Cleans Backups of EBS volumes |
| 85 |
| 86 for v in Volume.all(): |
| 87 v.trim_snapshots(True) |
| 88 """ |
| 89 |
| 90 TagBasedBackupCleanupScript= """#!/usr/bin/env python |
| 91 import boto |
| 92 |
| 93 # Cleans Backups of EBS volumes |
| 94 |
| 95 ec2 = boto.connect_ec2() |
| 96 ec2.trim_snapshots() |
| 97 """ |
| 98 |
| 99 class EBSInstaller(Installer): |
| 100 """ |
| 101 Set up the EBS stuff |
| 102 """ |
| 103 |
| 104 def __init__(self, config_file=None): |
| 105 Installer.__init__(self, config_file) |
| 106 self.instance_id = boto.config.get('Instance', 'instance-id') |
| 107 self.device = boto.config.get('EBS', 'device', '/dev/sdp') |
| 108 self.volume_id = boto.config.get('EBS', 'volume_id') |
| 109 self.logical_volume_name = boto.config.get('EBS', 'logical_volume_name') |
| 110 self.mount_point = boto.config.get('EBS', 'mount_point', '/ebs') |
| 111 |
| 112 def attach(self): |
| 113 ec2 = boto.connect_ec2() |
| 114 if self.logical_volume_name: |
| 115 # if a logical volume was specified, override the specified volume_i
d |
| 116 # (if there was one) with the current AWS volume for the logical vol
ume: |
| 117 logical_volume = Volume.find(name = self.logical_volume_name).next() |
| 118 self.volume_id = logical_volume._volume_id |
| 119 volume = ec2.get_all_volumes([self.volume_id])[0] |
| 120 # wait for the volume to be available. The volume may still be being cre
ated |
| 121 # from a snapshot. |
| 122 while volume.update() != 'available': |
| 123 boto.log.info('Volume %s not yet available. Current status = %s.' %
(volume.id, volume.status)) |
| 124 time.sleep(5) |
| 125 instance = ec2.get_all_instances([self.instance_id])[0].instances[0] |
| 126 attempt_attach = True |
| 127 while attempt_attach: |
| 128 try: |
| 129 ec2.attach_volume(self.volume_id, self.instance_id, self.device) |
| 130 attempt_attach = False |
| 131 except EC2ResponseError, e: |
| 132 if e.error_code != 'IncorrectState': |
| 133 # if there's an EC2ResonseError with the code set to Incorre
ctState, delay a bit for ec2 |
| 134 # to realize the instance is running, then try again. Otherw
ise, raise the error: |
| 135 boto.log.info('Attempt to attach the EBS volume %s to this i
nstance (%s) returned %s. Trying again in a bit.' % (self.volume_id, self.instan
ce_id, e.errors)) |
| 136 time.sleep(2) |
| 137 else: |
| 138 raise e |
| 139 boto.log.info('Attached volume %s to instance %s as device %s' % (self.v
olume_id, self.instance_id, self.device)) |
| 140 # now wait for the volume device to appear |
| 141 while not os.path.exists(self.device): |
| 142 boto.log.info('%s still does not exist, waiting 2 seconds' % self.de
vice) |
| 143 time.sleep(2) |
| 144 |
| 145 def make_fs(self): |
| 146 boto.log.info('make_fs...') |
| 147 has_fs = self.run('fsck %s' % self.device) |
| 148 if has_fs != 0: |
| 149 self.run('mkfs -t xfs %s' % self.device) |
| 150 |
| 151 def create_backup_script(self): |
| 152 t = Template(BackupScriptTemplate) |
| 153 s = t.substitute(volume_id=self.volume_id, instance_id=self.instance_id, |
| 154 mount_point=self.mount_point) |
| 155 fp = open('/usr/local/bin/ebs_backup', 'w') |
| 156 fp.write(s) |
| 157 fp.close() |
| 158 self.run('chmod +x /usr/local/bin/ebs_backup') |
| 159 |
| 160 def create_backup_cleanup_script(self, use_tag_based_cleanup = False): |
| 161 fp = open('/usr/local/bin/ebs_backup_cleanup', 'w') |
| 162 if use_tag_based_cleanup: |
| 163 fp.write(TagBasedBackupCleanupScript) |
| 164 else: |
| 165 fp.write(BackupCleanupScript) |
| 166 fp.close() |
| 167 self.run('chmod +x /usr/local/bin/ebs_backup_cleanup') |
| 168 |
| 169 def handle_mount_point(self): |
| 170 boto.log.info('handle_mount_point') |
| 171 if not os.path.isdir(self.mount_point): |
| 172 boto.log.info('making directory') |
| 173 # mount directory doesn't exist so create it |
| 174 self.run("mkdir %s" % self.mount_point) |
| 175 else: |
| 176 boto.log.info('directory exists already') |
| 177 self.run('mount -l') |
| 178 lines = self.last_command.output.split('\n') |
| 179 for line in lines: |
| 180 t = line.split() |
| 181 if t and t[2] == self.mount_point: |
| 182 # something is already mounted at the mount point |
| 183 # unmount that and mount it as /tmp |
| 184 if t[0] != self.device: |
| 185 self.run('umount %s' % self.mount_point) |
| 186 self.run('mount %s /tmp' % t[0]) |
| 187 break |
| 188 self.run('chmod 777 /tmp') |
| 189 # Mount up our new EBS volume onto mount_point |
| 190 self.run("mount %s %s" % (self.device, self.mount_point)) |
| 191 self.run('xfs_growfs %s' % self.mount_point) |
| 192 |
| 193 def update_fstab(self): |
| 194 f = open("/etc/fstab", "a") |
| 195 f.write('%s\t%s\txfs\tdefaults 0 0\n' % (self.device, self.mount_point)) |
| 196 f.close() |
| 197 |
| 198 def install(self): |
| 199 # First, find and attach the volume |
| 200 self.attach() |
| 201 |
| 202 # Install the xfs tools |
| 203 self.run('apt-get -y install xfsprogs xfsdump') |
| 204 |
| 205 # Check to see if the filesystem was created or not |
| 206 self.make_fs() |
| 207 |
| 208 # create the /ebs directory for mounting |
| 209 self.handle_mount_point() |
| 210 |
| 211 # create the backup script |
| 212 self.create_backup_script() |
| 213 |
| 214 # Set up the backup script |
| 215 minute = boto.config.get('EBS', 'backup_cron_minute', '0') |
| 216 hour = boto.config.get('EBS', 'backup_cron_hour', '4,16') |
| 217 self.add_cron("ebs_backup", "/usr/local/bin/ebs_backup", minute=minute,
hour=hour) |
| 218 |
| 219 # Set up the backup cleanup script |
| 220 minute = boto.config.get('EBS', 'backup_cleanup_cron_minute') |
| 221 hour = boto.config.get('EBS', 'backup_cleanup_cron_hour') |
| 222 if (minute != None) and (hour != None): |
| 223 # Snapshot clean up can either be done via the manage module, or via
the new tag based |
| 224 # snapshot code, if the snapshots have been tagged with the name of
the associated |
| 225 # volume. Check for the presence of the new configuration flag, and
use the appropriate |
| 226 # cleanup method / script: |
| 227 use_tag_based_cleanup = boto.config.has_option('EBS', 'use_tag_based
_snapshot_cleanup') |
| 228 self.create_backup_cleanup_script(use_tag_based_cleanup); |
| 229 self.add_cron("ebs_backup_cleanup", "/usr/local/bin/ebs_backup_clean
up", minute=minute, hour=hour) |
| 230 |
| 231 # Set up the fstab |
| 232 self.update_fstab() |
| 233 |
| 234 def main(self): |
| 235 if not os.path.exists(self.device): |
| 236 self.install() |
| 237 else: |
| 238 boto.log.info("Device %s is already attached, skipping EBS Installer
" % self.device) |
| OLD | NEW |