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 Instance 26""" 27import boto 28from boto.ec2.ec2object import EC2Object, TaggedEC2Object 29from boto.resultset import ResultSet 30from boto.ec2.address import Address 31from boto.ec2.blockdevicemapping import BlockDeviceMapping 32from boto.ec2.image import ProductCodes 33from boto.ec2.networkinterface import NetworkInterface 34from boto.ec2.group import Group 35import base64 36 37 38class InstanceState(object): 39 """ 40 The state of the instance. 41 42 :ivar code: The low byte represents the state. The high byte is an 43 opaque internal value and should be ignored. Valid values: 44 45 * 0 (pending) 46 * 16 (running) 47 * 32 (shutting-down) 48 * 48 (terminated) 49 * 64 (stopping) 50 * 80 (stopped) 51 52 :ivar name: The name of the state of the instance. Valid values: 53 54 * "pending" 55 * "running" 56 * "shutting-down" 57 * "terminated" 58 * "stopping" 59 * "stopped" 60 """ 61 def __init__(self, code=0, name=None): 62 self.code = code 63 self.name = name 64 65 def __repr__(self): 66 return '%s(%d)' % (self.name, self.code) 67 68 def startElement(self, name, attrs, connection): 69 pass 70 71 def endElement(self, name, value, connection): 72 if name == 'code': 73 self.code = int(value) 74 elif name == 'name': 75 self.name = value 76 else: 77 setattr(self, name, value) 78 79 80class InstancePlacement(object): 81 """ 82 The location where the instance launched. 83 84 :ivar zone: The Availability Zone of the instance. 85 :ivar group_name: The name of the placement group the instance is 86 in (for cluster compute instances). 87 :ivar tenancy: The tenancy of the instance (if the instance is 88 running within a VPC). An instance with a tenancy of dedicated 89 runs on single-tenant hardware. 90 """ 91 def __init__(self, zone=None, group_name=None, tenancy=None): 92 self.zone = zone 93 self.group_name = group_name 94 self.tenancy = tenancy 95 96 def __repr__(self): 97 return self.zone 98 99 def startElement(self, name, attrs, connection): 100 pass 101 102 def endElement(self, name, value, connection): 103 if name == 'availabilityZone': 104 self.zone = value 105 elif name == 'groupName': 106 self.group_name = value 107 elif name == 'tenancy': 108 self.tenancy = value 109 else: 110 setattr(self, name, value) 111 112 113class Reservation(EC2Object): 114 """ 115 Represents a Reservation response object. 116 117 :ivar id: The unique ID of the Reservation. 118 :ivar owner_id: The unique ID of the owner of the Reservation. 119 :ivar groups: A list of Group objects representing the security 120 groups associated with launched instances. 121 :ivar instances: A list of Instance objects launched in this 122 Reservation. 123 """ 124 def __init__(self, connection=None): 125 super(Reservation, self).__init__(connection) 126 self.id = None 127 self.owner_id = None 128 self.groups = [] 129 self.instances = [] 130 131 def __repr__(self): 132 return 'Reservation:%s' % self.id 133 134 def startElement(self, name, attrs, connection): 135 if name == 'instancesSet': 136 self.instances = ResultSet([('item', Instance)]) 137 return self.instances 138 elif name == 'groupSet': 139 self.groups = ResultSet([('item', Group)]) 140 return self.groups 141 else: 142 return None 143 144 def endElement(self, name, value, connection): 145 if name == 'reservationId': 146 self.id = value 147 elif name == 'ownerId': 148 self.owner_id = value 149 else: 150 setattr(self, name, value) 151 152 def stop_all(self, dry_run=False): 153 for instance in self.instances: 154 instance.stop(dry_run=dry_run) 155 156 157class Instance(TaggedEC2Object): 158 """ 159 Represents an instance. 160 161 :ivar id: The unique ID of the Instance. 162 :ivar groups: A list of Group objects representing the security 163 groups associated with the instance. 164 :ivar public_dns_name: The public dns name of the instance. 165 :ivar private_dns_name: The private dns name of the instance. 166 :ivar state: The string representation of the instance's current state. 167 :ivar state_code: An integer representation of the instance's 168 current state. 169 :ivar previous_state: The string representation of the instance's 170 previous state. 171 :ivar previous_state_code: An integer representation of the 172 instance's current state. 173 :ivar key_name: The name of the SSH key associated with the instance. 174 :ivar instance_type: The type of instance (e.g. m1.small). 175 :ivar launch_time: The time the instance was launched. 176 :ivar image_id: The ID of the AMI used to launch this instance. 177 :ivar placement: The availability zone in which the instance is running. 178 :ivar placement_group: The name of the placement group the instance 179 is in (for cluster compute instances). 180 :ivar placement_tenancy: The tenancy of the instance, if the instance 181 is running within a VPC. An instance with a tenancy of dedicated 182 runs on a single-tenant hardware. 183 :ivar kernel: The kernel associated with the instance. 184 :ivar ramdisk: The ramdisk associated with the instance. 185 :ivar architecture: The architecture of the image (i386|x86_64). 186 :ivar hypervisor: The hypervisor used. 187 :ivar virtualization_type: The type of virtualization used. 188 :ivar product_codes: A list of product codes associated with this instance. 189 :ivar ami_launch_index: This instances position within it's launch group. 190 :ivar monitored: A boolean indicating whether monitoring is enabled or not. 191 :ivar monitoring_state: A string value that contains the actual value 192 of the monitoring element returned by EC2. 193 :ivar spot_instance_request_id: The ID of the spot instance request 194 if this is a spot instance. 195 :ivar subnet_id: The VPC Subnet ID, if running in VPC. 196 :ivar vpc_id: The VPC ID, if running in VPC. 197 :ivar private_ip_address: The private IP address of the instance. 198 :ivar ip_address: The public IP address of the instance. 199 :ivar platform: Platform of the instance (e.g. Windows) 200 :ivar root_device_name: The name of the root device. 201 :ivar root_device_type: The root device type (ebs|instance-store). 202 :ivar block_device_mapping: The Block Device Mapping for the instance. 203 :ivar state_reason: The reason for the most recent state transition. 204 :ivar groups: List of security Groups associated with the instance. 205 :ivar interfaces: List of Elastic Network Interfaces associated with 206 this instance. 207 :ivar ebs_optimized: Whether instance is using optimized EBS volumes 208 or not. 209 :ivar instance_profile: A Python dict containing the instance 210 profile id and arn associated with this instance. 211 """ 212 213 def __init__(self, connection=None): 214 super(Instance, self).__init__(connection) 215 self.id = None 216 self.dns_name = None 217 self.public_dns_name = None 218 self.private_dns_name = None 219 self.key_name = None 220 self.instance_type = None 221 self.launch_time = None 222 self.image_id = None 223 self.kernel = None 224 self.ramdisk = None 225 self.product_codes = ProductCodes() 226 self.ami_launch_index = None 227 self.monitored = False 228 self.monitoring_state = None 229 self.spot_instance_request_id = None 230 self.subnet_id = None 231 self.vpc_id = None 232 self.private_ip_address = None 233 self.ip_address = None 234 self.requester_id = None 235 self._in_monitoring_element = False 236 self.persistent = False 237 self.root_device_name = None 238 self.root_device_type = None 239 self.block_device_mapping = None 240 self.state_reason = None 241 self.group_name = None 242 self.client_token = None 243 self.eventsSet = None 244 self.groups = [] 245 self.platform = None 246 self.interfaces = [] 247 self.hypervisor = None 248 self.virtualization_type = None 249 self.architecture = None 250 self.instance_profile = None 251 self._previous_state = None 252 self._state = InstanceState() 253 self._placement = InstancePlacement() 254 255 def __repr__(self): 256 return 'Instance:%s' % self.id 257 258 @property 259 def state(self): 260 return self._state.name 261 262 @property 263 def state_code(self): 264 return self._state.code 265 266 @property 267 def previous_state(self): 268 if self._previous_state: 269 return self._previous_state.name 270 return None 271 272 @property 273 def previous_state_code(self): 274 if self._previous_state: 275 return self._previous_state.code 276 return 0 277 278 @property 279 def placement(self): 280 return self._placement.zone 281 282 @property 283 def placement_group(self): 284 return self._placement.group_name 285 286 @property 287 def placement_tenancy(self): 288 return self._placement.tenancy 289 290 def startElement(self, name, attrs, connection): 291 retval = super(Instance, self).startElement(name, attrs, connection) 292 if retval is not None: 293 return retval 294 if name == 'monitoring': 295 self._in_monitoring_element = True 296 elif name == 'blockDeviceMapping': 297 self.block_device_mapping = BlockDeviceMapping() 298 return self.block_device_mapping 299 elif name == 'productCodes': 300 return self.product_codes 301 elif name == 'stateReason': 302 self.state_reason = SubParse('stateReason') 303 return self.state_reason 304 elif name == 'groupSet': 305 self.groups = ResultSet([('item', Group)]) 306 return self.groups 307 elif name == "eventsSet": 308 self.eventsSet = SubParse('eventsSet') 309 return self.eventsSet 310 elif name == 'networkInterfaceSet': 311 self.interfaces = ResultSet([('item', NetworkInterface)]) 312 return self.interfaces 313 elif name == 'iamInstanceProfile': 314 self.instance_profile = SubParse('iamInstanceProfile') 315 return self.instance_profile 316 elif name == 'currentState': 317 return self._state 318 elif name == 'previousState': 319 self._previous_state = InstanceState() 320 return self._previous_state 321 elif name == 'instanceState': 322 return self._state 323 elif name == 'placement': 324 return self._placement 325 return None 326 327 def endElement(self, name, value, connection): 328 if name == 'instanceId': 329 self.id = value 330 elif name == 'imageId': 331 self.image_id = value 332 elif name == 'dnsName' or name == 'publicDnsName': 333 self.dns_name = value # backwards compatibility 334 self.public_dns_name = value 335 elif name == 'privateDnsName': 336 self.private_dns_name = value 337 elif name == 'keyName': 338 self.key_name = value 339 elif name == 'amiLaunchIndex': 340 self.ami_launch_index = value 341 elif name == 'previousState': 342 self.previous_state = value 343 elif name == 'instanceType': 344 self.instance_type = value 345 elif name == 'rootDeviceName': 346 self.root_device_name = value 347 elif name == 'rootDeviceType': 348 self.root_device_type = value 349 elif name == 'launchTime': 350 self.launch_time = value 351 elif name == 'platform': 352 self.platform = value 353 elif name == 'kernelId': 354 self.kernel = value 355 elif name == 'ramdiskId': 356 self.ramdisk = value 357 elif name == 'state': 358 if self._in_monitoring_element: 359 self.monitoring_state = value 360 if value == 'enabled': 361 self.monitored = True 362 self._in_monitoring_element = False 363 elif name == 'spotInstanceRequestId': 364 self.spot_instance_request_id = value 365 elif name == 'subnetId': 366 self.subnet_id = value 367 elif name == 'vpcId': 368 self.vpc_id = value 369 elif name == 'privateIpAddress': 370 self.private_ip_address = value 371 elif name == 'ipAddress': 372 self.ip_address = value 373 elif name == 'requesterId': 374 self.requester_id = value 375 elif name == 'persistent': 376 if value == 'true': 377 self.persistent = True 378 else: 379 self.persistent = False 380 elif name == 'groupName': 381 if self._in_monitoring_element: 382 self.group_name = value 383 elif name == 'clientToken': 384 self.client_token = value 385 elif name == "eventsSet": 386 self.events = value 387 elif name == 'hypervisor': 388 self.hypervisor = value 389 elif name == 'virtualizationType': 390 self.virtualization_type = value 391 elif name == 'architecture': 392 self.architecture = value 393 elif name == 'ebsOptimized': 394 self.ebs_optimized = (value == 'true') 395 else: 396 setattr(self, name, value) 397 398 def _update(self, updated): 399 self.__dict__.update(updated.__dict__) 400 401 def update(self, validate=False, dry_run=False): 402 """ 403 Update the instance's state information by making a call to fetch 404 the current instance attributes from the service. 405 406 :type validate: bool 407 :param validate: By default, if EC2 returns no data about the 408 instance the update method returns quietly. If 409 the validate param is True, however, it will 410 raise a ValueError exception if no data is 411 returned from EC2. 412 """ 413 rs = self.connection.get_all_reservations([self.id], dry_run=dry_run) 414 if len(rs) > 0: 415 r = rs[0] 416 for i in r.instances: 417 if i.id == self.id: 418 self._update(i) 419 elif validate: 420 raise ValueError('%s is not a valid Instance ID' % self.id) 421 return self.state 422 423 def terminate(self, dry_run=False): 424 """ 425 Terminate the instance 426 """ 427 rs = self.connection.terminate_instances([self.id], dry_run=dry_run) 428 if len(rs) > 0: 429 self._update(rs[0]) 430 431 def stop(self, force=False, dry_run=False): 432 """ 433 Stop the instance 434 435 :type force: bool 436 :param force: Forces the instance to stop 437 438 :rtype: list 439 :return: A list of the instances stopped 440 """ 441 rs = self.connection.stop_instances([self.id], force, dry_run=dry_run) 442 if len(rs) > 0: 443 self._update(rs[0]) 444 445 def start(self, dry_run=False): 446 """ 447 Start the instance. 448 """ 449 rs = self.connection.start_instances([self.id], dry_run=dry_run) 450 if len(rs) > 0: 451 self._update(rs[0]) 452 453 def reboot(self, dry_run=False): 454 return self.connection.reboot_instances([self.id], dry_run=dry_run) 455 456 def get_console_output(self, dry_run=False): 457 """ 458 Retrieves the console output for the instance. 459 460 :rtype: :class:`boto.ec2.instance.ConsoleOutput` 461 :return: The console output as a ConsoleOutput object 462 """ 463 return self.connection.get_console_output(self.id, dry_run=dry_run) 464 465 def confirm_product(self, product_code, dry_run=False): 466 return self.connection.confirm_product_instance( 467 self.id, 468 product_code, 469 dry_run=dry_run 470 ) 471 472 def use_ip(self, ip_address, dry_run=False): 473 """ 474 Associates an Elastic IP to the instance. 475 476 :type ip_address: Either an instance of 477 :class:`boto.ec2.address.Address` or a string. 478 :param ip_address: The IP address to associate 479 with the instance. 480 481 :rtype: bool 482 :return: True if successful 483 """ 484 485 if isinstance(ip_address, Address): 486 ip_address = ip_address.public_ip 487 return self.connection.associate_address( 488 self.id, 489 ip_address, 490 dry_run=dry_run 491 ) 492 493 def monitor(self, dry_run=False): 494 return self.connection.monitor_instance(self.id, dry_run=dry_run) 495 496 def unmonitor(self, dry_run=False): 497 return self.connection.unmonitor_instance(self.id, dry_run=dry_run) 498 499 def get_attribute(self, attribute, dry_run=False): 500 """ 501 Gets an attribute from this instance. 502 503 :type attribute: string 504 :param attribute: The attribute you need information about 505 Valid choices are: 506 507 * instanceType 508 * kernel 509 * ramdisk 510 * userData 511 * disableApiTermination 512 * instanceInitiatedShutdownBehavior 513 * rootDeviceName 514 * blockDeviceMapping 515 * productCodes 516 * sourceDestCheck 517 * groupSet 518 * ebsOptimized 519 520 :rtype: :class:`boto.ec2.image.InstanceAttribute` 521 :return: An InstanceAttribute object representing the value of the 522 attribute requested 523 """ 524 return self.connection.get_instance_attribute( 525 self.id, 526 attribute, 527 dry_run=dry_run 528 ) 529 530 def modify_attribute(self, attribute, value, dry_run=False): 531 """ 532 Changes an attribute of this instance 533 534 :type attribute: string 535 :param attribute: The attribute you wish to change. 536 537 * instanceType - A valid instance type (m1.small) 538 * kernel - Kernel ID (None) 539 * ramdisk - Ramdisk ID (None) 540 * userData - Base64 encoded String (None) 541 * disableApiTermination - Boolean (true) 542 * instanceInitiatedShutdownBehavior - stop|terminate 543 * sourceDestCheck - Boolean (true) 544 * groupSet - Set of Security Groups or IDs 545 * ebsOptimized - Boolean (false) 546 547 :type value: string 548 :param value: The new value for the attribute 549 550 :rtype: bool 551 :return: Whether the operation succeeded or not 552 """ 553 return self.connection.modify_instance_attribute( 554 self.id, 555 attribute, 556 value, 557 dry_run=dry_run 558 ) 559 560 def reset_attribute(self, attribute, dry_run=False): 561 """ 562 Resets an attribute of this instance to its default value. 563 564 :type attribute: string 565 :param attribute: The attribute to reset. Valid values are: 566 kernel|ramdisk 567 568 :rtype: bool 569 :return: Whether the operation succeeded or not 570 """ 571 return self.connection.reset_instance_attribute( 572 self.id, 573 attribute, 574 dry_run=dry_run 575 ) 576 577 def create_image(self, name, description=None, no_reboot=False, 578 dry_run=False): 579 """ 580 Will create an AMI from the instance in the running or stopped 581 state. 582 583 :type name: string 584 :param name: The name of the new image 585 586 :type description: string 587 :param description: An optional human-readable string describing 588 the contents and purpose of the AMI. 589 590 :type no_reboot: bool 591 :param no_reboot: An optional flag indicating that the bundling process 592 should not attempt to shutdown the instance before 593 bundling. If this flag is True, the responsibility 594 of maintaining file system integrity is left to the 595 owner of the instance. 596 597 :rtype: string 598 :return: The new image id 599 """ 600 return self.connection.create_image( 601 self.id, 602 name, 603 description, 604 no_reboot, 605 dry_run=dry_run 606 ) 607 608 609class ConsoleOutput(object): 610 def __init__(self, parent=None): 611 self.parent = parent 612 self.instance_id = None 613 self.timestamp = None 614 self.output = None 615 616 def startElement(self, name, attrs, connection): 617 return None 618 619 def endElement(self, name, value, connection): 620 if name == 'instanceId': 621 self.instance_id = value 622 elif name == 'timestamp': 623 self.timestamp = value 624 elif name == 'output': 625 self.output = base64.b64decode(value) 626 else: 627 setattr(self, name, value) 628 629 630class InstanceAttribute(dict): 631 ValidValues = ['instanceType', 'kernel', 'ramdisk', 'userData', 632 'disableApiTermination', 633 'instanceInitiatedShutdownBehavior', 634 'rootDeviceName', 'blockDeviceMapping', 'sourceDestCheck', 635 'groupSet'] 636 637 def __init__(self, parent=None): 638 dict.__init__(self) 639 self.instance_id = None 640 self.request_id = None 641 self._current_value = None 642 643 def startElement(self, name, attrs, connection): 644 if name == 'blockDeviceMapping': 645 self[name] = BlockDeviceMapping() 646 return self[name] 647 elif name == 'groupSet': 648 self[name] = ResultSet([('item', Group)]) 649 return self[name] 650 else: 651 return None 652 653 def endElement(self, name, value, connection): 654 if name == 'instanceId': 655 self.instance_id = value 656 elif name == 'requestId': 657 self.request_id = value 658 elif name == 'value': 659 if value == 'true': 660 value = True 661 elif value == 'false': 662 value = False 663 self._current_value = value 664 elif name in self.ValidValues: 665 self[name] = self._current_value 666 667 668class SubParse(dict): 669 def __init__(self, section, parent=None): 670 dict.__init__(self) 671 self.section = section 672 673 def startElement(self, name, attrs, connection): 674 return None 675 676 def endElement(self, name, value, connection): 677 if name != self.section: 678 self[name] = value 679