Esempio n. 1
0
 def __init__(self, core, clock=None):
     """
     :param mimic.core.MimicCore core: The core object to dispatch routes
         from.
     :param twisted.internet.task.Clock clock: The clock to advance from the
         ``/mimic/v1.1/tick`` API.
     """
     self.core = core
     self.clock = clock
     self.identity_behavior_registry = BehaviorRegistryCollection()
Esempio n. 2
0
class RegionalStackCollection(object):
    """
    A collection of :obj:`Stack` objects for a region.
    """
    tenant_id = attr.ib()
    region_name = attr.ib()
    stacks = attr.ib(default=attr.Factory(list))
    behavior_registry_collection = attr.ib(
        default=attr.Factory(lambda: BehaviorRegistryCollection()))

    def stack_by_id(self, stack_id):
        """
        Retrieves a stack by its ID
        """
        for stack in self.stacks:
            if stack.stack_id == stack_id:
                return stack

    def request_list(self, absolutize_url, show_deleted=False, tags=[]):
        """
        Tries a stack list operation.
        """
        def should_show_stack(stack):
            """
            Determines if a stack should be shown for the list response.
            """
            if stack.is_deleted() and not show_deleted:
                return False

            for tag in tags:
                if not stack.has_tag(tag):
                    return False

            return True

        result = {
            "stacks": [
                stack.json(absolutize_url) for stack in self.stacks
                if should_show_stack(stack)
            ]
        }

        return json.dumps(result)

    def request_creation(self, request, body, absolutize_url):
        """
        Tries a stack create operation.
        """
        registry = self.behavior_registry_collection.registry_by_event(
            stack_creation)
        behavior = registry.behavior_for_attributes({
            'tenant_id':
            self.tenant_id,
            'stack_name':
            body['stack_name']
        })

        return behavior(collection=self,
                        request=request,
                        body=body,
                        absolutize_url=absolutize_url)

    def request_check(self, request, stack_name, stack_id):
        """
        Tries a stack check operation.
        """
        registry = self.behavior_registry_collection.registry_by_event(
            stack_check)
        behavior = registry.behavior_for_attributes({
            'tenant_id': self.tenant_id,
            'stack_name': stack_name,
            'stack_id': stack_id
        })

        return behavior(collection=self,
                        request=request,
                        stack_name=stack_name,
                        stack_id=stack_id)

    def request_update(self, request, stack_name, stack_id):
        """
        Tries a stack update operation.
        """
        registry = self.behavior_registry_collection.registry_by_event(
            stack_update)
        behavior = registry.behavior_for_attributes({
            'tenant_id': self.tenant_id,
            'stack_name': stack_name,
            'stack_id': stack_id
        })

        return behavior(collection=self,
                        request=request,
                        stack_name=stack_name,
                        stack_id=stack_id)

    def request_deletion(self, request, stack_name, stack_id):
        """
        Tries a stack delete operation.
        """
        registry = self.behavior_registry_collection.registry_by_event(
            stack_deletion)
        behavior = registry.behavior_for_attributes({
            'tenant_id': self.tenant_id,
            'stack_name': stack_name,
            'stack_id': stack_id
        })

        return behavior(collection=self,
                        request=request,
                        stack_name=stack_name,
                        stack_id=stack_id)
Esempio n. 3
0
        return create_building_behavior(
            {"duration": float(metadata['server_building'])}
        )
    if 'server_error' in metadata:
        return create_error_status_behavior()
    return None


@attributes(
    ["tenant_id", "region_name", "clock",
     Attribute("servers", default_factory=list),
     Attribute("image_store", default_factory=list),
     Attribute("flavors_store", default_factory=list),
     Attribute(
         "behavior_registry_collection",
         default_factory=lambda: BehaviorRegistryCollection())]
)
class RegionalServerCollection(object):
    """
    A collection of servers, in a given region, for a given tenant.
    """

    def server_by_id(self, server_id):
        """
        Retrieve a :obj:`Server` object by its ID.
        """
        for server in self.servers:
            if server.server_id == server_id and server.status != u"DELETED":
                return server

    def flavor_by_id(self, flavor_id):
