1# Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/ 2# Copyright (c) 2010, Eucalyptus Systems, Inc. 3# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved 4# 5# Permission is hereby granted, free of charge, to any person obtaining a 6# copy of this software and associated documentation files (the 7# "Software"), to deal in the Software without restriction, including 8# without limitation the rights to use, copy, modify, merge, publish, dis- 9# tribute, sublicense, and/or sell copies of the Software, and to permit 10# persons to whom the Software is furnished to do so, subject to the fol- 11# lowing conditions: 12# 13# The above copyright notice and this permission notice shall be included 14# in all copies or substantial portions of the Software. 15# 16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 18# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22# IN THE SOFTWARE. 23 24""" 25Represents an EC2 Elastic Block Storage Volume 26""" 27from boto.resultset import ResultSet 28from boto.ec2.tag import Tag 29from boto.ec2.ec2object import TaggedEC2Object 30 31 32class Volume(TaggedEC2Object): 33 """ 34 Represents an EBS volume. 35 36 :ivar id: The unique ID of the volume. 37 :ivar create_time: The timestamp of when the volume was created. 38 :ivar status: The status of the volume. 39 :ivar size: The size (in GB) of the volume. 40 :ivar snapshot_id: The ID of the snapshot this volume was created 41 from, if applicable. 42 :ivar attach_data: An AttachmentSet object. 43 :ivar zone: The availability zone this volume is in. 44 :ivar type: The type of volume (standard or consistent-iops) 45 :ivar iops: If this volume is of type consistent-iops, this is 46 the number of IOPS provisioned (10-300). 47 :ivar encrypted: True if this volume is encrypted. 48 """ 49 50 def __init__(self, connection=None): 51 super(Volume, self).__init__(connection) 52 self.id = None 53 self.create_time = None 54 self.status = None 55 self.size = None 56 self.snapshot_id = None 57 self.attach_data = None 58 self.zone = None 59 self.type = None 60 self.iops = None 61 self.encrypted = None 62 63 def __repr__(self): 64 return 'Volume:%s' % self.id 65 66 def startElement(self, name, attrs, connection): 67 retval = super(Volume, self).startElement(name, attrs, connection) 68 if retval is not None: 69 return retval 70 if name == 'attachmentSet': 71 self.attach_data = AttachmentSet() 72 return self.attach_data 73 elif name == 'tagSet': 74 self.tags = ResultSet([('item', Tag)]) 75 return self.tags 76 else: 77 return None 78 79 def endElement(self, name, value, connection): 80 if name == 'volumeId': 81 self.id = value 82 elif name == 'createTime': 83 self.create_time = value 84 elif name == 'status': 85 if value != '': 86 self.status = value 87 elif name == 'size': 88 self.size = int(value) 89 elif name == 'snapshotId': 90 self.snapshot_id = value 91 elif name == 'availabilityZone': 92 self.zone = value 93 elif name == 'volumeType': 94 self.type = value 95 elif name == 'iops': 96 self.iops = int(value) 97 elif name == 'encrypted': 98 self.encrypted = (value.lower() == 'true') 99 else: 100 setattr(self, name, value) 101 102 def _update(self, updated): 103 self.__dict__.update(updated.__dict__) 104 105 def update(self, validate=False, dry_run=False): 106 """ 107 Update the data associated with this volume by querying EC2. 108 109 :type validate: bool 110 :param validate: By default, if EC2 returns no data about the 111 volume the update method returns quietly. If 112 the validate param is True, however, it will 113 raise a ValueError exception if no data is 114 returned from EC2. 115 """ 116 # Check the resultset since Eucalyptus ignores the volumeId param 117 unfiltered_rs = self.connection.get_all_volumes( 118 [self.id], 119 dry_run=dry_run 120 ) 121 rs = [x for x in unfiltered_rs if x.id == self.id] 122 if len(rs) > 0: 123 self._update(rs[0]) 124 elif validate: 125 raise ValueError('%s is not a valid Volume ID' % self.id) 126 return self.status 127 128 def delete(self, dry_run=False): 129 """ 130 Delete this EBS volume. 131 132 :rtype: bool 133 :return: True if successful 134 """ 135 return self.connection.delete_volume(self.id, dry_run=dry_run) 136 137 def attach(self, instance_id, device, dry_run=False): 138 """ 139 Attach this EBS volume to an EC2 instance. 140 141 :type instance_id: str 142 :param instance_id: The ID of the EC2 instance to which it will 143 be attached. 144 145 :type device: str 146 :param device: The device on the instance through which the 147 volume will be exposed (e.g. /dev/sdh) 148 149 :rtype: bool 150 :return: True if successful 151 """ 152 return self.connection.attach_volume( 153 self.id, 154 instance_id, 155 device, 156 dry_run=dry_run 157 ) 158 159 def detach(self, force=False, dry_run=False): 160 """ 161 Detach this EBS volume from an EC2 instance. 162 163 :type force: bool 164 :param force: Forces detachment if the previous detachment 165 attempt did not occur cleanly. This option can lead to 166 data loss or a corrupted file system. Use this option only 167 as a last resort to detach a volume from a failed 168 instance. The instance will not have an opportunity to 169 flush file system caches nor file system meta data. If you 170 use this option, you must perform file system check and 171 repair procedures. 172 173 :rtype: bool 174 :return: True if successful 175 """ 176 instance_id = None 177 if self.attach_data: 178 instance_id = self.attach_data.instance_id 179 device = None 180 if self.attach_data: 181 device = self.attach_data.device 182 return self.connection.detach_volume( 183 self.id, 184 instance_id, 185 device, 186 force, 187 dry_run=dry_run 188 ) 189 190 def create_snapshot(self, description=None, dry_run=False): 191 """ 192 Create a snapshot of this EBS Volume. 193 194 :type description: str 195 :param description: A description of the snapshot. 196 Limited to 256 characters. 197 198 :rtype: :class:`boto.ec2.snapshot.Snapshot` 199 :return: The created Snapshot object 200 """ 201 return self.connection.create_snapshot( 202 self.id, 203 description, 204 dry_run=dry_run 205 ) 206 207 def volume_state(self): 208 """ 209 Returns the state of the volume. Same value as the status attribute. 210 """ 211 return self.status 212 213 def attachment_state(self): 214 """ 215 Get the attachment state. 216 """ 217 state = None 218 if self.attach_data: 219 state = self.attach_data.status 220 return state 221 222 def snapshots(self, owner=None, restorable_by=None, dry_run=False): 223 """ 224 Get all snapshots related to this volume. Note that this requires 225 that all available snapshots for the account be retrieved from EC2 226 first and then the list is filtered client-side to contain only 227 those for this volume. 228 229 :type owner: str 230 :param owner: If present, only the snapshots owned by the 231 specified user will be returned. Valid values are: 232 233 * self 234 * amazon 235 * AWS Account ID 236 237 :type restorable_by: str 238 :param restorable_by: If present, only the snapshots that 239 are restorable by the specified account id will be returned. 240 241 :rtype: list of L{boto.ec2.snapshot.Snapshot} 242 :return: The requested Snapshot objects 243 244 """ 245 rs = self.connection.get_all_snapshots( 246 owner=owner, 247 restorable_by=restorable_by, 248 dry_run=dry_run 249 ) 250 mine = [] 251 for snap in rs: 252 if snap.volume_id == self.id: 253 mine.append(snap) 254 return mine 255 256 257class AttachmentSet(object): 258 """ 259 Represents an EBS attachmentset. 260 261 :ivar id: The unique ID of the volume. 262 :ivar instance_id: The unique ID of the attached instance 263 :ivar status: The status of the attachment 264 :ivar attach_time: Attached since 265 :ivar device: The device the instance has mapped 266 """ 267 def __init__(self): 268 self.id = None 269 self.instance_id = None 270 self.status = None 271 self.attach_time = None 272 self.device = None 273 274 def __repr__(self): 275 return 'AttachmentSet:%s' % self.id 276 277 def startElement(self, name, attrs, connection): 278 pass 279 280 def endElement(self, name, value, connection): 281 if name == 'volumeId': 282 self.id = value 283 elif name == 'instanceId': 284 self.instance_id = value 285 elif name == 'status': 286 self.status = value 287 elif name == 'attachTime': 288 self.attach_time = value 289 elif name == 'device': 290 self.device = value 291 else: 292 setattr(self, name, value) 293 294 295class VolumeAttribute(object): 296 def __init__(self, parent=None): 297 self.id = None 298 self._key_name = None 299 self.attrs = {} 300 301 def startElement(self, name, attrs, connection): 302 if name == 'autoEnableIO': 303 self._key_name = name 304 return None 305 306 def endElement(self, name, value, connection): 307 if name == 'value': 308 if value.lower() == 'true': 309 self.attrs[self._key_name] = True 310 else: 311 self.attrs[self._key_name] = False 312 elif name == 'volumeId': 313 self.id = value 314 else: 315 setattr(self, name, value) 316