Beispiel #1
0
class CinderMock(object):
    """
    Cinder Mock
    """
    def __init__(self, api_mock, uri_prefix, session_store, name):
        """
        Create a Cinder region with a given URI prefix
        """
        self.uri_prefix = uri_prefix
        self._api_mock = api_mock
        self._session_store = session_store
        self._name = name

    app = MimicApp()

    @app.route('/v2/<string:tenant_id>/volumes', methods=['GET'])
    def get_volumes(self, request, tenant_id):
        """
        Lists summary information for all Block Storage volumes that the tenant
        can access.
        http://developer.openstack.org/api-ref-blockstorage-v2.html#listVolumes
        """
        request.setResponseCode(200)
        return json.dumps({'volumes': []})

    @app.route('/v2/<string:tenant_id>/volumes/detail', methods=['GET'])
    def get_volumes_detail(self, request, tenant_id):
        """
        Lists detailed information for all Block Storage volumes that the
        tenant can access.
        http://developer.openstack.org/api-ref-blockstorage-v2.html#listVolumesDetail
        """
        request.setResponseCode(200)
        return json.dumps({'volumes': []})
Beispiel #2
0
class GlanceAdminApi(object):
    """
    Rest endpoints for mocked Glance Admin API.
    """

    app = MimicApp()

    def __init__(self, core):
        """
        :param MimicCore core: The core to which this Glance Admin API will be
        communicating.
        """
        self.core = core

    @app.route('/v2/images', methods=['GET'])
    def get_images_for_admin(self, request):
        """
        Returns a list of glance images.
        """
        return json.dumps(self.core.glance_admin_image_store.list_images())

    @app.route('/v2/schemas/image', methods=['GET'])
    def get_image_schema_for_admin(self, request):
        """
        Returns the glance image schema.
        """
        return json.dumps(get_image_schema())
Beispiel #3
0
class DNSMock(object):
    """
    DNS Mock
    """
    def __init__(self, api_mock, uri_prefix, session_store, name):
        """
        Create a DNS region with a given URI prefix
        """
        self.uri_prefix = uri_prefix
        self._api_mock = api_mock
        self._session_store = session_store
        self._name = name

    app = MimicApp()

    @app.route('/v1.0/<string:tenant_id>/rdns/cloudServersOpenStack',
               methods=['GET'])
    def get_PTR_records(self, request, tenant_id):
        """
        Lists all PTR records configured for a specified Cloud device
        """
        request.setResponseCode(404)
        return json.dumps({
            'message': 'Not Found',
            'code': 404,
            'details': 'No PTR records found'
        })
class RackConnectV3Region(object):
    """
    A set of ``klein`` routes representing a RackConnect V3 endpoint.
    """
    iapi = attr.ib()
    uri_prefix = attr.ib()
    session_store = attr.ib()
    region_name = attr.ib()
    default_pools = attr.ib()

    app = MimicApp()

    @app.route("/v3/<string:tenant_id>/load_balancer_pools", branch=True)
    def get_tenant_lb_pools(self, request, tenant_id):
        """
        Get a resource for a tenant's load balancer pools in this region.
        """
        tenant_store = self.session_store.session_for_tenant_id(tenant_id)
        per_tenant_lbs = tenant_store.data_for_api(self.iapi,
                                                   lambda: defaultdict(list))
        per_tenant_per_region_lbs = per_tenant_lbs[self.region_name]

        if not per_tenant_per_region_lbs:
            per_tenant_per_region_lbs.extend(
                [LoadBalancerPool() for _ in range(self.default_pools)])

        handler = LoadBalancerPoolsInRegion(lbpools=per_tenant_per_region_lbs,
                                            clock=self.session_store.clock)
        return handler.app.resource()
Beispiel #5
0
class NovaControlApiRegion(object):
    """
    Klein resources for the Nova Control plane API
    """
    api_mock = attr.ib()
    uri_prefix = attr.ib()
    session_store = attr.ib()
    region = attr.ib()

    app = MimicApp()

    @app.route('/v2/<string:tenant_id>/behaviors', branch=True)
    def handle_behaviors(self, request, tenant_id):
        """
        Return the behavior CRUD resource.
        """
        region_collection = self._collection_from_tenant(tenant_id)
        behavior_handler = NovaControlApiRegionBehaviors(
            region_collection.behavior_registry_collection)
        return behavior_handler.app.resource()

    @app.route("/v2/<string:tenant_id>/attributes/", methods=['POST'])
    def change_attributes(self, request, tenant_id):
        """
        Modify the specified attributes on existing servers.

        The request looks like this::

            {
                "status": {
                    "test_server_id_8482197407": "ERROR",
                    "test_server_id_0743289146": "BUILD"
                }
            }

        As more attributes are added, they should be additional top-level keys
        where "status" goes in this request.
        """
        region_collection = self._collection_from_tenant(tenant_id)
        attributes_description = json_from_request(request)
        statuses_description = attributes_description["status"]
        servers = [
            region_collection.server_by_id(server_id)
            for server_id in statuses_description
        ]
        if None in servers:
            request.setResponseCode(BAD_REQUEST)
            return b''
        for server in servers:
            server.update_status(statuses_description[server.server_id])
        request.setResponseCode(CREATED)
        return b''

    def _collection_from_tenant(self, tenant_id):
        """
        Retrieve the server collection for this region for the given tenant.
        """
        return (self.api_mock.nova_api._get_session(
            self.session_store, tenant_id).collection_for_region(self.region))
Beispiel #6
0
class QueueApiRoutes(object):
    """
    Klein routes for queue API methods.
    """

    app = MimicApp()

    def __init__(self, api_mock, uri_prefix, session_store, queue_name):
        """
        Create a queue region with a given URI prefix (used for generating URIs
        to queues).
        """
        self.uri_prefix = uri_prefix
        self._api_mock = api_mock
        self._session_store = session_store
        self._queue_name = queue_name

    def _queue_cache(self, tenant_id):
        """
        Get the given queue-cache object for the given tenant, creating one if
        there isn't one.
        """
        return (
            self._session_store.session_for_tenant_id(tenant_id).data_for_api(
                self._api_mock,
                lambda: collections.defaultdict(Q_Cache))[self._queue_name])

    @app.route("/v1/<string:tenant_id>/queues/<string:queue_name>",
               methods=['PUT'])
    def create_queue(self, request, tenant_id, queue_name):
        """
        Api call to create and save queue. HTTP status code of 201.
        """
        queue_id = randrange(99999)
        q_cache = self._queue_cache(tenant_id)
        response_data = add_queue(queue_id, queue_name, tenant_id, q_cache)
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route("/v1/<string:tenant_id>/queues", methods=['GET'])
    def list_queues(self, request, tenant_id):
        """
        Api call to get a list of queues. HTTP status code of 200
        """
        q_cache = self._queue_cache(tenant_id)
        response_data = list_queues(tenant_id, q_cache)
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route("/v1/<string:tenant_id>/queues/<string:queue_name>",
               methods=['DELETE'])
    def del_queue(self, request, tenant_id, queue_name):
        """
        Api call to delete a queue. HTTP status code of 201
        """
        q_cache = self._queue_cache(tenant_id)
        response_data = delete_queue(queue_name, q_cache)
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])
Beispiel #7
0
class OneLoadBalancerPool(object):
    """
    A set of ``klein`` routes handling the RackConnect V3 API for a single
    load balancer pool
    """
    app = MimicApp()

    @app.route("/", methods=["GET"])
    def get_pool_information(self, request):
        """
        API call to get the details of the single load balancer pool
        corresponding to this handler.

        Returns a 200 because the pool definitely exists by the time this
        handler is invoked.

        http://docs.rcv3.apiary.io/#get-%2Fv3%2F%7Btenant_id%7D%2Fload_balancer_pools%2F%7Bid%7D
        """
        return json.dumps(self.pool.as_json())

    @app.route("/nodes", methods=["GET"])
    def get_node_collection_information(self, request):
        """
        List all the nodes for the load balancer pool.

        Returns a 200 always.

        http://docs.rcv3.apiary.io/#get-%2Fv3%2F%7Btenant_id%7D%2Fload_balancer_pools%2F%7Bload_balancer_pool_id%7D%2Fnodes
        """
        return json.dumps([node.short_json() for node in self.pool.nodes])

    @app.route("/nodes/details", methods=["GET"])
    def get_node_collection_details_information(self, request):
        """
        Get detailed information about all the nodes, including the cloud
        server network information.  This is unimplemented as it might
        need to be hooked up to the nova api.

        http://docs.rcv3.apiary.io/#get-%2Fv3%2F%7Btenant_id%7D%2Fload_balancer_pools%2F%7Bload_balancer_pool_id%7D%2Fnodes%2Fdetails
        """
        request.setResponseCode(NOT_IMPLEMENTED)

    @app.route("/nodes", methods=["POST"])
    def add_single_pool_node(self, request):
        """
        Add a single pool node to the load balancer pool is not implemented
        yet.

        http://docs.rcv3.apiary.io/#post-%2Fv3%2F%7Btenant_id%7D%2Fload_balancer_pools%2F%7Bload_balancer_pool_id%7D%2Fnodes
        """
        request.setResponseCode(NOT_IMPLEMENTED)

    @app.route("/nodes/<string:node_id>", branch=True)
    def handle_single_node_requests(self, request, node_id):
        """
        Catchall to specify that single node operations (GET, DELETE,
        GET details) are not implemented yet
        """
        request.setResponseCode(NOT_IMPLEMENTED)
Beispiel #8
0
class CloudFeedsControlRegion(object):
    """
    Klein routes for cloud feed's control API within a particular region.
    """
    api_mock = attr.ib()
    uri_prefix = attr.ib()
    session_store = attr.ib()
    region = attr.ib()

    app = MimicApp()
Beispiel #9
0
class LoadBalancerControlRegion(object):
    """
    Klein routes for load balancer's control API within a particular region.
    """

    app = MimicApp()

    def _collection_from_tenant(self, tenant_id):
        """
        Retrieve the server collection for this region for the given tenant.
        """
        return (self.api_mock.lb_api._get_session(self.session_store, tenant_id)
                .collection_for_region(self.region))

    @app.route(
        '/v2/<string:tenant_id>/loadbalancer/<int:clb_id>/attributes',
        methods=['PATCH']
    )
    def set_attributes(self, request, tenant_id, clb_id):
        """
        Alters the supported attributes of the CLB to supported values.  To
        return things back to normal, you'll first need to list the CLB to get
        any original values yourself.
        """
        regional_lbs = self._collection_from_tenant(tenant_id)
        if not regional_lbs.lb_in_region(clb_id):
            request.setResponseCode(404)
            return json.dumps({
                "message": "Tenant {0} doesn't own load balancer {1}".format(
                    tenant_id, clb_id
                ),
                "code": 404,
            })

        try:
            content = json.loads(request.content.read())
        except ValueError:
            request.setResponseCode(400)
            return json.dumps(invalid_resource("Invalid JSON request body"))

        try:
            regional_lbs.set_attributes(clb_id, content)
        except BadKeysError, bke:
            request.setResponseCode(400)
            return json.dumps({
                "message": str(bke),
                "code": 400,
            })
        except BadValueError, bve:
            request.setResponseCode(400)
            return json.dumps({
                "message": str(bve),
                "code": 400,
            })
