Example #1
0
 def _parse_block_device_mappings(self):
     block_device_map = BlockDeviceMapping()
     for mapping in self.block_device_mapping_dict:
         block_type = BlockDeviceType()
         mount_point = mapping.get("device_name")
         if "ephemeral" in mapping.get("virtual_name", ""):
             block_type.ephemeral_name = mapping.get("virtual_name")
         else:
             block_type.volume_type = mapping.get("ebs._volume_type")
             block_type.snapshot_id = mapping.get("ebs._snapshot_id")
             block_type.delete_on_termination = mapping.get(
                 "ebs._delete_on_termination")
             block_type.size = mapping.get("ebs._volume_size")
             block_type.iops = mapping.get("ebs._iops")
         block_device_map[mount_point] = block_type
     return block_device_map
Example #2
0
 def _parse_block_device_mappings(self):
     block_device_map = BlockDeviceMapping()
     for mapping in self.block_device_mapping_dict:
         block_type = BlockDeviceType()
         mount_point = mapping.get("DeviceName")
         if mapping.get("VirtualName") and "ephemeral" in mapping.get("VirtualName"):
             block_type.ephemeral_name = mapping.get("VirtualName")
         elif mapping.get("NoDevice", "false") == "true":
             block_type.no_device = "true"
         else:
             ebs = mapping.get("Ebs", {})
             block_type.volume_type = ebs.get("VolumeType")
             block_type.snapshot_id = ebs.get("SnapshotId")
             block_type.delete_on_termination = ebs.get("DeleteOnTermination")
             block_type.size = ebs.get("VolumeSize")
             block_type.iops = ebs.get("Iops")
             block_type.throughput = ebs.get("Throughput")
             block_type.encrypted = ebs.get("Encrypted")
         block_device_map[mount_point] = block_type
     return block_device_map
Example #3
0
    def __init__(self, ec2_backend, image_id, user_data, security_groups,
                 **kwargs):
        super().__init__()
        self.ec2_backend = ec2_backend
        self.id = random_instance_id()
        self.owner_id = get_account_id()
        self.lifecycle = kwargs.get("lifecycle")

        nics = kwargs.get("nics", {})

        launch_template_arg = kwargs.get("launch_template", {})
        if launch_template_arg and not image_id:
            # the image id from the template should be used
            template_version = ec2_backend._get_template_from_args(
                launch_template_arg)
            self.image_id = template_version.image_id
        else:
            self.image_id = image_id
        # Check if we have tags to process
        if launch_template_arg:
            template_version = ec2_backend._get_template_from_args(
                launch_template_arg)
            tag_spec_set = template_version.data.get("TagSpecification", {})
            tags = convert_tag_spec(tag_spec_set)
            instance_tags = tags.get("instance", {})
            self.add_tags(instance_tags)

        self._state = InstanceState("running", 16)
        self._reason = ""
        self._state_reason = StateReason()
        self.user_data = user_data
        self.security_groups = security_groups
        self.instance_type = kwargs.get("instance_type", "m1.small")
        self.region_name = kwargs.get("region_name", "us-east-1")
        placement = kwargs.get("placement", None)
        self.subnet_id = kwargs.get("subnet_id")
        if not self.subnet_id:
            self.subnet_id = next(
                (n["SubnetId"] for n in nics if "SubnetId" in n), None)
        in_ec2_classic = not bool(self.subnet_id)
        self.key_name = kwargs.get("key_name")
        self.ebs_optimized = kwargs.get("ebs_optimized", False)
        self.source_dest_check = "true"
        self.launch_time = utc_date_and_time()
        self.ami_launch_index = kwargs.get("ami_launch_index", 0)
        self.disable_api_termination = kwargs.get("disable_api_termination",
                                                  False)
        self.instance_initiated_shutdown_behavior = (
            kwargs.get("instance_initiated_shutdown_behavior") or "stop")
        self.sriov_net_support = "simple"
        self._spot_fleet_id = kwargs.get("spot_fleet_id", None)
        self.associate_public_ip = kwargs.get("associate_public_ip", False)
        if in_ec2_classic:
            # If we are in EC2-Classic, autoassign a public IP
            self.associate_public_ip = True

        amis = self.ec2_backend.describe_images(
            filters={"image-id": self.image_id})
        ami = amis[0] if amis else None
        if ami is None:
            warnings.warn(
                "Could not find AMI with image-id:{0}, "
                "in the near future this will "
                "cause an error.\n"
                "Use ec2_backend.describe_images() to "
                "find suitable image for your test".format(self.image_id),
                PendingDeprecationWarning,
            )

        self.platform = ami.platform if ami else None
        self.virtualization_type = ami.virtualization_type if ami else "paravirtual"
        self.architecture = ami.architecture if ami else "x86_64"
        self.root_device_name = ami.root_device_name if ami else None

        # handle weird bug around user_data -- something grabs the repr(), so
        # it must be clean
        if isinstance(self.user_data, list) and len(self.user_data) > 0:
            if isinstance(self.user_data[0], bytes):
                # string will have a "b" prefix -- need to get rid of it
                self.user_data[0] = self.user_data[0].decode("utf-8")

        if self.subnet_id:
            subnet = ec2_backend.get_subnet(self.subnet_id)
            self._placement.zone = subnet.availability_zone

            if self.associate_public_ip is None:
                # Mapping public ip hasnt been explicitly enabled or disabled
                self.associate_public_ip = subnet.map_public_ip_on_launch == "true"
        elif placement:
            self._placement.zone = placement
        else:
            self._placement.zone = ec2_backend.region_name + "a"

        self.block_device_mapping = BlockDeviceMapping()

        self._private_ips = set()
        self.prep_nics(
            nics,
            private_ip=kwargs.get("private_ip"),
            associate_public_ip=self.associate_public_ip,
            security_groups=self.security_groups,
        )