Esempio n. 4
0
class RegionalServerCollection(object):
    """
    A collection of servers, in a given region, for a given tenant.
    """
    tenant_id = attr.ib()
    region_name = attr.ib()
    clock = attr.ib()
    servers = attr.ib(default=attr.Factory(list))
    behavior_registry_collection = attr.ib(
        default=attr.Factory(lambda: BehaviorRegistryCollection()))

    def server_by_id(self, server_id):
        """
        Retrieve a :obj:`Server` object by its ID.
        """
        for server in self.servers:
            if server.server_id == server_id and server.status != u"DELETED":
                return server

    def request_creation(self, creation_http_request, creation_json,
                         absolutize_url):
        """
        Request that a server be created.
        """
        server = creation_json.get('server', {})
        metadata = server.get('metadata', {})
        behavior = metadata_to_creation_behavior(metadata)
        if behavior is None:
            registry = self.behavior_registry_collection.registry_by_event(
                server_creation)
            behavior = registry.behavior_for_attributes({
                "tenant_id":
                self.tenant_id,
                "server_name":
                creation_json["server"]["name"],
                "metadata":
                creation_json["server"].get("metadata", {})
            })
        return behavior(self, creation_http_request, creation_json,
                        absolutize_url)

    def request_read(self, http_get_request, server_id, absolutize_url):
        """
        Request the information / details for an individual server.

        Not found response verified against Rackspace Cloud Servers as of
        2015-04-30.
        """
        server = self.server_by_id(server_id)
        if server is None:
            return dumps(
                not_found("Instance could not be found", http_get_request))
        return dumps({"server": server.detail_json(absolutize_url)})

    def request_ips(self, http_get_ips_request, server_id):
        """
        Request the addresses JSON for a specific server.

        Not found response verified against Rackspace Cloud Servers as of
        2015-04-30.
        """
        http_get_ips_request.setResponseCode(200)
        server = self.server_by_id(server_id)
        if server is None:
            return dumps(
                not_found("Instance does not exist", http_get_ips_request))
        return dumps({"addresses": server.addresses_json()})

    def request_list(self,
                     http_get_request,
                     include_details,
                     absolutize_url,
                     name=u"",
                     limit=None,
                     marker=None,
                     changes_since=None):
        """
        Request the list JSON for all servers.

        :param str changes_since: ISO8601 formatted datetime. Based on
            http://docs.rackspace.com/servers/api/v2/cs-devguide/content/ChangesSince.html

        Note: only supports filtering by name right now, but will need to
        support more going forward.

        Pagination behavior verified against Rackspace Nova as of 2015-04-29.
        """
        to_be_listed = self.servers

        if changes_since is not None:
            since = timestamp_to_seconds(changes_since)
            to_be_listed = [s for s in to_be_listed if s.update_time >= since]

        # marker can be passed without limit, in which case the whole server
        # list, after the server that matches the marker, is returned
        if marker is not None:
            last_seen = [
                i for i, server in enumerate(to_be_listed)
                if server.server_id == marker
            ]
            if not last_seen:
                # Error response and body verified against Rackspace Nova as
                # of 2015-04-29
                return dumps(
                    bad_request("marker [{0}] not found".format(marker),
                                http_get_request))
            else:
                last_seen = last_seen[0]
                to_be_listed = to_be_listed[last_seen + 1:]

        # A valid marker is an ID in the entire server list.  It does not
        # have to be for a server that matches the given name.
        to_be_listed = [
            server for server in to_be_listed if name in server.server_name
        ]

        if changes_since is None:
            to_be_listed = [s for s in to_be_listed if s.status != u"DELETED"]

        if limit is not None:
            try:
                limit = int(limit)
            except ValueError:
                return dumps(
                    bad_request("limit param must be an integer",
                                http_get_request))
            if limit < 0:
                return dumps(
                    bad_request("limit param must be positive",
                                http_get_request))

            to_be_listed = to_be_listed[:limit]

        result = {
            "servers": [
                server.brief_json(absolutize_url)
                if not include_details else server.detail_json(absolutize_url)
                for server in to_be_listed
            ]
        }

        # A server links blob is included only if limit is passed.  If
        # only the marker was provided, no server links blob is included.
        # Note that if limit=0, an empty server list is returned and no
        # server link blob is returned.
        if limit and len(to_be_listed) >= limit:
            query_params = {'limit': limit}
            query_params['marker'] = to_be_listed[-1].server_id
            if name:
                query_params['name'] = name

            path = "v2/{0}/servers{1}?{2}".format(
                self.tenant_id, "/detail" if include_details else "",
                urlencode(query_params))
            result["servers_links"] = [{
                "href": absolutize_url(path),
                "rel": "next"
            }]

        return dumps(result)

    def request_delete(self, http_delete_request, server_id):
        """
        Delete a server with the given ID.

        Not found response verified against Rackspace Cloud Servers as of
        2015-04-30.
        """
        server = self.server_by_id(server_id)
        if server is None:
            return dumps(
                not_found("Instance could not be found", http_delete_request))
        if 'delete_server_failure' in server.metadata:
            srvfail = loads(server.metadata['delete_server_failure'])
            if srvfail['times']:
                srvfail['times'] -= 1
                server.metadata['delete_server_failure'] = dumps(srvfail)
                http_delete_request.setResponseCode(500)
                return b''
        http_delete_request.setResponseCode(204)
        server.update_status(u"DELETED")
        return b''

    def request_action(self, http_action_request, server_id, absolutize_url,
                       regional_image_collection, image_store):
        """
        Perform the requested action on the provided server
        """
        server = self.server_by_id(server_id)
        if server is None:
            return dumps(
                not_found("Instance " + server_id + " could not be found",
                          http_action_request))
        action_json = json_from_request(http_action_request)
        if 'resize' in action_json:
            flavor = action_json['resize'].get('flavorRef')
            if not flavor:
                return dumps(
                    bad_request(
                        "Resize requests require 'flavorRef' attribute",
                        http_action_request))

            server.status = 'VERIFY_RESIZE'
            server.oldFlavor = server.flavor_ref
            server.flavor_ref = flavor
            http_action_request.setResponseCode(202)
            return b''

        elif 'confirmResize' in action_json or 'revertResize' in action_json:
            if server.status == 'VERIFY_RESIZE' and 'confirmResize' in action_json:
                server.status = 'ACTIVE'
                http_action_request.setResponseCode(204)
                return b''
            elif server.status == 'VERIFY_RESIZE' and 'revertResize' in action_json:
                server.status = 'ACTIVE'
                server.flavor_ref = server.oldFlavor
                http_action_request.setResponseCode(202)
                return b''
            else:
                return dumps(
                    conflicting(
                        "Cannot '" + list(action_json.keys())[0] +
                        "' instance " + server_id +
                        " while it is in vm_state active",
                        http_action_request))
        elif 'rescue' in action_json:
            if server.status != 'ACTIVE':
                return dumps(
                    conflicting(
                        "Cannot 'rescue' instance " + server_id +
                        " while it is in task state other than active",
                        http_action_request))
            else:
                server.status = 'RESCUE'
                http_action_request.setResponseCode(200)
                password = random_string(12)
                return dumps({"adminPass": password})

        elif 'unrescue' in action_json:
            if server.status == 'RESCUE':
                server.status = 'ACTIVE'
                http_action_request.setResponseCode(200)
                return b''
            else:
                return dumps(
                    conflicting(
                        "Cannot 'unrescue' instance " + server_id +
                        " while it is in task state other than rescue",
                        http_action_request))

        elif 'reboot' in action_json:
            reboot_type = action_json['reboot'].get('type')
            if not reboot_type:
                return dumps(
                    bad_request("Missing argument 'type' for reboot",
                                http_action_request))
            if reboot_type == 'HARD':
                server.status = 'HARD_REBOOT'
                http_action_request.setResponseCode(202)
                server.collection.clock.callLater(6.0, server.update_status,
                                                  u"ACTIVE")
                return b''
            elif reboot_type == 'SOFT':
                server.status = 'REBOOT'
                http_action_request.setResponseCode(202)
                server.collection.clock.callLater(3.0, server.update_status,
                                                  u"ACTIVE")
                return b''
            else:
                return dumps(
                    bad_request(
                        "Argument 'type' for reboot is not HARD or SOFT",
                        http_action_request))

        elif 'changePassword' in action_json:
            password = action_json['changePassword'].get('adminPass')
            if not password:
                return dumps(
                    bad_request("No adminPass was specified",
                                http_action_request))
            if server.status == 'ACTIVE':
                http_action_request.setResponseCode(202)
                return b''
            else:
                return dumps(
                    conflicting(
                        "Cannot 'changePassword' instance " + server_id +
                        " while it is in task state other than active",
                        http_action_request))

        elif 'rebuild' in action_json:
            image_ref = action_json['rebuild'].get('imageRef')
            if not image_ref:
                return dumps(
                    bad_request("Could not parse imageRef from request.",
                                http_action_request))
            if server.status == 'ACTIVE':
                server.image_ref = image_ref
                server.status = 'REBUILD'
                http_action_request.setResponseCode(202)
                server.collection.clock.callLater(5.0, server.update_status,
                                                  u"ACTIVE")
                server_details = server.detail_json(absolutize_url)
                server_details['adminPass'] = '******'
                return dumps({"server": server_details})
            else:
                return dumps(
                    conflicting(
                        "Cannot 'rebuild' instance " + server_id +
                        " while it is in task state other than active",
                        http_action_request))

        elif 'createImage' in action_json:
            image_name = action_json['createImage'].get('name')
            server == self.server_by_id(server_id)
            links = server.links_json(absolutize_url)
            server_id = server.server_id
            image_ref = server.image_ref
            image = image_store.get_image_by_id(image_ref)
            image_json = regional_image_collection.get_image(
                http_action_request, image_ref, absolutize_url)
            image_dict = loads(image_json)
            flavor_classes = image_dict['image']['metadata']['flavor_classes']
            os_type = image_dict['image']['metadata']['os_type']
            os_distro = image_dict['image']['metadata'][
                'org.openstack__1__os_distro']
            vm_mode = image_dict['image']['metadata']['vm_mode']
            disk_config = image_dict['image']['metadata']['auto_disk_config']
            image_id = text_type(uuid.uuid4())
            image_size = image.image_size
            minRam = image.minRam
            minDisk = image.minDisk
            saved_image = RackspaceSavedImage(image_id=image_id,
                                              tenant_id=self.tenant_id,
                                              image_size=image_size,
                                              name=image_name,
                                              minRam=minRam,
                                              minDisk=minDisk,
                                              links=links,
                                              server_id=server_id,
                                              flavor_classes=flavor_classes,
                                              os_type=os_type,
                                              os_distro=os_distro,
                                              vm_mode=vm_mode,
                                              disk_config=disk_config)

            image_store.add_image_to_store(saved_image)
            http_action_request.setHeader(b"Location", b"www.someurl.com")
            http_action_request.setResponseCode(202)
            return b''

        else:
            return dumps(
                bad_request("There is no such action currently supported",
                            http_action_request))