Beispiel #10
0
class MimicRoot(object):
    """
    Klein routes for the root of the mimic URI hierarchy.
    """

    app = MimicApp()

    def __init__(self, core):
        """
        :param mimic.core.MimicCore core: The core object to dispatch routes
            from.
        """
        self.core = core

    @app.route("/", methods=["GET"])
    def help(self, request):
        """
        A helpful greeting message.
        """
        request.responseHeaders.setRawHeaders("content-type", ["text/plain"])
        return ("To get started with Mimic, POST an authentication request to:"
                "\n\n/identity/v2.0/tokens")

    @app.route("/identity", branch=True)
    def get_auth_api(self, request):
        """
        Get the identity ...
        """
        return AuthApi(self.core).app.resource()

    @app.route('/mimic/v1.0/presets', methods=['GET'])
    def get_mimic_presets(self, request):
        """
        Return the preset values for mimic
        """
        request.setResponseCode(200)
        return json.dumps(get_presets)

    @app.route("/service/<string:region_name>/<string:service_id>",
               branch=True)
    def get_service_resource(self, request, region_name, service_id):
        """
        Based on the URL prefix of a region and a service, where the region is
        an identifier (like ORD, DFW, etc) and service is a
        dynamically-generated UUID for a particular plugin, retrieve the
        resource associated with that service.
        """
        serviceObject = self.core.service_with_region(
            region_name, service_id, base_uri_from_request(request))

        if serviceObject is None:
            # workaround for https://github.com/twisted/klein/issues/56
            return NoResource()
        return serviceObject
Beispiel #11
0
class GlanceAdminApi(object):
    """
    Rest endpoints for mocked Glance Admin API.
    """

    app = MimicApp()

    def __init__(self, core):
        """
        :param MimicCore core: The core to which this Glance Admin API will be
        communicating.
        """
        self.core = core

    @app.route('/v2/images', methods=['POST'])
    def create_image(self, request):
        """
        Creates a new image and returns response code 201.
        """
        return json.dumps(
            self.core.glance_admin_image_store.create_image(request))

    @app.route('/v2/images', methods=['GET'])
    def get_images_for_admin(self, request):
        """
        Returns a list of glance images.
        """
        return json.dumps(self.core.glance_admin_image_store.list_images())

    @app.route('/v2/images/<string:image_id>', methods=['GET'])
    def get_image_for_admin(self, request, image_id):
        """
        Returns image with given `image_id`.
        """
        return json.dumps(
            self.core.glance_admin_image_store.get_image(request, image_id))

    @app.route('/v2/images/<string:image_id>', methods=['DELETE'])
    def delete_image(self, request, image_id):
        """
        Deletes the image and returns response code 204.
        """
        return self.core.glance_admin_image_store.delete_image(
            request, image_id)

    @app.route('/v2/schemas/image', methods=['GET'])
    def get_image_schema_for_admin(self, request):
        """
        Returns the glance image schema.
        """
        return json.dumps(get_image_schema())
Beispiel #12
0
class MimicPresetApi(object):

    """
    Rest endpoints for mocked Load balancer api.
    """
    app = MimicApp()

    @app.route('/v1.0/mimic/presets', methods=['GET'])
    def get_mimic_presets(self, request):
        """
        Return the preset values for mimic
        """
        request.setResponseCode(200)
        return json.dumps(get_presets)
Beispiel #13
0
class CloudFeedsRegion(object):
    """
    Klein routes for cloud feeds API methods within a particular region.
    """

    app = MimicApp()

    def __init__(self, api_mock, uri_prefix, session_store, region_name):
        """
        Fetches the cloud feeds id for a failure, invalid scenarios, etc.
        """
        self.uri_prefix = uri_prefix
        self.region_name = region_name
        self._api_mock = api_mock
        self._session_store = session_store
Beispiel #14
0
class SwiftRegion(object):
    """
    :obj:`SwiftRegion` is a set of klein routes and application representing a
    Swift endpoint.
    """

    app = MimicApp()

    @app.route("/v1/<string:tenant_id>", branch=True)
    def get_one_tenant_resource(self, request, tenant_id):
        """
        Get a resource for a tenant in this region.
        """
        return (
            self.session_store.session_for_tenant_id(tenant_id).data_for_api(
                self.api, lambda: SwiftTenantInRegion().app.resource()))
Beispiel #15
0
class NovaControlApiRegion(object):
    """
    Klien resources for the Nova Control plane API
    """
    app = MimicApp()

    @app.route('/v2/<string:tenant_id>/behaviors/creation/', methods=['POST'])
    def register_creation_behavior(self, request, tenant_id):
        """
        Register the specified behavior to cause a future server creation
        operation to behave in the described way.

        The request looks like this::

            {
                # list of criteria for which requests will behave in the
                # described way
                "criteria": [
                    {"tenant_id": "maybe_fail_.*"},
                    {"server_name": "failing_server_.*"},
                    {"metadata": {"key_we_should_have": "fail",
                                  "key_we_should_not_have": null}}
                ],
                # what kind of behavior: in this case, "fail the request"
                "name": "fail",
                # parameters for the behavior: in this case,
                # "return a 404 with a message".
                "parameters": {
                    "code": 404,
                    "message": "Stuff is broken, what"
                }
            }
        """
        request.setResponseCode(CREATED)
        global_collection = self.api_mock.nova_api._get_session(
            self.session_store, tenant_id)
        behavior_description = json.loads(request.content.read())
        behavior = server_creation.create_behavior(
            behavior_description['name'], behavior_description['parameters'])
        criteria = criteria_collection_from_request_criteria(
            behavior_description['criteria'], nova_criterion_factories)
        region_collection = global_collection.collection_for_region(
            self.region)
        region_collection.register_creation_behavior_for_criteria(
            behavior, criteria)
        return b''
Beispiel #16
0
class CustomerApi(object):
    """
    Rest endpoints for mocked Customer api.
    """

    app = MimicApp()

    def __init__(self, core):
        """
        :param MimicCore core: The core to which this Customer Api will be
        communicating.
        """
        self.core = core

    @app.route('/<string:tenant_id>/contacts', methods=['GET'])
    def get_customer_contacts_for_tenant(self, request, tenant_id):
        """
        Responds with code 200 and returns a list of contacts for the given tenant.

        Until the contacts are set by the `POST` call, this returns the default
        contacts for a tenant.
        """
        response = self.core.contacts_store.list_contacts_for_tenant(tenant_id)
        return json.dumps(response)

    @app.route('/<string:tenant_id>/contacts', methods=['POST'])
    def add_customer_contacts_for_tenant(self, request, tenant_id):
        """
        Adds new contacts to a tenant and responds with a 200.

        Note: If there is a GET on the tenant before this `POST` call, the default
        contacts would have been listed. This POST will overwrite the existing contacts
        and only set the contacts provided.
        """
        content = json_from_request(request)
        contact_list = [(each_contact["email"], each_contact["role"])
                        for each_contact in content]
        self.core.contacts_store.add_to_contacts_store(tenant_id, contact_list)
        return b''
Beispiel #17
0
class NeutronMock(object):
    """
    Klein routes for the Neturon API.
    """
    def __init__(self, api_mock, uri_prefix, session_store, name):
        """
        Create a networks region with a given URI prefix
        """
        self.uri_prefix = uri_prefix
        self._api_mock = api_mock
        self._session_store = session_store
        self._name = name

    app = MimicApp()

    @app.route('/v2/<string:tenant_id>/networks', methods=['GET'])
    def get_networks(self, request, tenant_id):
        """
        Retrieves list of networks to which the specified tenant has access.
        http://developer.openstack.org/api-ref-networking-v2.html#listNetworks
        """
        request.setResponseCode(200)
        return json.dumps({'networks': []})
Beispiel #18
0
class GlanceMock(object):
    """
    Glance Mock
    """
    def __init__(self, api_mock, uri_prefix, session_store, name):
        """
        Create a glance region with a given URI prefix.
        """
        self.uri_prefix = uri_prefix
        self._api_mock = api_mock
        self._session_store = session_store
        self._name = name

    app = MimicApp()

    @app.route('/v2/<string:tenant_id>/images', methods=['GET'])
    def get_images(self, request, tenant_id):
        """
        Returns a list of glance images. Currently there is no provision
        for shared versus unshared images in the response
        """
        request.setResponseCode(200)
        return json.dumps(get_images())
Beispiel #19
0
class ValkyrieApi(object):
    """
    Rest endpoints for the Valkyrie API.
    """

    app = MimicApp()

    def __init__(self, core):
        """
        :param MimicCore core: The core to which the Valkyrie Api will be
        communicating.
        """
        self.core = core

    @app.route('/login', methods=['POST'])
    def login(self, request):
        """
        Responds with response code 200 and returns an auth token
        See https://valkyrie.my.rackspace.com/#authentication
        """
        return self.core.valkyrie_store.create_token(request)

    @app.route('/login_user', methods=['POST'])
    def login_user(self, request):
        """
        Responds with response code 200 and returns an auth token
        See https://valkyrie.my.rackspace.com/#authentication
        """
        return self.core.valkyrie_store.create_token(request)

    effective_any_permissions_route = (
        '/account/<int:account_number>'
        '/permissions/contacts/any'
        '/by_contact/<int:contact_id>/effective')

    @app.route(effective_any_permissions_route, methods=['GET'])
    def effective_any_permissions(self, request, account_number, contact_id):
        """
        Responds with response code 200 and returns a list of all permissions
        for the given account and contact
        See https://valkyrie.my.rackspace.com/#managed-accounts
        """
        return self.core.valkyrie_store.get_permissions(
            request, account_number, contact_id, None)

    effective_accounts_permissions_route = (
        '/account/<int:account_number>'
        '/permissions/contacts/accounts'
        '/by_contact/<int:contact_id>/effective')

    @app.route(effective_accounts_permissions_route, methods=['GET'])
    def effective_accounts_permissions(self, request, account_number,
                                       contact_id):
        """
        Responds with response code 200 and returns a list of account level permissions
        for the given account and contact
        See https://valkyrie.my.rackspace.com/#managed-accounts
        """
        return self.core.valkyrie_store.get_permissions(
            request, account_number, contact_id, 1)

    effective_devices_permissions_route = (
        '/account/<int:account_number>'
        '/permissions/contacts/devices'
        '/by_contact/<int:contact_id>/effective')

    @app.route(effective_devices_permissions_route, methods=['GET'])
    def effective_devices_permissions(self, request, account_number,
                                      contact_id):
        """
        Responds with response code 200 and returns a list of device level permissions
        for the given account and contact
        See https://valkyrie.my.rackspace.com/#managed-accounts
        """
        return self.core.valkyrie_store.get_permissions(
            request, account_number, contact_id, 2)