Example #4
0
class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel):
    VALID_ATTRIBUTES = {
        "instanceType",
        "kernel",
        "ramdisk",
        "userData",
        "disableApiTermination",
        "instanceInitiatedShutdownBehavior",
        "rootDeviceName",
        "blockDeviceMapping",
        "productCodes",
        "sourceDestCheck",
        "groupSet",
        "ebsOptimized",
        "sriovNetSupport",
    }

    def __init__(self, ec2_backend, image_id, user_data, security_groups,
                 **kwargs):
        super().__init__()
        self.ec2_backend = ec2_backend
        self.id = random_instance_id()
        self.owner_id = get_account_id()
        self.lifecycle = kwargs.get("lifecycle")

        nics = kwargs.get("nics", {})

        launch_template_arg = kwargs.get("launch_template", {})
        if launch_template_arg and not image_id:
            # the image id from the template should be used
            template_version = ec2_backend._get_template_from_args(
                launch_template_arg)
            self.image_id = template_version.image_id
        else:
            self.image_id = image_id
        # Check if we have tags to process
        if launch_template_arg:
            template_version = ec2_backend._get_template_from_args(
                launch_template_arg)
            tag_spec_set = template_version.data.get("TagSpecification", {})
            tags = convert_tag_spec(tag_spec_set)
            instance_tags = tags.get("instance", {})
            self.add_tags(instance_tags)

        self._state = InstanceState("running", 16)
        self._reason = ""
        self._state_reason = StateReason()
        self.user_data = user_data
        self.security_groups = security_groups
        self.instance_type = kwargs.get("instance_type", "m1.small")
        self.region_name = kwargs.get("region_name", "us-east-1")
        placement = kwargs.get("placement", None)
        self.subnet_id = kwargs.get("subnet_id")
        if not self.subnet_id:
            self.subnet_id = next(
                (n["SubnetId"] for n in nics if "SubnetId" in n), None)
        in_ec2_classic = not bool(self.subnet_id)
        self.key_name = kwargs.get("key_name")
        self.ebs_optimized = kwargs.get("ebs_optimized", False)
        self.source_dest_check = "true"
        self.launch_time = utc_date_and_time()
        self.ami_launch_index = kwargs.get("ami_launch_index", 0)
        self.disable_api_termination = kwargs.get("disable_api_termination",
                                                  False)
        self.instance_initiated_shutdown_behavior = (
            kwargs.get("instance_initiated_shutdown_behavior") or "stop")
        self.sriov_net_support = "simple"
        self._spot_fleet_id = kwargs.get("spot_fleet_id", None)
        self.associate_public_ip = kwargs.get("associate_public_ip", False)
        if in_ec2_classic:
            # If we are in EC2-Classic, autoassign a public IP
            self.associate_public_ip = True

        amis = self.ec2_backend.describe_images(
            filters={"image-id": self.image_id})
        ami = amis[0] if amis else None
        if ami is None:
            warnings.warn(
                "Could not find AMI with image-id:{0}, "
                "in the near future this will "
                "cause an error.\n"
                "Use ec2_backend.describe_images() to "
                "find suitable image for your test".format(self.image_id),
                PendingDeprecationWarning,
            )

        self.platform = ami.platform if ami else None
        self.virtualization_type = ami.virtualization_type if ami else "paravirtual"
        self.architecture = ami.architecture if ami else "x86_64"
        self.root_device_name = ami.root_device_name if ami else None

        # handle weird bug around user_data -- something grabs the repr(), so
        # it must be clean
        if isinstance(self.user_data, list) and len(self.user_data) > 0:
            if isinstance(self.user_data[0], bytes):
                # string will have a "b" prefix -- need to get rid of it
                self.user_data[0] = self.user_data[0].decode("utf-8")

        if self.subnet_id:
            subnet = ec2_backend.get_subnet(self.subnet_id)
            self._placement.zone = subnet.availability_zone

            if self.associate_public_ip is None:
                # Mapping public ip hasnt been explicitly enabled or disabled
                self.associate_public_ip = subnet.map_public_ip_on_launch == "true"
        elif placement:
            self._placement.zone = placement
        else:
            self._placement.zone = ec2_backend.region_name + "a"

        self.block_device_mapping = BlockDeviceMapping()

        self._private_ips = set()
        self.prep_nics(
            nics,
            private_ip=kwargs.get("private_ip"),
            associate_public_ip=self.associate_public_ip,
            security_groups=self.security_groups,
        )

    @property
    def vpc_id(self):
        if self.subnet_id:
            subnet = self.ec2_backend.get_subnet(self.subnet_id)
            return subnet.vpc_id
        if self.nics and 0 in self.nics:
            return self.nics[0].subnet.vpc_id
        return None

    def __del__(self):
        try:
            subnet = self.ec2_backend.get_subnet(self.subnet_id)
            for ip in self._private_ips:
                subnet.del_subnet_ip(ip)
        except Exception:
            # Its not "super" critical we clean this up, as reset will do this
            # worst case we'll get IP address exaustion... rarely
            pass

    def add_block_device(
        self,
        size,
        device_path,
        snapshot_id=None,
        encrypted=False,
        delete_on_termination=False,
        kms_key_id=None,
        volume_type=None,
    ):
        volume = self.ec2_backend.create_volume(
            size=size,
            zone_name=self._placement.zone,
            snapshot_id=snapshot_id,
            encrypted=encrypted,
            kms_key_id=kms_key_id,
            volume_type=volume_type,
        )
        self.ec2_backend.attach_volume(volume.id, self.id, device_path,
                                       delete_on_termination)

    def setup_defaults(self):
        # Default have an instance with root volume should you not wish to
        # override with attach volume cmd.
        volume = self.ec2_backend.create_volume(size=8,
                                                zone_name=self._placement.zone)
        self.ec2_backend.attach_volume(volume.id, self.id, "/dev/sda1", True)

    def teardown_defaults(self):
        for device_path in list(self.block_device_mapping.keys()):
            volume = self.block_device_mapping[device_path]
            volume_id = volume.volume_id
            self.ec2_backend.detach_volume(volume_id, self.id, device_path)
            if volume.delete_on_termination:
                self.ec2_backend.delete_volume(volume_id)

    @property
    def get_block_device_mapping(self):
        return self.block_device_mapping.items()

    @property
    def private_ip(self):
        return self.nics[0].private_ip_address

    @property
    def private_dns(self):
        formatted_ip = self.private_ip.replace(".", "-")
        if self.region_name == "us-east-1":
            return "ip-{0}.ec2.internal".format(formatted_ip)
        else:
            return "ip-{0}.{1}.compute.internal".format(
                formatted_ip, self.region_name)

    @property
    def public_ip(self):
        return self.nics[0].public_ip

    @property
    def public_dns(self):
        if self.public_ip:
            formatted_ip = self.public_ip.replace(".", "-")
            if self.region_name == "us-east-1":
                return "ec2-{0}.compute-1.amazonaws.com".format(formatted_ip)
            else:
                return "ec2-{0}.{1}.compute.amazonaws.com".format(
                    formatted_ip, self.region_name)

    @staticmethod
    def cloudformation_name_type():
        return None

    @staticmethod
    def cloudformation_type():
        # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-instance.html
        return "AWS::EC2::Instance"

    @classmethod
    def create_from_cloudformation_json(cls, resource_name,
                                        cloudformation_json, region_name,
                                        **kwargs):
        from ..models import ec2_backends

        properties = cloudformation_json["Properties"]

        ec2_backend = ec2_backends[region_name]
        security_group_ids = properties.get("SecurityGroups", [])
        group_names = [
            ec2_backend.get_security_group_from_id(group_id).name
            for group_id in security_group_ids
        ]

        reservation = ec2_backend.add_instances(
            image_id=properties["ImageId"],
            user_data=properties.get("UserData"),
            count=1,
            security_group_names=group_names,
            instance_type=properties.get("InstanceType", "m1.small"),
            subnet_id=properties.get("SubnetId"),
            key_name=properties.get("KeyName"),
            private_ip=properties.get("PrivateIpAddress"),
            block_device_mappings=properties.get("BlockDeviceMappings", {}),
        )
        instance = reservation.instances[0]
        for tag in properties.get("Tags", []):
            instance.add_tag(tag["Key"], tag["Value"])

        # Associating iam instance profile.
        # TODO: Don't forget to implement replace_iam_instance_profile_association once update_from_cloudformation_json
        #  for ec2 instance will be implemented.
        if properties.get("IamInstanceProfile"):
            ec2_backend.associate_iam_instance_profile(
                instance_id=instance.id,
                iam_instance_profile_name=properties.get("IamInstanceProfile"),
            )

        return instance

    @classmethod
    def delete_from_cloudformation_json(cls, resource_name,
                                        cloudformation_json, region_name):
        from ..models import ec2_backends

        ec2_backend = ec2_backends[region_name]
        all_instances = ec2_backend.all_instances()

        # the resource_name for instances is the stack name, logical id, and random suffix separated
        # by hyphens.  So to lookup the instances using the 'aws:cloudformation:logical-id' tag, we need to
        # extract the logical-id from the resource_name
        logical_id = resource_name.split("-")[1]

        for instance in all_instances:
            instance_tags = instance.get_tags()
            for tag in instance_tags:
                if (tag["key"] == "aws:cloudformation:logical-id"
                        and tag["value"] == logical_id):
                    instance.delete(region_name)

    @property
    def physical_resource_id(self):
        return self.id

    def start(self):
        for nic in self.nics.values():
            nic.start()

        self._state.name = "running"
        self._state.code = 16

        self._reason = ""
        self._state_reason = StateReason()

    def stop(self):
        for nic in self.nics.values():
            nic.stop()

        self._state.name = "stopped"
        self._state.code = 80

        self._reason = "User initiated ({0})".format(
            datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC"))
        self._state_reason = StateReason(
            "Client.UserInitiatedShutdown: User initiated shutdown",
            "Client.UserInitiatedShutdown",
        )

    def delete(self, region):  # pylint: disable=unused-argument
        self.terminate()

    def terminate(self):
        for nic in self.nics.values():
            nic.stop()

        self.teardown_defaults()

        if self._spot_fleet_id:
            spot_fleet = self.ec2_backend.get_spot_fleet_request(
                self._spot_fleet_id)
            for spec in spot_fleet.launch_specs:
                if (spec.instance_type == self.instance_type
                        and spec.subnet_id == self.subnet_id):
                    break
            spot_fleet.fulfilled_capacity -= spec.weighted_capacity
            spot_fleet.spot_requests = [
                req for req in spot_fleet.spot_requests if req.instance != self
            ]

        self._state.name = "terminated"
        self._state.code = 48

        self._reason = "User initiated ({0})".format(
            datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC"))
        self._state_reason = StateReason(
            "Client.UserInitiatedShutdown: User initiated shutdown",
            "Client.UserInitiatedShutdown",
        )

        # Disassociate iam instance profile if associated, otherwise iam_instance_profile_associations will
        # be pointing to None.
        if self.ec2_backend.iam_instance_profile_associations.get(self.id):
            self.ec2_backend.disassociate_iam_instance_profile(
                association_id=self.ec2_backend.
                iam_instance_profile_associations[self.id].id)

    def reboot(self):
        self._state.name = "running"
        self._state.code = 16

        self._reason = ""
        self._state_reason = StateReason()

    @property
    def dynamic_group_list(self):
        return self.security_groups

    def prep_nics(self,
                  nic_spec,
                  private_ip=None,
                  associate_public_ip=None,
                  security_groups=None):
        self.nics = {}

        if self.subnet_id:
            subnet = self.ec2_backend.get_subnet(self.subnet_id)
            if not private_ip:
                private_ip = subnet.get_available_subnet_ip(instance=self)
            else:
                subnet.request_ip(private_ip, instance=self)

            self._private_ips.add(private_ip)
        elif private_ip is None:
            # Preserve old behaviour if in EC2-Classic mode
            private_ip = random_private_ip()

        # Primary NIC defaults
        primary_nic = {
            "SubnetId": self.subnet_id,
            "PrivateIpAddress": private_ip,
            "AssociatePublicIpAddress": associate_public_ip,
        }
        primary_nic = dict((k, v) for k, v in primary_nic.items() if v)

        # If empty NIC spec but primary NIC values provided, create NIC from
        # them.
        if primary_nic and not nic_spec:
            nic_spec = [primary_nic]
            nic_spec[0]["DeviceIndex"] = 0

        # Flesh out data structures and associations
        for nic in nic_spec:
            device_index = int(nic.get("DeviceIndex"))

            nic_id = nic.get("NetworkInterfaceId")
            if nic_id:
                # If existing NIC found, use it.
                use_nic = self.ec2_backend.get_network_interface(nic_id)
                use_nic.device_index = device_index
                use_nic.public_ip_auto_assign = False

            else:
                # If primary NIC values provided, use them for the primary NIC.
                if device_index == 0 and primary_nic:
                    nic.update(primary_nic)

                if "SubnetId" in nic:
                    subnet = self.ec2_backend.get_subnet(nic["SubnetId"])
                else:
                    # Get default Subnet
                    zone = self._placement.zone
                    subnet = self.ec2_backend.get_default_subnet(
                        availability_zone=zone)

                group_ids = nic.get("SecurityGroupId") or []
                if security_groups:
                    group_ids.extend([group.id for group in security_groups])

                use_nic = self.ec2_backend.create_network_interface(
                    subnet,
                    nic.get("PrivateIpAddress"),
                    device_index=device_index,
                    public_ip_auto_assign=nic.get("AssociatePublicIpAddress",
                                                  False),
                    group_ids=group_ids,
                )

            self.attach_eni(use_nic, device_index)

    def attach_eni(self, eni, device_index):
        device_index = int(device_index)
        self.nics[device_index] = eni

        # This is used upon associate/disassociate public IP.
        eni.instance = self
        eni.attachment_id = random_eni_attach_id()
        eni.attach_time = utc_date_and_time()
        eni.status = "in-use"
        eni.device_index = device_index

        return eni.attachment_id

    def detach_eni(self, eni):
        self.nics.pop(eni.device_index, None)
        eni.instance = None
        eni.attachment_id = None
        eni.attach_time = None
        eni.status = "available"
        eni.device_index = None

    @classmethod
    def has_cfn_attr(cls, attr):
        return attr in [
            "AvailabilityZone",
            "PrivateDnsName",
            "PublicDnsName",
            "PrivateIp",
            "PublicIp",
        ]

    def get_cfn_attribute(self, attribute_name):
        from moto.cloudformation.exceptions import UnformattedGetAttTemplateException

        if attribute_name == "AvailabilityZone":
            return self.placement
        elif attribute_name == "PrivateDnsName":
            return self.private_dns
        elif attribute_name == "PublicDnsName":
            return self.public_dns
        elif attribute_name == "PrivateIp":
            return self.private_ip
        elif attribute_name == "PublicIp":
            return self.public_ip
        raise UnformattedGetAttTemplateException()

    def applies(self, filters):
        if filters:
            applicable = False
            for f in filters:
                acceptable_values = f["values"]
                if f["name"] == "instance-state-name":
                    if self._state.name in acceptable_values:
                        applicable = True
                if f["name"] == "instance-state-code":
                    if str(self._state.code) in acceptable_values:
                        applicable = True
            return applicable
        # If there are no filters, all instances are valid
        return True