Beispiel #20
0
class LoadBalancerRegion(object):
    """
    Klein routes for load balancer API methods within a particular region.
    """

    app = MimicApp()

    def __init__(self, api_mock, uri_prefix, session_store, region_name):
        """
        Fetches the load balancer id for a failure, invalid scenarios and
        the count on the number of time 422 should be returned on add node.
        """
        self.uri_prefix = uri_prefix
        self.region_name = region_name
        self._api_mock = api_mock
        self._session_store = session_store

    def session(self, tenant_id):
        """
        Gets a session for a particular tenant, creating one if there isn't
        one.
        """
        return (
            self._session_store.session_for_tenant_id(tenant_id).data_for_api(
                self._api_mock,
                lambda: defaultdict(Region_Tenant_CLBs))[self.region_name])

    @app.route('/v2/<string:tenant_id>/loadbalancers', methods=['POST'])
    def add_load_balancer(self, request, tenant_id):
        """
        Creates a load balancer and adds it to the load balancer store.
        Returns the newly created load balancer with response code 202
        """
        try:
            content = json.loads(request.content.read())
        except ValueError:
            request.setResponseCode(400)
            return json.dumps(invalid_resource("Invalid JSON request body"))

        lb_id = randrange(99999)
        response_data = add_load_balancer(tenant_id, self.session(tenant_id),
                                          content['loadBalancer'], lb_id,
                                          self._session_store.clock.seconds())
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route('/v2/<string:tenant_id>/loadbalancers/<int:lb_id>',
               methods=['GET'])
    def get_load_balancers(self, request, tenant_id, lb_id):
        """
        Returns a list of all load balancers created using mimic with response code 200
        """
        response_data = get_load_balancers(self.session(tenant_id), lb_id,
                                           self._session_store.clock.seconds())
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route('/v2/<string:tenant_id>/loadbalancers', methods=['GET'])
    def list_load_balancers(self, request, tenant_id):
        """
        Returns a list of all load balancers created using mimic with response code 200
        """
        response_data = list_load_balancers(
            tenant_id, self.session(tenant_id),
            self._session_store.clock.seconds())
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route('/v2/<string:tenant_id>/loadbalancers/<int:lb_id>',
               methods=['DELETE'])
    def delete_load_balancer(self, request, tenant_id, lb_id):
        """
        Creates a load balancer and adds it to the load balancer store.
        Returns the newly created load balancer with response code 200
        """
        response_data = del_load_balancer(self.session(tenant_id), lb_id,
                                          self._session_store.clock.seconds())
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route('/v2/<string:tenant_id>/loadbalancers/<int:lb_id>/nodes',
               methods=['POST'])
    def add_node_to_load_balancer(self, request, tenant_id, lb_id):
        """
        Return a successful add node response with code 200
        """
        try:
            content = json.loads(request.content.read())
        except ValueError:
            request.setResponseCode(400)
            return json.dumps(invalid_resource("Invalid JSON request body"))

        node_list = content['nodes']
        response_data = add_node(self.session(tenant_id), node_list, lb_id,
                                 self._session_store.clock.seconds())
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route(
        '/v2/<string:tenant_id>/loadbalancers/<int:lb_id>/nodes/<int:node_id>',
        methods=['GET'])
    def get_nodes(self, request, tenant_id, lb_id, node_id):
        """
        Returns a 200 response code and list of nodes on the load balancer
        """
        response_data = get_nodes(self.session(tenant_id), lb_id, node_id,
                                  self._session_store.clock.seconds())
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route(
        '/v2/<string:tenant_id>/loadbalancers/<int:lb_id>/nodes/<int:node_id>',
        methods=['DELETE'])
    def delete_node_from_load_balancer(self, request, tenant_id, lb_id,
                                       node_id):
        """
        Returns a 204 response code, for any load balancer created using the mocks
        """
        response_data = delete_node(self.session(tenant_id), lb_id, node_id,
                                    self._session_store.clock.seconds())
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route('/v2/<string:tenant_id>/loadbalancers/<int:lb_id>/nodes',
               methods=['GET'])
    def list_nodes_for_load_balancer(self, request, tenant_id, lb_id):
        """
        Returns a 200 response code and list of nodes on the load balancer
        """
        response_data = list_nodes(self.session(tenant_id), lb_id,
                                   self._session_store.clock.seconds())
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])
Beispiel #21
0
class AuthApi(object):
    """
    Rest endpoints for mocked Auth api.
    """

    app = MimicApp()

    def __init__(self, core):
        """
        :param MimicCore core: The core to which this AuthApi will be
            authenticating.
        """
        self.core = core

    @app.route('/v2.0/tokens', methods=['POST'])
    def get_token_and_service_catalog(self, request):
        """
        Return a service catalog consisting of all plugin endpoints and an api
        token.
        """
        try:
            content = json.loads(request.content.read())
        except ValueError:
            request.setResponseCode(400)
            return json.dumps(invalid_resource("Invalid JSON request body"))

        tenant_id = (content['auth'].get('tenantName', None)
                     or content['auth'].get('tenantId', None))

        def format_response(callable_returning_session,
                            nonmatching_tenant_message_generator):
            try:
                session = callable_returning_session()
            except NonMatchingTenantError as e:
                request.setResponseCode(401)
                return json.dumps({
                    "unauthorized": {
                        "code": 401,
                        "message": nonmatching_tenant_message_generator(e)
                    }
                })
            else:
                request.setResponseCode(200)
                prefix_map = {
                    # map of entry to URI prefix for that entry
                }

                def lookup(entry):
                    return prefix_map[entry]

                result = get_token(
                    session.tenant_id,
                    entry_generator=lambda tenant_id: list(
                        self.core.entries_for_tenant(
                            tenant_id, prefix_map,
                            base_uri_from_request(request))),
                    prefix_for_endpoint=lookup,
                    response_token=session.token,
                    response_user_id=session.user_id,
                    response_user_name=session.username,
                )
                return json.dumps(result)

        username_generator = (
            lambda exception: "Tenant with Name/Id: '{0}' is not valid for "
            "User '{1}' (id: '{2}')".format(exception.desired_tenant, exception
                                            .session.username, exception.
                                            session.user_id))

        if content['auth'].get('passwordCredentials'):
            username = content['auth']['passwordCredentials']['username']
            password = content['auth']['passwordCredentials']['password']
            return format_response(
                lambda: self.core.sessions.session_for_username_password(
                    username, password, tenant_id), username_generator)

        elif content['auth'].get('RAX-KSKEY:apiKeyCredentials'):
            username = content['auth']['RAX-KSKEY:apiKeyCredentials'][
                'username']
            api_key = content['auth']['RAX-KSKEY:apiKeyCredentials']['apiKey']
            return format_response(
                lambda: self.core.sessions.session_for_api_key(
                    username, api_key, tenant_id), username_generator)

        elif content['auth'].get('token') and tenant_id:
            token = content['auth']['token']['id']
            return format_response(
                lambda: self.core.sessions.session_for_token(token, tenant_id),
                lambda e: "Token doesn't belong to Tenant with Id/Name: "
                "'{0}'".format(e.desired_tenant))
        else:
            request.setResponseCode(400)
            return json.dumps(invalid_resource("Invalid JSON request body"))

    @app.route('/v1.1/mosso/<string:tenant_id>', methods=['GET'])
    def get_username(self, request, tenant_id):
        """
        Returns response with random usernames.
        """
        request.setResponseCode(301)
        session = self.core.sessions.session_for_tenant_id(tenant_id)
        return json.dumps(dict(user=dict(id=session.username)))

    @app.route('/v2.0/RAX-AUTH/impersonation-tokens', methods=['POST'])
    def get_impersonation_token(self, request):
        """
        Return a token id with expiration.
        """
        request.setResponseCode(200)
        try:
            content = json.loads(request.content.read())
        except ValueError:
            request.setResponseCode(400)
            return json.dumps(invalid_resource("Invalid JSON request body"))

        expires_in = content['RAX-AUTH:impersonation']['expire-in-seconds']
        username = content['RAX-AUTH:impersonation']['user']['username']

        session = self.core.sessions.session_for_impersonation(
            username, expires_in)
        return json.dumps({
            "access": {
                "token": {
                    "id": session.token,
                    "expires": format_timestamp(session.expires)
                }
            }
        })

    @app.route('/v2.0/tokens/<string:token_id>/endpoints', methods=['GET'])
    def get_endpoints_for_token(self, request, token_id):
        """
        Return a service catalog consisting of nova and load balancer mocked
        endpoints.
        """
        # FIXME: TEST
        request.setResponseCode(200)
        prefix_map = {}
        session = self.core.sessions.session_for_token(token_id)
        return json.dumps(
            get_endpoints(session.tenant_id,
                          entry_generator=lambda tenant_id: list(
                              self.core.entries_for_tenant(
                                  tenant_id, prefix_map,
                                  base_uri_from_request(request))),
                          prefix_for_endpoint=prefix_map.get))
Beispiel #22
0
class MimicRoot(object):
    """
    Klein routes for the root of the mimic URI hierarchy.
    """

    app = MimicApp()

    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()

    @app.route("/", methods=["GET"])
    def help(self, request):
        """
        A helpful greeting message.
        """
        request.responseHeaders.setRawHeaders(b"content-type", [b"text/plain"])
        return ("To get started with Mimic, POST an authentication request to:"
                "\n\n/identity/v2.0/tokens\n").encode('utf-8')

    @app.route("/identity", branch=True)
    def get_auth_api(self, request):
        """
        Get the identity ...
        """
        return IdentityApi(self.core,
                           self.identity_behavior_registry).app.resource()

    @app.route("/noit", branch=True)
    def get_noit_api(self, request):
        """
        Mock Noit api here ... until mimic allows services outside of the
        service catalog.
        """
        return NoitApi(self.core, self.clock).app.resource()

    @app.route("/sendgrid/mail.send.json", methods=['POST'])
    def send_grid_api(self, request):
        """
        Mock SendGrid api responds with a 200.
        """
        request.setResponseCode(200)
        return b''

    @app.route("/cloudmonitoring.rackspace.com", branch=True)
    def mailgun_api(self, request):
        """
        Mock Mail Gun API.
        """
        return mailgun_api.MailGunApi(self.core).app.resource()

    @app.route("/fastly", branch=True)
    def get_fastly_api(self, request):
        """
        Get the Fastly API ...
        """
        return fastly_api.FastlyApi(self.core).app.resource()

    @app.route("/v1/customer_accounts/CLOUD", branch=True)
    def get_customer_api(self, request):
        """
        Adds support for the Customer API
        """
        return customer_api.CustomerApi(self.core).app.resource()

    @app.route("/ironic/v1", branch=True)
    def ironic_api(self, request):
        """
        Mock Ironic API.
        """
        return ironic_api.IronicApi(self.core).app.resource()

    @app.route("/valkyrie/v2.0", branch=True)
    def valkyrie_api(self, request):
        """
        Mock Valkyrie API.
        """
        return valkyrie_api.ValkyrieApi(self.core).app.resource()

    @app.route('/mimic/v1.0/presets', methods=['GET'])
    def get_mimic_presets(self, request):
        """
        Return the preset values for mimic
        """
        request.setResponseCode(200)
        return json.dumps(get_presets)

    @app.route("/mimic/v1.1/tick", methods=['POST'])
    def advance_time(self, request):
        """
        Advance time by the given number of seconds.
        """
        body = json_from_request(request)
        amount = body['amount']
        self.clock.advance(amount)
        request.setResponseCode(200)
        return json.dumps({
            "advanced": amount,
            "now": seconds_to_timestamp(self.clock.seconds())
        })

    @app.route("/mimic/v1.1/IdentityControlAPI/behaviors", branch=True)
    def handle_identity_behaviors(self, request):
        """
        Handle creating/deleting behaviors for mimic identity.
        """
        api = AuthControlApiBehaviors(self.identity_behavior_registry)
        return api.app.resource()

    @app.route("/mimicking/<string:service_id>/<string:region_name>",
               branch=True)
    def get_service_resource(self, request, service_id, region_name):
        """
        Based on the URL prefix of a region and a service, where the region is
        an identifier (like ORD, DFW, etc) and service is a
        dynamically-generated UUID for a particular plugin, retrieve the
        resource associated with that service.
        """
        service_object = self.core.service_with_region(
            region_name, service_id, base_uri_from_request(request))

        if service_object is None:
            # workaround for https://github.com/twisted/klein/issues/56
            return NoResource()
        return service_object

    @app.route('/domain/', methods=['GET'])
    def get_domain_mocks(self, request):
        """
        :return: The list of all domain mocks
        :rtype: Bytes of JSON serialized list
        """
        request.setResponseCode(200)
        return json.dumps([mock.domain() for mock in self.core.domains])

    @app.route('/domain/<string:domain>', branch=True)
    def get_domain_resource(self, request, domain):
        """
        Retrieve a resource corresponding to a :obj:`IAPIDomainMock` for the
        named domain.
        """
        for mock in self.core.domains:
            if mock.domain() == domain:
                request.setResponseCode(200)
                return mock.resource()
        else:
            request.setResponseCode(404)
            request.setHeader(b"content-type", b"text/plain; charset=utf-8")
            return b"No such domain."

    @app.route("/glance", branch=True)
    def glance_admin_api(self, request):
        """
        Mock for the glance admin api
        """
        return glance_api.GlanceAdminApi(self.core).app.resource()
Beispiel #23
0
class NoitApi(object):
    """
    Rest endpoints for mocked Noit api.
    """

    app = MimicApp()

    def __init__(self, core, clock):
        """
        :param MimicCore core: The core to which this NoitApi will be
            communicating.
        """
        self.core = core
        self.clock = clock

    def validate_check_payload(self, request):
        """
        Validate the check request payload and returns the response code
        """
        content = str(request.content.read())
        try:
            payload = xmltodict.parse(content)
        except:
            return 500, None
        attributes = [
            "name", "module", "target", "period", "timeout", "filterset"
        ]
        for each in attributes:
            if not payload["check"]["attributes"].get(each):
                return 404, None
        return 200, payload["check"]["attributes"]

    @app.route('/checks/test', methods=['POST'])
    def test_check(self, request):
        """
        Validates the check xml and returns the metrics
        """
        response = self.validate_check_payload(request)
        if (response[0] == 200):
            request.setHeader("content-type", "application/xml")
            response_body = test_check(response[1]["module"])
            return xmltodict.unparse(response_body)
        request.setResponseCode(response[0])
        return

    @app.route('/checks/set/<check_id>', methods=['PUT'])
    def set_check(self, request, check_id):
        """
        Creates a check for the given check_id. If the check_id already
        exists, then it updates that check.
        TBD: Include error 400 and 500s. Module cannot be updated (test
            against noit service to see the response code expected)
        """
        try:
            UUID(check_id)
        except (ValueError, AttributeError):
            request.setResponseCode(500)
            return
        request.setHeader("content-type", "application/xml")
        response = self.validate_check_payload(request)
        request.setResponseCode(response[0])
        if (response[0] == 200):
            response_body = create_check(response[1], check_id)
            return xmltodict.unparse(response_body)
        return

    @app.route('/checks/show/<check_id>', methods=['GET'])
    def get_checks(self, request, check_id):
        """
        Return the current configuration and state of the specified check.
        """
        request.setHeader("content-type", "application/xml")
        return xmltodict.unparse(get_check(check_id))

    @app.route('/config/checks', methods=['GET'])
    def get_all_checks(self, request):
        """
        Return the current configuration and state of all checks.
        """
        request.setHeader("content-type", "application/xml")
        return xmltodict.unparse(get_all_checks())

    @app.route('/checks/delete/<check_id>', methods=['DELETE'])
    def delete_checks(self, request, check_id):
        """
        Delete the specified check and return 204 response code
        """
        response_code = delete_check(check_id) or 200
        request.setResponseCode(response_code)
        request.setHeader("content-type", "application/xml")
        return
Beispiel #24
0
class SwiftTenantInRegion(object):
    """
    A :obj:`SwiftTenantInRegion` represents a single tenant and their
    associated storage resources within one region.
    """

    app = MimicApp()

    def __init__(self):
        """
        Initialize a tenant with some containers.
        """
        self.containers = {}

    @app.route("/<string:container_name>", methods=["PUT"])
    def create_container(self, request, container_name):
        """
        Api call to create and save container.  HTTP status code of 201 if
        created, else returns 202.
        """
        if container_name not in self.containers:
            self.containers[container_name] = Container(name=container_name)
            request.setResponseCode(CREATED)
        else:
            request.setResponseCode(ACCEPTED)
        return b""

    @app.route("/<string:container_name>", methods=["GET"])
    def get_container(self, request, container_name):
        """
        Api call to get a container, given the name of the container.  HTTP
        status code of 200 when such a container exists, 404 if not.
        """
        if container_name in self.containers:
            request.responseHeaders.setRawHeaders("content-type",
                                                  ["application/json"])
            request.responseHeaders.setRawHeaders("x-container-object-count",
                                                  ["0"])
            request.responseHeaders.setRawHeaders("x-container-bytes-used",
                                                  ["0"])
            request.setResponseCode(OK)
            return dumps([
                obj.as_json()
                for obj in self.containers[container_name].objects.values()
            ])
        else:
            return NoResource()

    @app.route("/<string:container_name>/<string:object_name>",
               methods=["GET"])
    def get_object(self, request, container_name, object_name):
        """
        Get an object from a container.
        """
        return self.containers[container_name].objects[object_name].data

    @app.route("/<string:container_name>/<string:object_name>",
               methods=["PUT"])
    def put_object(self, request, container_name, object_name):
        """
        Create or update an object in a container.
        """
        request.setResponseCode(201)
        container = self.containers[container_name]
        content_type = request.requestHeaders.getRawHeaders('content-type')[0]
        container.objects[object_name] = Object(name=object_name,
                                                data=request.content.read(),
                                                content_type=content_type)
        return b''
Beispiel #25
0
class NovaRegion(object):
    """
    Klein routes for the API within a Cloud Servers region.

    :ivar dict _tenant_cache: a mapping of tenant_id (bytes) to a "server
        cache" (:obj:`S_Cache`), which itself maps server_id to a
        JSON-serializable data structure of the 'server' key of GET responses.
    """
    def __init__(self, api_mock, uri_prefix, session_store, name):
        """
        Create a nova region with a given URI prefix (used for generating URIs
        to servers).
        """
        self.uri_prefix = uri_prefix
        self._api_mock = api_mock
        self._session_store = session_store
        self._name = name

    def _server_cache_for_tenant(self, tenant_id):
        """
        Get the given server-cache object for the given tenant, creating one if
        there isn't one.
        """
        return (
            self._session_store.session_for_tenant_id(tenant_id).data_for_api(
                self._api_mock,
                lambda: collections.defaultdict(S_Cache))[self._name])

    app = MimicApp()

    @app.route('/v2/<string:tenant_id>/servers', methods=['POST'])
    def create_server(self, request, tenant_id):
        """
        Returns a generic create server response, with status 'ACTIVE'.
        """
        try:
            content = json.loads(request.content.read())
        except ValueError:
            request.setResponseCode(400)
            return json.dumps(invalid_resource("Invalid JSON request body"))

        server_id = 'test-server{0}-id-{0}'.format(str(randrange(9999999999)))
        response_data = create_server(
            tenant_id,
            content['server'],
            server_id,
            self.uri_prefix,
            s_cache=self._server_cache_for_tenant(tenant_id),
            current_time=seconds_to_timestamp(
                self._session_store.clock.seconds()))
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route('/v2/<string:tenant_id>/servers/<string:server_id>',
               methods=['GET'])
    def get_server(self, request, tenant_id, server_id):
        """
        Returns a generic get server response, with status 'ACTIVE'
        """
        response_data = get_server(server_id,
                                   self._server_cache_for_tenant(tenant_id),
                                   self._session_store.clock.seconds())
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route('/v2/<string:tenant_id>/servers', methods=['GET'])
    def list_servers(self, request, tenant_id):
        """
        Returns list of servers that were created by the mocks, with the given
        name.
        """
        return _list_servers(
            request,
            tenant_id,
            s_cache=self._server_cache_for_tenant(tenant_id),
            current_timestamp=self._session_store.clock.seconds())

    @app.route('/v2/<string:tenant_id>/servers/detail', methods=['GET'])
    def list_servers_with_details(self, request, tenant_id):
        """
        Returns list of servers that were created by the mocks, with details
        such as the metadata.
        """
        return _list_servers(
            request,
            tenant_id,
            details=True,
            s_cache=self._server_cache_for_tenant(tenant_id),
            current_timestamp=self._session_store.clock.seconds())

    @app.route('/v2/<string:tenant_id>/servers/<string:server_id>',
               methods=['DELETE'])
    def delete_server(self, request, tenant_id, server_id):
        """
        Returns a 204 response code, for any server id'
        """
        response_data = delete_server(
            server_id, s_cache=self._server_cache_for_tenant(tenant_id))
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route('/v2/<string:tenant_id>/images/<string:image_id>',
               methods=['GET'])
    def get_image(self, request, tenant_id, image_id):
        """
        Returns a get image response, for any given imageid
        """
        response_data = get_image(
            image_id, s_cache=self._server_cache_for_tenant(tenant_id))
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route('/v2/<string:tenant_id>/flavors/<string:flavor_id>',
               methods=['GET'])
    def get_flavor(self, request, tenant_id, flavor_id):
        """
        Returns a get flavor response, for any given flavorid
        """
        response_data = get_flavor(
            flavor_id, s_cache=self._server_cache_for_tenant(tenant_id))
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route('/v2/<string:tenant_id>/limits', methods=['GET'])
    def get_limit(self, request, tenant_id):
        """
        Returns a get flavor response, for any given flavorid
        """
        request.setResponseCode(200)
        return json.dumps(
            get_limit(s_cache=self._server_cache_for_tenant(tenant_id)))

    @app.route('/v2/<string:tenant_id>/servers/<string:server_id>/ips',
               methods=['GET'])
    def get_ips(self, request, tenant_id, server_id):
        """
        Returns a get flavor response, for any given flavorid.
        (currently the GET ips works only after a GET server after the server is created)
        """
        response_data = list_addresses(
            server_id, s_cache=self._server_cache_for_tenant(tenant_id))
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])
Beispiel #26
0
class NovaRegion(object):
    """
    Klein routes for the API within a Cloud Servers region.

    :ivar dict _tenant_cache: a mapping of tenant_id (bytes) to a "server
        cache" (:obj:`S_Cache`), which itself maps server_id to a
        JSON-serializable data structure of the 'server' key of GET responses.
    """
    def __init__(self, api_mock, uri_prefix, session_store, name):
        """
        Create a nova region with a given URI prefix (used for generating URIs
        to servers).
        """
        self.uri_prefix = uri_prefix
        self._api_mock = api_mock
        self._session_store = session_store
        self._name = name

    def url(self, suffix):
        """
        Generate a URL to an object within the Nova URL hierarchy, given the
        part of the URL that comes after.
        """
        return str(URLPath.fromString(self.uri_prefix).child(suffix))

    def _region_collection_for_tenant(self, tenant_id):
        """
        Get the given server-cache object for the given tenant, creating one if
        there isn't one.
        """
        return (self._api_mock._get_session(
            self._session_store, tenant_id).collection_for_region(self._name))

    app = MimicApp()

    @app.route('/v2/<string:tenant_id>/servers', methods=['POST'])
    def create_server(self, request, tenant_id):
        """
        Returns a generic create server response, with status 'ACTIVE'.
        """
        try:
            content = json.loads(request.content.read())
        except ValueError:
            request.setResponseCode(400)
            return json.dumps(invalid_resource("Invalid JSON request body"))

        return (self._region_collection_for_tenant(tenant_id).request_creation(
            request, content, self.url))

    @app.route('/v2/<string:tenant_id>/servers/<string:server_id>',
               methods=['GET'])
    def get_server(self, request, tenant_id, server_id):
        """
        Returns a generic get server response, with status 'ACTIVE'
        """
        return (self._region_collection_for_tenant(tenant_id).request_read(
            request, server_id, self.url))

    @app.route('/v2/<string:tenant_id>/servers', methods=['GET'])
    def list_servers(self, request, tenant_id):
        """
        Returns list of servers that were created by the mocks, with the given
        name.
        """
        return (self._region_collection_for_tenant(tenant_id).request_list(
            request,
            include_details=False,
            absolutize_url=self.url,
            name=request.args.get('name', [u""])[0]))

    @app.route('/v2/<string:tenant_id>/servers/detail', methods=['GET'])
    def list_servers_with_details(self, request, tenant_id):
        """
        Returns list of servers that were created by the mocks, with details
        such as the metadata.
        """
        return (self._region_collection_for_tenant(tenant_id).request_list(
            request,
            include_details=True,
            absolutize_url=self.url,
            name=request.args.get('name', [u""])[0]))

    @app.route('/v2/<string:tenant_id>/servers/<string:server_id>',
               methods=['DELETE'])
    def delete_server(self, request, tenant_id, server_id):
        """
        Returns a 204 response code, for any server id'
        """
        return (self._region_collection_for_tenant(tenant_id).request_delete(
            request, server_id))

    @app.route('/v2/<string:tenant_id>/images/<string:image_id>',
               methods=['GET'])
    def get_image(self, request, tenant_id, image_id):
        """
        Returns a get image response, for any given imageid
        """
        response_data = get_image(image_id)
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route('/v2/<string:tenant_id>/flavors/<string:flavor_id>',
               methods=['GET'])
    def get_flavor(self, request, tenant_id, flavor_id):
        """
        Returns a get flavor response, for any given flavorid
        """
        response_data = get_flavor(flavor_id)
        request.setResponseCode(response_data[1])
        return json.dumps(response_data[0])

    @app.route('/v2/<string:tenant_id>/limits', methods=['GET'])
    def get_limit(self, request, tenant_id):
        """
        Returns a get flavor response, for any given flavorid
        """
        request.setResponseCode(200)
        return json.dumps(get_limit())

    @app.route('/v2/<string:tenant_id>/servers/<string:server_id>/ips',
               methods=['GET'])
    def get_ips(self, request, tenant_id, server_id):
        """
        Returns the IP addresses for the specified server.
        """
        return (self._region_collection_for_tenant(tenant_id).request_ips(
            request, server_id))
Beispiel #27
0
class IdentityApi(object):
    """
    Rest endpoints for mocked Auth api.

    :ivar core: an instance of :class:`mimic.core.MimicCore`
    :ivar registry_collection: an instance of
        :class:`mimic.model.behaviors.BehaviorRegistryCollection`
    :ivar apikey_length: the string length of any generated apikeys
    """
    core = attr.ib(validator=attr.validators.instance_of(MimicCore))
    registry_collection = attr.ib(
        validator=attr.validators.instance_of(BehaviorRegistryCollection))
    app = MimicApp()

    apikey_length = 32

    @classmethod
    def make_apikey(cls):
        """
        Generate an API key
        """
        # length of the final APIKey value
        generation_length = int(cls.apikey_length / 2)
        return text_type(
            binascii.hexlify(os.urandom(generation_length)).decode('utf-8'))

    @app.route('/v2.0', methods=['GET'])
    def get_version(self, request):
        """
        Returns keystone version.
        """
        base_uri = base_uri_from_request(request)
        return json.dumps(get_version_v2(base_uri))

    @app.route('/v2.0/tokens', methods=['POST'])
    def get_token_and_service_catalog(self, request):
        """
        Return a service catalog consisting of all plugin endpoints and an api
        token.
        """
        try:
            content = json_from_request(request)
        except ValueError:
            pass
        else:
            for cred_type in (PasswordCredentials, APIKeyCredentials,
                              TokenCredentials):
                if cred_type.type_key in content['auth']:
                    try:
                        cred = cred_type.from_json(content)
                    except (KeyError, TypeError):
                        pass
                    else:
                        registry = self.registry_collection.registry_by_event(
                            authentication)
                        behavior = registry.behavior_for_attributes(
                            attr.asdict(cred))
                        return behavior(self.core, request, cred)

        request.setResponseCode(400)
        return json.dumps(invalid_resource("Invalid JSON request body"))

    @app.route('/v1.1/mosso/<string:tenant_id>', methods=['GET'])
    def get_username(self, request, tenant_id):
        """
        Returns response with random usernames.
        """
        request.setResponseCode(301)
        session = self.core.sessions.session_for_tenant_id(tenant_id)
        return json.dumps(dict(user=dict(id=session.username)))

    @app.route('/v2.0/users', methods=['GET'])
    def get_users_details(self, request):
        """
        Returns response with  detailed account information about each user
        including email, name, user ID, account configuration and status
        information.
        """
        username = request.args.get(b"name")[0].decode("utf-8")
        session = self.core.sessions.session_for_username_password(
            username, "test")
        return json.dumps(
            dict(
                user={
                    "RAX-AUTH:domainId": session.tenant_id,
                    "id": session.user_id,
                    "enabled": True,
                    "username": session.username,
                    "email": "*****@*****.**",
                    "RAX-AUTH:defaultRegion": "ORD",
                    "created": seconds_to_timestamp(time.time()),
                    "updated": seconds_to_timestamp(time.time())
                }))

    @app.route('/v2.0/users/<string:user_id>/OS-KSADM/credentials',
               methods=['GET'])
    def get_user_credentials_osksadm(self, request, user_id):
        """
        Support, such as it is, for the credentials call.

        `OpenStack Identity v2 Extension List Credentials
        <http://developer.openstack.org/api-ref-identity-v2-ext.html#listCredentials>`_
        """
        if user_id in self.core.sessions._userid_to_session:
            username = self.core.sessions._userid_to_session[user_id].username
            apikey = self.make_apikey()
            return json.dumps({
                'credentials': [{
                    'RAX-KSKEY:apiKeyCredentials': {
                        'username': username,
                        'apiKey': apikey
                    }
                }],
                "credentials_links": []
            })
        else:
            request.setResponseCode(404)
            return json.dumps({
                'itemNotFound': {
                    'code': 404,
                    'message': 'User ' + user_id + ' not found'
                }
            })

    @app.route(
        '/v2.0/users/<string:user_id>/OS-KSADM/credentials/RAX-KSKEY:apiKeyCredentials',
        methods=['GET'])
    def rax_kskey_apikeycredentials(self, request, user_id):
        """
        Support, such as it is, for the apiKeysCredentials call.

        reference: https://developer.rackspace.com/docs/cloud-identity/v2/api-reference/users-operations/#get-user-credentials  # noqa
        """
        if user_id in self.core.sessions._userid_to_session:
            username = self.core.sessions._userid_to_session[user_id].username
            apikey = self.make_apikey()
            return json.dumps({
                'RAX-KSKEY:apiKeyCredentials': {
                    'username': username,
                    'apiKey': apikey
                }
            })
        else:
            return json.dumps(
                not_found('User ' + user_id + ' not found', request))

    @app.route('/v2.0/RAX-AUTH/impersonation-tokens', methods=['POST'])
    def get_impersonation_token(self, request):
        """
        Return a token id with expiration.
        """
        request.setResponseCode(200)
        try:
            content = json_from_request(request)
        except ValueError:
            request.setResponseCode(400)
            return json.dumps(invalid_resource("Invalid JSON request body"))

        x_auth_token = request.getHeader(b"x-auth-token")
        if x_auth_token is not None:
            x_auth_token = x_auth_token.decode("utf-8")
        cred = ImpersonationCredentials.from_json(content, x_auth_token)
        registry = self.registry_collection.registry_by_event(authentication)
        behavior = registry.behavior_for_attributes({
            "token":
            cred.impersonator_token,
            "username":
            cred.impersonated_username
        })
        return behavior(self.core, request, cred)

    @app.route('/v2.0/tokens/<string:token_id>', methods=['GET'])
    def validate_token(self, request, token_id):
        """
        Creates a new session for the given tenant_id and token_id
        and always returns response code 200.
        `OpenStack Identity v2 Admin Validate Token
        <http://developer.openstack.org/api-ref-identity-admin-v2.html#admin-validateToken>`_
        """
        request.setResponseCode(200)
        session = None

        # Attempt to get the session based on tenant_id+token if the optional
        # tenant_id is provided; if tenant_id is not provided, then just look
        # it up based on the token.
        tenant_id = request.args.get(b'belongsTo')
        if tenant_id is not None:
            tenant_id = tenant_id[0].decode("utf-8")
            session = self.core.sessions.session_for_tenant_id(
                tenant_id, token_id)

        else:
            session = self.core.sessions.session_for_token(token_id)

        response = get_token(
            session.tenant_id,
            response_token=session.token,
            response_user_id=session.user_id,
            response_user_name=session.username,
        )
        if session.impersonator_session_for_token(token_id) is not None:
            impersonator_session = session.impersonator_session_for_token(
                token_id)
            response["access"][
                "RAX-AUTH:impersonator"] = impersonator_user_role(
                    impersonator_session.user_id,
                    impersonator_session.username)

        if token_id in get_presets["identity"]["token_fail_to_auth"]:
            # This is returning a 401 Unauthorized message but in a 404 not_found
            # JSON data format. Is there a reason for this? An old OpenStack bug?
            request.setResponseCode(401)
            return json.dumps({
                'itemNotFound': {
                    'code': 401,
                    'message': 'Invalid auth token'
                }
            })

        imp_token = get_presets["identity"]["maas_admin_roles"]
        racker_token = get_presets["identity"]["racker_token"]
        if token_id in imp_token:
            response["access"]["RAX-AUTH:impersonator"] = {
                "id":
                response["access"]["user"]["id"],
                "name":
                response["access"]["user"]["name"],
                "roles": [{
                    "id": "123",
                    "name": "monitoring:service-admin"
                }, {
                    "id": "234",
                    "name": "object-store:admin"
                }]
            }
        if token_id in racker_token:
            response["access"]["RAX-AUTH:impersonator"] = {
                "id": response["access"]["user"]["id"],
                "name": response["access"]["user"]["name"],
                "roles": [{
                    "id": "9",
                    "name": "Racker"
                }]
            }
        if tenant_id in get_presets["identity"]["observer_role"]:
            response["access"]["user"]["roles"] = [{
                "id": "observer",
                "description": "Global Observer Role.",
                "name": "observer"
            }]
        if tenant_id in get_presets["identity"]["creator_role"]:
            response["access"]["user"]["roles"] = [{
                "id": "creator",
                "description": "Global Creator Role.",
                "name": "creator"
            }]
        if tenant_id in get_presets["identity"]["admin_role"]:
            response["access"]["user"]["roles"] = [{
                "id": "admin",
                "description": "Global Admin Role.",
                "name": "admin"
            }, {
                "id": "observer",
                "description": "Global Observer Role.",
                "name": "observer"
            }]

        # Canned responses to be removed ...

        if token_id in get_presets["identity"]["non_dedicated_observer"]:
            response["access"]["token"]["tenant"] = {
                "id": "135790",
                "name": "135790",
            }
            response["access"]["user"] = {
                "id":
                "12",
                "name":
                "OneTwo",
                "roles": [{
                    "id": "1",
                    "name": "monitoring:observer",
                    "description": "Monitoring Observer"
                }]
            }

        if token_id in get_presets["identity"]["non_dedicated_admin"]:
            response["access"]["token"]["tenant"] = {
                "id": "135790",
                "name": "135790",
            }
            response["access"]["user"] = {
                "id":
                "34",
                "name":
                "ThreeFour",
                "roles": [{
                    "id": "1",
                    "name": "monitoring:admin",
                    "description": "Monitoring Admin"
                }, {
                    "id": "2",
                    "name": "admin",
                    "description": "Admin"
                }]
            }

        if token_id in get_presets["identity"]["non_dedicated_impersonator"]:
            response["access"]["token"]["tenant"] = {
                "id": "135790",
                "name": "135790",
            }
            response["access"]["user"] = {
                "id":
                "34",
                "name":
                "ThreeFour",
                "roles": [{
                    "id": "1",
                    "name": "identity:nobody",
                    "description": "Nobody"
                }]
            }
            response["access"]["RAX-AUTH:impersonator"] = {
                "id":
                response["access"]["user"]["id"],
                "name":
                response["access"]["user"]["name"],
                "roles": [{
                    "id": "1",
                    "name": "monitoring:service-admin"
                }, {
                    "id": "2",
                    "name": "object-store:admin"
                }]
            }

        if token_id in get_presets["identity"]["non_dedicated_racker"]:
            response["access"]["token"]["tenant"] = {
                "id": "135790",
                "name": "135790",
            }
            response["access"]["user"] = {
                "id":
                "34",
                "name":
                "ThreeFour",
                "roles": [{
                    "id": "1",
                    "name": "identity:nobody",
                    "description": "Nobody"
                }]
            }
            response["access"]["RAX-AUTH:impersonator"] = {
                "id": response["access"]["user"]["id"],
                "name": response["access"]["user"]["name"],
                "roles": [{
                    "id": "1",
                    "name": "Racker"
                }]
            }

        if token_id in get_presets["identity"][
                "dedicated_full_device_permission_holder"]:
            response["access"]["token"]["tenant"] = {
                "id": "hybrid:123456",
                "name": "hybrid:123456",
            }
            response["access"]["user"] = {
                "id":
                "12",
                "name":
                "HybridOneTwo",
                "roles": [{
                    "id": "1",
                    "name": "monitoring:observer",
                    "tenantId": "hybrid:123456"
                }],
                "RAX-AUTH:contactId":
                "12"
            }

        if token_id in get_presets["identity"][
                "dedicated_account_permission_holder"]:
            response["access"]["token"]["tenant"] = {
                "id": "hybrid:123456",
                "name": "hybrid:123456",
            }
            response["access"]["user"] = {
                "id":
                "34",
                "name":
                "HybridThreeFour",
                "roles": [{
                    "id": "1",
                    "name": "monitoring:creator",
                    "description": "Monitoring Creator"
                }, {
                    "id": "2",
                    "name": "creator",
                    "description": "Creator"
                }],
                "RAX-AUTH:contactId":
                "34"
            }

        if token_id in get_presets["identity"][
                "dedicated_limited_device_permission_holder"]:
            response["access"]["token"]["tenant"] = {
                "id": "hybrid:123456",
                "name": "hybrid:123456",
            }
            response["access"]["user"] = {
                "id":
                "56",
                "name":
                "HybridFiveSix",
                "roles": [{
                    "id": "1",
                    "name": "monitoring:observer",
                    "description": "Monitoring Observer"
                }, {
                    "id": "2",
                    "name": "observer",
                    "description": "Observer"
                }],
                "RAX-AUTH:contactId":
                "56"
            }

        if token_id in get_presets["identity"]["dedicated_racker"]:
            response["access"]["token"]["tenant"] = {
                "id": "hybrid:123456",
                "name": "hybrid:123456",
            }
            response["access"]["user"] = {
                "id":
                "12",
                "name":
                "HybridOneTwo",
                "roles": [{
                    "id": "1",
                    "name": "identity:nobody",
                    "description": "Nobody"
                }],
                "RAX-AUTH:contactId":
                "12"
            }
            response["access"]["RAX-AUTH:impersonator"] = {
                "id": response["access"]["user"]["id"],
                "name": response["access"]["user"]["name"],
                "roles": [{
                    "id": "1",
                    "name": "Racker"
                }]
            }

        if token_id in get_presets["identity"]["dedicated_impersonator"]:
            response["access"]["token"]["tenant"] = {
                "id": "hybrid:123456",
                "name": "hybrid:123456",
            }
            response["access"]["user"] = {
                "id":
                "34",
                "name":
                "HybridThreeFour",
                "roles": [{
                    "id": "1",
                    "name": "identity:nobody",
                    "description": "Nobody"
                }],
                "RAX-AUTH:contactId":
                "34"
            }
            response["access"]["RAX-AUTH:impersonator"] = {
                "id": response["access"]["user"]["id"],
                "name": response["access"]["user"]["name"],
                "roles": [{
                    "id": "1",
                    "name": "monitoring:service-admin"
                }]
            }

        if token_id in get_presets["identity"][
                "dedicated_non_permission_holder"]:
            response["access"]["token"]["tenant"] = {
                "id": "hybrid:123456",
                "name": "hybrid:123456",
            }
            response["access"]["user"] = {
                "id":
                "78",
                "name":
                "HybridSevenEight",
                "roles": [{
                    "id": "1",
                    "name": "identity:user-admin",
                    "description": "User admin"
                }],
                "RAX-AUTH:contactId":
                "78"
            }

        if token_id in get_presets["identity"][
                "dedicated_quasi_user_impersonator"]:
            response["access"]["token"]["tenant"] = {
                "id": "hybrid:123456",
                "name": "hybrid:123456",
            }
            response["access"]["user"] = {
                "id":
                "90",
                "name":
                "HybridNineZero",
                "roles": [{
                    "id": "1",
                    "name": "identity:user-admin",
                    "description": "Admin"
                }, {
                    "id": "3",
                    "name": "hybridRole",
                    "description": "Hybrid Admin",
                    "tenantId": "hybrid:123456"
                }]
            }
            response["access"]["RAX-AUTH:impersonator"] = {
                "id": response["access"]["user"]["id"],
                "name": response["access"]["user"]["name"],
                "roles": [{
                    "id": "1",
                    "name": "monitoring:service-admin"
                }]
            }

        return json.dumps(response)

    @app.route('/v2.0/tokens/<string:token_id>/endpoints', methods=['GET'])
    def get_endpoints_for_token(self, request, token_id):
        """
        Return a service catalog consisting of nova and load balancer mocked
        endpoints.

        `OpenStack Identity v2 Admin Endpoints for Token
        <http://developer.openstack.org/api-ref/identity/v2-admin/#list-endoints-for-token>`_
        """
        # FIXME: TEST
        request.setResponseCode(200)
        prefix_map = {}
        session = self.core.sessions.session_for_token(token_id)
        return json.dumps(
            get_endpoints(session.tenant_id,
                          entry_generator=lambda tenant_id: list(
                              self.core.entries_for_tenant(
                                  tenant_id, prefix_map,
                                  base_uri_from_request(request))),
                          prefix_for_endpoint=prefix_map.get))

    @app.route('/v2.0/tenants', methods=['GET'])
    def list_tenants(self, request):
        """
        List all tenants for the specified auth token.

        The token for this call is specified in the X-Auth-Token header,
        like using the services in the service catalog. Mimic supports
        only one tenant per session, so the number of listed tenants is
        always 1 if the call succeeds.

        For more information about this call, refer to the `Rackspace Cloud
        Identity Developer Guide
        <https://developer.rackspace.com/docs/cloud-identity/v2/developer-guide/#list-tenants>`_
        """
        try:
            sess = self.core.sessions.existing_session_for_token(
                request.getHeader(b'x-auth-token').decode('utf-8'))
            return json.dumps({
                'tenants': [{
                    'id': sess.tenant_id,
                    'name': sess.tenant_id,
                    'enabled': True
                }]
            })
        except KeyError:
            request.setResponseCode(401)
            return json.dumps({
                'unauthorized': {
                    'code':
                    401,
                    'message':
                    ("No valid token provided. Please use the 'X-Auth-Token'"
                     " header with a valid token.")
                }
            })

    @app.route('/v2.0/services', methods=['GET'])
    @require_auth_token
    def list_external_api_services(self, request):
        """
        List the available external services that endpoint templates
        may be added to.

        .. note:: Does not implement the limits or markers.
        `OpenStack Identity v2 OS-KSADM List Services
        <http://developer.openstack.org/api-ref/identity/v2-ext/index.html#list-services-admin-extension>`_
        """
        request.setResponseCode(200)
        return json.dumps({
            "OS-KSADM:services": [{
                "name": api.name_key,
                "type": api.type_key,
                "id": api.uuid_key,
                "description": api.description
            } for api in [
                self.core.get_external_api(api_id)
                for api_id in self.core.get_external_apis()
            ]]
        })

    @app.route('/v2.0/services', methods=['POST'])
    @require_auth_token
    def create_external_api_service(self, request):
        """
        Create a new external api service that endpoint templates
        may be added to.

        .. note:: Only requires 'name' and 'type' fields in the JSON. If the 'id'
            or 'description' fields are present, then they will be used;
            otherwise a UUID4 will be assigned to the 'id' field and the
            'description' will be given a generic value.
        `OpenStack Identity v2 OS-KSADM Create Service
        <http://developer.openstack.org/api-ref/identity/v2-ext/index.html#create-service-admin-extension>`_
        """
        try:
            content = json_from_request(request)
        except ValueError:
            return json.dumps(bad_request("Invalid JSON request body",
                                          request))

        try:
            service_name = content['name']
            service_type = content['type']
        except KeyError:
            return json.dumps(
                bad_request(
                    "Invalid Content. 'name' and 'type' fields are required.",
                    request))

        try:
            service_id = content['id']
        except KeyError:
            service_id = text_type(uuid.uuid4())

        try:
            service_description = content['description']
        except KeyError:
            service_description = u"External API referenced by Mimic"

        if service_id in self.core.get_external_apis():
            return json.dumps(
                conflict(
                    "Conflict: Service with the same uuid already exists.",
                    request))

        try:
            self.core.add_api(
                ExternalApiStore(service_id,
                                 service_name,
                                 service_type,
                                 description=service_description))
        except ServiceNameExists:
            return json.dumps(
                conflict(
                    "Conflict: Service with the same name already exists.",
                    request))
        else:
            request.setResponseCode(201)
            return b''

    @app.route('/v2.0/services/<string:service_id>', methods=['DELETE'])
    @require_auth_token
    def delete_external_api_service(self, request, service_id):
        """
        Delete/Remove an existing  external service api. It must not have
        any endpoint templates assigned to it for success.

        `OpenStack Identity v2 OS-KSADM Delete Service
        <http://developer.openstack.org/api-ref/identity/v2-ext/index.html#delete-service-admin-extension>`_
        """
        try:
            self.core.remove_external_api(service_id)
        except ServiceDoesNotExist:
            return json.dumps(
                not_found("Service not found. Unable to remove.", request))
        except ServiceHasTemplates:
            return json.dumps(
                conflict("Service still has endpoint templates.", request))
        else:
            request.setResponseCode(204)
            return b''

    @app.route('/v2.0/OS-KSCATALOG/endpointTemplates', methods=['GET'])
    @require_auth_token
    def list_endpoint_templates(self, request):
        """
        List the available endpoint templates.

        .. note:: Marker/Limit capability not implemented here.

        `OpenStack Identity v2 OS-KSCATALOG List Endpoint Templates
        <http://developer.openstack.org/api-ref-identity-v2-ext.html>`_
        """
        # caller may provide a specific API to list by setting the
        # serviceid header
        external_apis_to_list = []
        service_id = request.getHeader(b'serviceid')
        if service_id is not None:
            external_apis_to_list = [service_id.decode('utf-8')]
        else:
            external_apis_to_list = [
                api_id for api_id in self.core.get_external_apis()
            ]

        try:
            data = []
            request.setResponseCode(200)
            for api_id in external_apis_to_list:
                api = self.core.get_external_api(api_id)
                for endpoint_template in api.list_templates():
                    data.append(endpoint_template.serialize())

            return json.dumps({
                "OS-KSCATALOG": data,
                "OS-KSCATALOG:endpointsTemplates_links": []
            })
        except ServiceDoesNotExist:
            request.setResponseCode(404)
            return json.dumps(
                not_found("Unable to find the requested API", request))

    @app.route('/v2.0/OS-KSCATALOG/endpointTemplates', methods=['POST'])
    @require_auth_token
    def add_endpoint_templates(self, request):
        """
        Add an API endpoint template to the system. By default the API
        described by the template will disabled for all users.

        .. note:: Either the service-id must be specified in the header or
            a Service Name by the same name must already exist. Otherwise
            a Not Found (404) will be returned.

        .. note:: A template has certain required parametes. For Mimic the
            id, name, type, and region parameters are required. See
            EndpointTemplateStore.required_mapping for details. Other
            implementations may have different requirements.

        `OpenStack Identity v2 OS-KSCATALOG Create Endpoint Template
        <http://developer.openstack.org/api-ref-identity-v2-ext.html>`_
        """
        try:
            content = json_from_request(request)
        except ValueError:
            return json.dumps(bad_request("Invalid JSON request body",
                                          request))

        try:
            endpoint_template_instance = EndpointTemplateStore.deserialize(
                content)
        except InvalidEndpointTemplateMissingKey as ex:
            return json.dumps(
                bad_request(
                    "JSON body does not contain the required parameters: " +
                    text_type(ex), request))

        # Access the Service ID that tells which External API
        # is to support this template.
        service_id = request.getHeader(b'serviceid')
        if service_id is not None:
            service_id = service_id.decode('utf-8')

        # Check all existing External APIs for the API ID
        # to ensure that none of them contain it already. The
        # value must be unique.
        for api_id in self.core.get_external_apis():
            api = self.core.get_external_api(api_id)
            if api.has_template(endpoint_template_instance.id_key):
                return json.dumps(
                    conflict(
                        "ID value is already assigned to an existing template",
                        request))

            # While we're at it, if we need to look up the service ID
            # and find the External API that will ultimately provide it
            # then grab that too instead of repeating the search.
            elif api.name_key == endpoint_template_instance.name_key:
                if service_id is None:
                    service_id = api.uuid_key

        try:
            service = self.core.get_external_api(service_id)
        except ServiceDoesNotExist:
            return json.dumps(
                not_found("Service API for endoint template not found",
                          request))

        try:
            service.add_template(endpoint_template_instance)
        except (EndpointTemplateAlreadyExists,
                InvalidEndpointTemplateServiceType):
            return json.dumps(
                conflict(
                    "Endpoint already exists or service type does not match.",
                    request))
        else:
            request.setResponseCode(201)
            return b''

    @app.route('/v2.0/OS-KSCATALOG/endpointTemplates/<string:template_id>',
               methods=['PUT'])
    @require_auth_token
    def update_endpoint_templates(self, request, template_id):
        """
        Update an API endpoint template already in the system.

        .. note:: A template by the same id must already exist in the system.

        .. note:: Either the service-id must be specified in the header or
            a Service Name by the same name must already exist. Otherwise
            a Not Found (404) will be returned.

        `OpenStack Identity v2 OS-KSCATALOG Update Endpoint Template
        <http://developer.openstack.org/api-ref-identity-v2-ext.html>`_
        """
        try:
            content = json_from_request(request)
        except ValueError:
            return json.dumps(bad_request("Invalid JSON request body",
                                          request))

        try:
            if content['id'] != template_id:
                return json.dumps(
                    conflict(
                        "Template ID in URL does not match that of the JSON body",
                        request))

            endpoint_template_instance = EndpointTemplateStore.deserialize(
                content)
        except (InvalidEndpointTemplateMissingKey, KeyError) as ex:
            # KeyError is for the content['id'] line
            return json.dumps(
                bad_request(
                    "JSON body does not contain the required parameters: " +
                    text_type(ex), request))

        service_id = request.getHeader(b'serviceid')
        if service_id is None:
            for api_id in self.core.get_external_apis():
                api = self.core.get_external_api(api_id)
                if api.has_template(template_id):
                    service_id = api.uuid_key
        else:
            service_id = service_id.decode('utf-8')

        try:
            service = self.core.get_external_api(service_id)
        except ServiceDoesNotExist:
            return json.dumps(
                not_found("Service API for endoint template not found",
                          request))

        try:
            service.update_template(endpoint_template_instance)
        except (InvalidEndpointTemplateServiceType, InvalidEndpointTemplateId):
            return json.dumps(
                conflict(
                    "Endpoint already exists and service id or service type "
                    "does not match.", request))
        except EndpointTemplateDoesNotExist:
            return json.dumps(
                not_found(
                    "Unable to update non-existent template. Template must "
                    "first be added before it can be updated.", request))
        else:
            request.setResponseCode(201)
            return b''

    @app.route('/v2.0/OS-KSCATALOG/endpointTemplates/<string:template_id>',
               methods=['DELETE'])
    @require_auth_token
    def delete_endpoint_templates(self, request, template_id):
        """
        Delete an endpoint API template from the system.

        .. note:: Either the service-id must be specified in the header or
            a Service Name by the same name must already exist. Otherwise
            a Not Found (404) will be returned.

        `OpenStack Identity v2 OS-KSCATALOG Delete Endpoint Template
        <http://developer.openstack.org/api-ref-identity-v2-ext.html>`_
        """
        service_id = request.getHeader(b'serviceid')
        if service_id is not None:
            api = self.core.get_external_api(service_id.decode('utf-8'))
            if api.has_template(template_id):
                api.remove_template(template_id)
                request.setResponseCode(204)
                return b''
        else:
            for api_id in self.core.get_external_apis():
                api = self.core.get_external_api(api_id)
                if api.has_template(template_id):
                    api.remove_template(template_id)
                    request.setResponseCode(204)
                    return b''

        return json.dumps(
            not_found(
                "Unable to locate an External API with the given Template ID.",
                request))

    @app.route('/v2.0/tenants/<string:tenant_id>/OS-KSCATALOG/endpoints',
               methods=['GET'])
    @require_auth_token
    def list_endpoints_for_tenant(self, request, tenant_id):
        """
        List the available endpoints for a given tenant-id.

        .. note:: Marker/Limit capability not implemented here.

        `OpenStack Identity v2 OS-KSCATALOG List Endpoints for Tenant
        <http://developer.openstack.org/api-ref-identity-v2-ext.html>`_
        """
        # caller may provide a specific API to list by setting the
        # serviceid header
        external_apis_to_list = []
        service_id = request.getHeader(b'serviceid')
        if service_id is not None:
            external_apis_to_list = [service_id.decode('utf-8')]
        else:
            external_apis_to_list = [
                api_id for api_id in self.core.get_external_apis()
            ]

        try:
            data = []
            request.setResponseCode(200)
            for api_id in external_apis_to_list:
                api = self.core.get_external_api(api_id)
                for endpoint_template in api.list_tenant_templates(tenant_id):
                    data.append(endpoint_template.serialize(tenant_id))

            return json.dumps({"endpoints": data, "endpoints_links": []})
        except ServiceDoesNotExist:
            request.setResponseCode(404)
            return json.dumps(
                not_found("Unable to find the requested API", request))

    @app.route('/v2.0/tenants/<string:tenant_id>/OS-KSCATALOG/endpoints',
               methods=['POST'])
    @require_auth_token
    def create_endpoint_for_tenant(self, request, tenant_id):
        """
        Enable a given endpoint template for a given tenantid.

        `OpenStack Identity v2 OS-KSCATALOG Create Endpoint for Tenant
        <http://developer.openstack.org/api-ref-identity-v2-ext.html>`_
        """
        try:
            content = json_from_request(request)
        except ValueError:
            return json.dumps(bad_request("Invalid JSON request body",
                                          request))

        try:
            template_id = content['OS-KSCATALOG:endpointTemplate']['id']
        except KeyError:
            return json.dumps(
                bad_request(
                    "Invalid Content. OS-KSCATALOG:endpointTemplate:id is "
                    "required.", request))

        for api_id in self.core.get_external_apis():
            api = self.core.get_external_api(api_id)
            if api.has_template(template_id):
                api.enable_endpoint_for_tenant(tenant_id, template_id)
                request.setResponseCode(201)
                return b''

        return json.dumps(
            not_found(
                "Unable to locate an External API with the given Template ID.",
                request))

    @app.route('/v2.0/tenants/<string:tenant_id>/OS-KSCATALOG/endpoints/'
               '<string:template_id>',
               methods=['DELETE'])
    @require_auth_token
    def remove_endpoint_for_tenant(self, request, tenant_id, template_id):
        """
        Disable a given endpoint template for a given tenantid if it's been
        enabled. This does not affect an endpoint template that has been
        globally enabled.

        `OpenStack Identity v2 OS-KSCATALOG Delete Endpoint for Tenant
        <http://developer.openstack.org/api-ref-identity-v2-ext.html>`_
        """
        for api_id in self.core.get_external_apis():
            api = self.core.get_external_api(api_id)
            if api.has_template(template_id):
                try:
                    api.disable_endpoint_for_tenant(tenant_id, template_id)
                except EndpointTemplateDisabledForTenant:
                    return json.dumps(
                        not_found("Template not enabled for tenant", request))
                else:
                    request.setResponseCode(204)
                    return b''

        return json.dumps(
            not_found(
                "Unable to locate an External API with the given Template ID.",
                request))
Beispiel #28
0
class IronicApi(object):
    """
    Rest endpoints for the Ironic API.
    """

    app = MimicApp()

    def __init__(self, core):
        """
        :param MimicCore core: The core to which the Ironic Api will be
        communicating.
        """
        self.core = core

    @app.route('/nodes', methods=['POST'])
    def create_node(self, request):
        """
        Responds with response code 201 and returns the newly created node.
        """
        return self.core.ironic_node_store.create_node(request)

    @app.route('/nodes/<string:node_id>', methods=['DELETE'])
    def delete_node(self, request, node_id):
        """
        Responds with response code 204 and delete the node.
        """
        return self.core.ironic_node_store.delete_node(request, node_id)

    @app.route('/nodes', methods=['GET'])
    def list_nodes(self, request):
        """
        Responds with response code 200 with a list of nodes.
        """
        return self.core.ironic_node_store.list_nodes(include_details=False)

    @app.route('/nodes/detail', methods=['GET'])
    def list_nodes_with_details(self, request):
        """
        Responds with response code 200 with a list of nodes and its details.
        """
        return self.core.ironic_node_store.list_nodes(include_details=True)

    @app.route('/nodes/<string:node_id>', methods=['GET'])
    def get_node_details(self, request, node_id):
        """
        Responds with response code 200 with details of the nodes.
        """
        return self.core.ironic_node_store.get_node_details(request, node_id)

    @app.route('/nodes/<string:node_id>/states/provision', methods=['PUT'])
    def set_node_provision_state(self, request, node_id):
        """
        Responds with response code 202 and sets the provision state of
        the node.
        """
        return self.core.ironic_node_store.set_node_provision_state(
            request, node_id)

    @app.route('/nodes/<string:node_id>/vendor_passthru/<string:method>',
               methods=['POST'])
    def vendor_passthru_cache_image(self, request, node_id, method):
        """
        Responds with response code 202 and sets the :obj:`Node`'s cache_image_id
        and cache_status.
        Returns 400 if `node_id` does not exist or if the `method` is not `cache_image`
        """
        return self.core.ironic_node_store.cache_image_using_vendor_passthru(
            request, node_id, method)
Beispiel #29
0
class ServerMetadata(object):

    """
    Klein routes for a particular server's metadata.
    """

    def __init__(self, server):
        """
        Handle requests having to deal a server's metadata.  No URIs
        are generated or used.
        """
        self._server = server

    app = MimicApp()

    @app.route('/', methods=['GET'])
    def list_metadata(self, request):
        """
        List all metadata associated with a server.
        """
        return json.dumps({'metadata': self._server.metadata})

    @app.route('/', methods=['PUT'])
    def set_metadata(self, request):
        """
        Set the metadata for the specified server - this replaces whatever
        metadata was there.  The resulting metadata is not a union of the
        previous metadata and the new metadata - it is *just* the new
        metadata.

        The body must look like:

        ``{"metadata": {...}}``

        although

        ``{"metadata": {...}, "other": "garbage", "keys": "included"}``

        is ok too.

        All the response messages and codes have been verified as of
        2015-04-23 against Rackspace Nova.
        """
        try:
            content = json.loads(request.content.read())
        except ValueError:
            return json.dumps(bad_request("Malformed request body", request))

        # more than one key is ok, non-"meta" keys are just ignored
        if 'metadata' not in content:
            return json.dumps(bad_request("Malformed request body", request))

        # When setting metadata, None is special for some reason
        if content['metadata'] is None:
            return json.dumps(bad_request(
                "Malformed request body. metadata must be object", request))

        try:
            Server.validate_metadata(content['metadata'])
        except BadRequestError as e:
            return json.dumps(bad_request(e.nova_message, request))
        except LimitError as e:
            return json.dumps(forbidden(e.nova_message, request))

        self._server.set_metadata(content['metadata'])
        return json.dumps({'metadata': content['metadata']})

    @app.route('/<key>', methods=['PUT'])
    def set_metadata_item(self, request, key):
        """
        Set a metadata item.  The body must look like:

        ``{"meta": {<key>: value}}``

        although

        ``{"meta": {<key>: value}, "other": "garbage", "keys": "included"}``

        is ok too.

        All the response messages and codes have been verified as of
        2015-04-23 against Rackspace Nova.
        """
        try:
            content = json.loads(request.content.read())
        except ValueError:
            request.setResponseCode(400)
            return json.dumps(bad_request("Malformed request body", request))

        # more than one key is ok, non-"meta" keys are just ignored
        if 'meta' not in content or not isinstance(content['meta'], dict):
            return json.dumps(bad_request("Malformed request body", request))

        if len(content['meta']) > 1:
            return json.dumps(bad_request(
                "Request body contains too many items", request))

        if key not in content['meta']:
            return json.dumps(bad_request(
                "Request body and URI mismatch", request))

        try:
            self._server.set_metadata_item(key, content['meta'][key])
        except BadRequestError as e:
            return json.dumps(bad_request(e.nova_message, request))
        except LimitError as e:
            return json.dumps(forbidden(e.nova_message, request))

        # no matter how many keys were passed in, only the meta key is
        # returned
        return json.dumps({'meta': content['meta']})
Beispiel #30
0
class NovaRegion(object):

    """
    Klein routes for the API within a Cloud Servers region.

    :ivar dict _tenant_cache: a mapping of tenant_id (bytes) to a "server
        cache" (:obj:`S_Cache`), which itself maps server_id to a
        JSON-serializable data structure of the 'server' key of GET responses.
    """

    def __init__(self, api_mock, uri_prefix, session_store, name):
        """
        Create a nova region with a given URI prefix (used for generating URIs
        to servers).
        """
        self.uri_prefix = uri_prefix
        self._api_mock = api_mock
        self._session_store = session_store
        self._name = name

    def url(self, suffix):
        """
        Generate a URL to an object within the Nova URL hierarchy, given the
        part of the URL that comes after.
        """
        return str(URLPath.fromString(self.uri_prefix).child(suffix))

    def _region_collection_for_tenant(self, tenant_id):
        """
        Get the given server-cache object for the given tenant, creating one if
        there isn't one.
        """
        return (self._api_mock._get_session(self._session_store, tenant_id)
                .collection_for_region(self._name))

    def _image_collection_for_tenant(self, tenant_id):
        tenant_session = self._session_store.session_for_tenant_id(tenant_id)
        image_global_collection = tenant_session.data_for_api(
            "image_collection",
            lambda: GlobalImageCollection(tenant_id=tenant_id,
                                          clock=self._session_store.clock))
        image_region_collection = image_global_collection.collection_for_region(
            self._name)
        return image_region_collection

    def _keypair_collection_for_tenant(self, tenant_id):
        """
        Returns the keypairs for a region
        """
        tenant_session = self._session_store.session_for_tenant_id(tenant_id)
        kp_global_collection = tenant_session.data_for_api(
            "keypair_collection",
            lambda: GlobalKeyPairCollections(tenant_id=tenant_id,
                                             clock=self._session_store.clock))
        kp_region_collection = kp_global_collection.collection_for_region(
            self._name)
        return kp_region_collection

    app = MimicApp()

    @app.route('/v2/<string:tenant_id>/servers', methods=['POST'])
    def create_server(self, request, tenant_id):
        """
        Returns a generic create server response, with status 'ACTIVE'.
        """
        try:
            content = json.loads(request.content.read())
        except ValueError:
            return json.dumps(
                bad_request("Invalid JSON request body", request))

        try:
            creation = (self._region_collection_for_tenant(tenant_id)
                        .request_creation(request, content, self.url))
        except BadRequestError as e:
            return json.dumps(bad_request(e.nova_message, request))
        except LimitError as e:
            return json.dumps(forbidden(e.nova_message, request))

        return creation

    @app.route('/v2/<string:tenant_id>/servers/<string:server_id>', methods=['GET'])
    def get_server(self, request, tenant_id, server_id):
        """
        Returns a generic get server response, with status 'ACTIVE'
        """
        return (
            self._region_collection_for_tenant(tenant_id)
            .request_read(request, server_id, self.url)
        )

    @app.route('/v2/<string:tenant_id>/servers', methods=['GET'])
    def list_servers(self, request, tenant_id):
        """
        Returns list of servers that were created by the mocks, with the given
        name.
        """
        return (
            self._region_collection_for_tenant(tenant_id)
            .request_list(
                request, include_details=False, absolutize_url=self.url,
                name=request.args.get('name', [u""])[0],
                limit=request.args.get('limit', [None])[0],
                marker=request.args.get('marker', [None])[0]
            )
        )

    @app.route('/v2/<string:tenant_id>/servers/detail', methods=['GET'])
    def list_servers_with_details(self, request, tenant_id):
        """
        Returns list of servers that were created by the mocks, with details
        such as the metadata.
        """
        return (
            self._region_collection_for_tenant(tenant_id)
            .request_list(
                request, include_details=True, absolutize_url=self.url,
                name=request.args.get('name', [u""])[0],
                limit=request.args.get('limit', [None])[0],
                marker=request.args.get('marker', [None])[0],
                changes_since=request.args.get('changes-since', [None])[0]
            )
        )

    @app.route('/v2/<string:tenant_id>/servers/<string:server_id>',
               methods=['DELETE'])
    def delete_server(self, request, tenant_id, server_id):
        """
        Returns a 204 response code, for any server id'
        """
        return (
            self._region_collection_for_tenant(tenant_id)
            .request_delete(request, server_id)
        )

    @app.route('/v2/<string:tenant_id>/images/<string:image_id>', methods=['GET'])
    def get_image(self, request, tenant_id, image_id):
        """
        Returns a get image response, for any given imageid
        """
        return(self._image_collection_for_tenant(tenant_id)
               .get_image(request, image_id, absolutize_url=self.url))

    @app.route('/v2/<string:tenant_id>/images/detail', methods=['GET'])
    def get_server_image_list_with_details(self, request, tenant_id):
        """
        Returns a image list.
        """
        return (self._image_collection_for_tenant(tenant_id)
                .list_images(include_details=True, absolutize_url=self.url))

    @app.route('/v2/<string:tenant_id>/images', methods=['GET'])
    def get_server_image_list(self, request, tenant_id):
        """
        Returns a image list.
        """
        return(self._image_collection_for_tenant(tenant_id)
               .list_images(include_details=False, absolutize_url=self.url))

    @app.route('/v2/<string:tenant_id>/flavors/<string:flavor_id>', methods=['GET'])
    def get_flavor_details(self, request, tenant_id, flavor_id):
        """
        Returns a get flavor response, for any given flavorid
        """
        flavor_collection = GlobalFlavorCollection(tenant_id=tenant_id,
                                                   clock=self._session_store.clock)
        return (flavor_collection.collection_for_region(region_name=self._name)
                .get_flavor(request, flavor_id, absolutize_url=self.url))

    @app.route('/v2/<string:tenant_id>/flavors', methods=['GET'])
    def get_flavor_list(self, request, tenant_id):
        """
        Returns a list of flavor with the response code 200.
        docs: http://bit.ly/1eXTSDC
        TO DO: The length of flavor list can be set using the control plane.
               Also be able to set different flavor types in the future.
        """
        flavor_collection = GlobalFlavorCollection(tenant_id=tenant_id,
                                                   clock=self._session_store.clock)
        return (flavor_collection.collection_for_region(region_name=self._name)
                .list_flavors(include_details=False, absolutize_url=self.url))

    @app.route('/v2/<string:tenant_id>/flavors/detail', methods=['GET'])
    def get_flavor_list_with_details(self, request, tenant_id):
        """
        Returns a list of flavor details with the response code 200.
        TO DO: The length of flavor list can be set using the control plane.
               Also be able to set different flavor types in the future.
        """
        flavor_collection = GlobalFlavorCollection(tenant_id=tenant_id,
                                                   clock=self._session_store.clock)
        return (flavor_collection.collection_for_region(region_name=self._name)
                .list_flavors(include_details=True, absolutize_url=self.url))

    @app.route('/v2/<string:tenant_id>/limits', methods=['GET'])
    def get_limit(self, request, tenant_id):
        """
        Returns a get flavor response, for any given flavorid
        """
        request.setResponseCode(200)
        return json.dumps(get_limit())

    @app.route('/v2/<string:tenant_id>/servers/<string:server_id>/ips', methods=['GET'])
    def get_ips(self, request, tenant_id, server_id):
        """
        Returns the IP addresses for the specified server.
        """
        return (
            self._region_collection_for_tenant(tenant_id).request_ips(
                request, server_id
            )
        )

    @app.route('/v2/<string:tenant_id>/servers/<string:server_id>/metadata',
               branch=True)
    def handle_server_metadata(self, request, tenant_id, server_id):
        """
        Handle metadata requests associated with a particular server.  Server
        not found error message verified as of 2015-04-23 against Rackspace
        Nova.
        """
        server = (self._region_collection_for_tenant(tenant_id)
                  .server_by_id(server_id))
        if server is None:
            return json.dumps(not_found('Server does not exist', request))

        return ServerMetadata(server).app.resource()

    @app.route('/v2/<string:tenant_id>/servers/<string:server_id>/action', methods=['POST'])
    def perform_action(self, request, tenant_id, server_id):
        """
        Perform the requested action on the server
        """
        return self._region_collection_for_tenant(tenant_id).request_action(request, server_id, self.url)

    @app.route("/v2/<string:tenant_id>/os-keypairs", methods=['GET'])
    def get_key_pairs(self, request, tenant_id):
        """
        Returns current key pairs.
        http://docs.rackspace.com/servers/api/v2/cs-devguide/content/ListKeyPairs.html
        """
        return self._keypair_collection_for_tenant(tenant_id).json_list()

    @app.route("/v2/<string:tenant_id>/os-keypairs", methods=['POST'])
    def create_key_pair(self, request, tenant_id):
        """
        Returns a newly created key pair with the specified name.
        http://docs.rackspace.com/servers/api/v2/cs-devguide/content/UploadKeyPair.html
        """
        try:
            content = json.loads(request.content.read())
            keypair = content["keypair"]
            keypair_from_request = KeyPair(
                name=keypair["name"], public_key=keypair["public_key"])
        except (ValueError or KeyError):
            request.setResponseCode(400)
            return json.dumps(bad_request("Malformed request body", request))

        keypair_response = self._keypair_collection_for_tenant(
            tenant_id).create_keypair(keypair=keypair_from_request)
        return json.dumps(keypair_response)

    @app.route("/v2/<string:tenant_id>/os-keypairs/<string:keypairname>", methods=['DELETE'])
    def delete_key_pair(self, request, tenant_id, keypairname):
        """
        Removes a key by its name.
        http://docs.rackspace.com/servers/api/v2/cs-devguide/content/DeleteKeyPair.html
        """
        try:
            self._keypair_collection_for_tenant(
                tenant_id).remove_keypair(keypairname)
        except ValueError:
            request.setResponseCode(404)
            return json.dumps("KeyPair not found: " + keypairname)

        request.setResponseCode(202)