Example #1
0
    def _generic_bulk_rcv3_step_test(self, step_class, expected_method):
        """
        A generic test for bulk RCv3 steps.

        :param step_class: The step class under test.
        :param str method: The expected HTTP method of the request.
        """
        lb_node_pairs = pset([
            ("lb-1", "node-a"),
            ("lb-1", "node-b"),
            ("lb-1", "node-c"),
            ("lb-1", "node-d"),
            ("lb-2", "node-a"),
            ("lb-2", "node-b"),
            ("lb-3", "node-c"),
            ("lb-3", "node-d")
        ])
        step = step_class(lb_node_pairs=lb_node_pairs)
        request = step.as_effect()
        self.assertEqual(request.intent.service_type,
                         ServiceType.RACKCONNECT_V3)
        self.assertEqual(request.intent.method, expected_method)

        success_pred = request.intent.success_pred
        if request.intent.method == "POST":
            self.assertEqual(success_pred, has_code(201, 409))
        else:
            self.assertEqual(success_pred, has_code(204, 409))

        self.assertEqual(request.intent.url, "load_balancer_pools/nodes")
        self.assertEqual(request.intent.headers, None)

        expected_data = [
            {'load_balancer_pool': {'id': 'lb-1'},
             'cloud_server': {'id': 'node-a'}},
            {'load_balancer_pool': {'id': 'lb-1'},
             'cloud_server': {'id': 'node-b'}},
            {'load_balancer_pool': {'id': 'lb-1'},
             'cloud_server': {'id': 'node-c'}},
            {'load_balancer_pool': {'id': 'lb-1'},
             'cloud_server': {'id': 'node-d'}},
            {'load_balancer_pool': {'id': 'lb-2'},
             'cloud_server': {'id': 'node-a'}},
            {'load_balancer_pool': {'id': 'lb-2'},
             'cloud_server': {'id': 'node-b'}},
            {'load_balancer_pool': {'id': 'lb-3'},
             'cloud_server': {'id': 'node-c'}},
            {'load_balancer_pool': {'id': 'lb-3'},
             'cloud_server': {'id': 'node-d'}}
        ]

        def key_fn(e):
            return (e["load_balancer_pool"]["id"], e["cloud_server"]["id"])

        request_data = sorted(request.intent.data, key=key_fn)
        self.assertEqual(request_data, expected_data)
Example #2
0
 def _bare_effect(self):
     """
     Just the RCv3 bulk request effect, with no callbacks.
     """
     # While 409 isn't success, that has to be introspected by
     # _rcv3_check_bulk_delete in order to recover from it.
     return _rackconnect_bulk_request(self.lb_node_pairs, "DELETE", success_pred=has_code(204, 409))
Example #3
0
def delete_and_verify(server_id):
    """
    Check the status of the server to see if it's actually been deleted.
    Succeeds only if it has been either deleted (404) or acknowledged by Nova
    to be deleted (task_state = "deleted").

    Note that ``task_state`` is in the server details key
    ``OS-EXT-STS:task_state``, which is supported by Openstack but available
    only when looking at the extended status of a server.
    """

    def check_task_state((resp, server_blob)):
        if resp.code == 404:
            return
        server_details = server_blob['server']
        is_deleting = server_details.get("OS-EXT-STS:task_state", "")
        if is_deleting.strip().lower() != "deleting":
            raise UnexpectedServerStatus(server_id, is_deleting, "deleting")

    def verify((_type, error, traceback)):
        if error.code != 204:
            raise _type, error, traceback
        ver_eff = service_request(
            ServiceType.CLOUD_SERVERS, 'GET',
            append_segments('servers', server_id),
            success_pred=has_code(200, 404))
        return ver_eff.on(check_task_state)

    return service_request(
        ServiceType.CLOUD_SERVERS, 'DELETE',
        append_segments('servers', server_id),
        success_pred=has_code(404)).on(error=catch(APIError, verify))
Example #4
0
def delete_and_verify(server_id):
    """
    Check the status of the server to see if it's actually been deleted.
    Succeeds only if it has been either deleted (404) or acknowledged by Nova
    to be deleted (task_state = "deleted").

    Note that ``task_state`` is in the server details key
    ``OS-EXT-STS:task_state``, which is supported by Openstack but available
    only when looking at the extended status of a server.
    """

    def check_task_state((resp, server_blob)):
        if resp.code == 404:
            return
        server_details = server_blob['server']
        is_deleting = server_details.get("OS-EXT-STS:task_state", "")
        if is_deleting.strip().lower() != "deleting":
            raise UnexpectedServerStatus(server_id, is_deleting, "deleting")

    def verify((_type, error, traceback)):
        if error.code != 204:
            raise _type, error, traceback
        ver_eff = service_request(
            ServiceType.CLOUD_SERVERS, 'GET',
            append_segments('servers', server_id),
            success_pred=has_code(200, 404))
        return ver_eff.on(check_task_state)

    return service_request(
        ServiceType.CLOUD_SERVERS, 'DELETE',
        append_segments('servers', server_id),
        success_pred=has_code(404)).on(error=catch(APIError, verify))
Example #5
0
    def _perform_add_event(self, response_sequence):
        """
        Given a sequence of functions that take an intent and returns a
        response (or raises an exception), perform :func:`add_event` and
        return the result.
        """
        log = object()
        eff = add_event(self.event, 'tid', 'ord', log)
        uid = '00000000-0000-0000-0000-000000000000'

        svrq = service_request(
            ServiceType.CLOUD_FEEDS,
            'POST',
            'autoscale/events',
            headers={'content-type': ['application/vnd.rackspace.atom+json']},
            data=self._get_request('INFO', uid, 'tid'),
            log=log,
            success_pred=has_code(201),
            json_response=False)

        seq = [
            (TenantScope(mock.ANY, 'tid'),
             nested_sequence([
                 retry_sequence(
                     Retry(effect=svrq,
                           should_retry=ShouldDelayAndRetry(
                               can_retry=mock.ANY,
                               next_interval=exponential_backoff_interval(2))),
                     response_sequence)
             ]))
        ]

        return perform_sequence(seq, eff)
Example #6
0
    def _perform_add_event(self, response_sequence):
        """
        Given a sequence of functions that take an intent and returns a
        response (or raises an exception), perform :func:`add_event` and
        return the result.
        """
        log = object()
        eff = add_event(self.event, 'tid', 'ord', log)
        uid = '00000000-0000-0000-0000-000000000000'

        svrq = service_request(
            ServiceType.CLOUD_FEEDS, 'POST', 'autoscale/events',
            headers={
                'content-type': ['application/vnd.rackspace.atom+json']},
            data=self._get_request('INFO', uid, 'tid'), log=log,
            success_pred=has_code(201),
            json_response=False)

        seq = [
            (TenantScope(mock.ANY, 'tid'), nested_sequence([
                retry_sequence(
                    Retry(effect=svrq, should_retry=ShouldDelayAndRetry(
                        can_retry=mock.ANY,
                        next_interval=exponential_backoff_interval(2))),
                    response_sequence
                )
            ]))
        ]

        return perform_sequence(seq, eff)
Example #7
0
 def verify((_type, error, traceback)):
     if error.code != 204:
         raise _type, error, traceback
     ver_eff = service_request(
         ServiceType.CLOUD_SERVERS, 'GET',
         append_segments('servers', server_id),
         success_pred=has_code(200, 404))
     return ver_eff.on(check_task_state)
Example #8
0
 def _bare_effect(self):
     """
     Just the RCv3 bulk request effect, with no callbacks.
     """
     # While 409 isn't success, that has to be introspected by
     # _rcv3_check_bulk_delete in order to recover from it.
     return _rackconnect_bulk_request(self.lb_node_pairs, "DELETE",
                                      success_pred=has_code(204, 409))
Example #9
0
 def verify((_type, error, traceback)):
     if error.code != 204:
         raise _type, error, traceback
     ver_eff = service_request(
         ServiceType.CLOUD_SERVERS, 'GET',
         append_segments('servers', server_id),
         success_pred=has_code(200, 404))
     return ver_eff.on(check_task_state)
Example #10
0
 def test_delete_and_verify_del_404(self):
     """
     :func:`delete_and_verify` invokes server delete and succeeds on 404
     """
     eff = delete_and_verify('sid')
     self.assertEqual(
         eff.intent,
         service_request(
             ServiceType.CLOUD_SERVERS, 'DELETE', 'servers/sid',
             success_pred=has_code(404)).intent)
     self.assertEqual(resolve_effect(eff, (ANY, {})), (ANY, {}))
Example #11
0
    def test_try_again(self):
        """
        If a node was already removed (or maybe was never part of the load
        balancer pool to begin with), or some load balancer was
        inactive, or one of the load balancers doesn't exist, returns
        an effect that removes the remaining load balancer pairs.
        """
        # This little piggy isn't even on this load balancer.
        node_a_id = '825b8c72-9951-4aff-9cd8-fa3ca5551c90'
        lb_a_id = '2b0e17b6-0429-4056-b86c-e670ad5de853'

        # This little piggy is going to be removed from this load balancer.
        node_b_id = "d6d3aa7c-dfa5-4e61-96ee-1d54ac1075d2"
        lb_b_id = 'd95ae0c4-6ab8-4873-b82f-f8433840cff2'

        # This little piggy isn't active!
        node_c_id = '08944038-80ba-4ae1-a188-c827444e02e2'
        lb_c_id = '150895a5-1aa7-45b7-b7a4-98b9c282f800'

        # This isn't even a little piggy!
        node_d_id = 'bc1e94c3-0c88-4828-9e93-d42259280987'
        lb_d_id = 'de52879e-1f84-4ecd-8988-91dfdc99570d'

        seq = [
            (service_request(
                service_type=ServiceType.RACKCONNECT_V3,
                method="DELETE",
                url='load_balancer_pools/nodes',
                data=[
                    {'load_balancer_pool': {'id': lb_b_id},
                     'cloud_server': {'id': node_b_id}}],
                success_pred=has_code(204, 409)).intent,
             lambda _: (StubResponse(204, {}), None)),
        ]

        body = {"errors":
                ["Node {node_id} is not a member of Load Balancer "
                 "Pool {lb_id}".format(node_id=node_a_id, lb_id=lb_a_id),
                 "Load Balancer Pool {lb_id} is not in an ACTIVE state"
                 .format(lb_id=lb_c_id),
                 "Load Balancer Pool {lb_id} does not exist"
                 .format(lb_id=lb_d_id)]}

        eff = _rcv3_check_bulk_delete(
            [(lb_a_id, node_a_id),
             (lb_b_id, node_b_id),
             (lb_c_id, node_c_id),
             (lb_d_id, node_d_id)],
            (StubResponse(409, {}), body))

        self.assertEqual(perform_sequence(seq, eff), (StepResult.SUCCESS, []))
Example #12
0
    def test_delete_and_verify_verifies(self):
        """
        :func:`delete_and_verify` verifies if the server task_state has changed
        to "deleting" after successful delete server call and succeeds if that
        has happened. The details call succeeds if it returns 404
        """
        eff = delete_and_verify('sid')
        eff = resolve_effect(
            eff, service_request_error_response(APIError(204, {})),
            is_error=True)

        self.assertEqual(
            eff.intent,
            service_request(
                ServiceType.CLOUD_SERVERS, 'GET', 'servers/sid',
                success_pred=has_code(200, 404)).intent)
        r = resolve_effect(
            eff,
            (StubResponse(200, {}),
             {'server': {"OS-EXT-STS:task_state": 'deleting'}}))
        self.assertIsNone(r)
Example #13
0
    def test_try_again(self):
        """
        If a node is already on the load balancer, returns an effect that
        removes the remaining load balancer pairs.
        """
        # This little piggy is already on the load balancer
        node_a_id = '825b8c72-9951-4aff-9cd8-fa3ca5551c90'
        lb_a_id = '2b0e17b6-0429-4056-b86c-e670ad5de853'

        # This little piggy is going to be added to this load balancer
        node_b_id = "d6d3aa7c-dfa5-4e61-96ee-1d54ac1075d2"
        lb_b_id = 'd95ae0c4-6ab8-4873-b82f-f8433840cff2'

        seq = [
            (service_request(
                service_type=ServiceType.RACKCONNECT_V3,
                method="POST",
                url='load_balancer_pools/nodes',
                data=[
                    {'load_balancer_pool': {'id': lb_b_id},
                     'cloud_server': {'id': node_b_id}}],
                success_pred=has_code(201, 409)).intent,
             lambda _: (StubResponse(201, {}), None)),
        ]

        body = {"errors":
                ["Cloud Server {node_id} is already a member of Load "
                 "Balancer Pool {lb_id}"
                 .format(node_id=node_a_id, lb_id=lb_a_id)]}

        eff = _rcv3_check_bulk_add(
            [(lb_a_id, node_a_id),
             (lb_b_id, node_b_id)],
            (StubResponse(409, {}), body))

        self.assertEqual(
            perform_sequence(seq, eff),
            (StepResult.RETRY,
             [ErrorReason.String(reason="must re-gather after adding to LB in "
                                        "order to update the active cache")]))
Example #14
0
 def test_create_server_request_with_name(self):
     """
     :obj:`CreateServer.as_effect` produces a request for creating a server.
     If the name is given, a randomly generated suffix is appended to the
     server name.
     """
     create = CreateServer(
         server_config=freeze({'server': {'name': 'myserver',
                                          'flavorRef': '1'}}))
     eff = create.as_effect()
     self.assertEqual(eff.intent, Func(generate_server_name))
     eff = resolve_effect(eff, 'random-name')
     self.assertEqual(
         eff.intent,
         service_request(
             ServiceType.CLOUD_SERVERS,
             'POST',
             'servers',
             data={'server': {'name': 'myserver-random-name',
                              'flavorRef': '1'}},
             success_pred=has_code(202),
             reauth_codes=(401,)).intent)
Example #15
0
    def test_create_server_noname(self):
        """
        :obj:`CreateServer.as_effect`, when no name is provided in the launch
        config, will generate the name will from scratch.

        This only verifies intent; result reporting is tested in
        :meth:`test_create_server`.
        """
        create = CreateServer(
            server_config=freeze({'server': {'flavorRef': '1'}}))
        eff = create.as_effect()
        self.assertEqual(eff.intent, Func(generate_server_name))
        eff = resolve_effect(eff, 'random-name')
        self.assertEqual(
            eff.intent,
            service_request(
                ServiceType.CLOUD_SERVERS,
                'POST',
                'servers',
                data={'server': {'name': 'random-name', 'flavorRef': '1'}},
                success_pred=has_code(202),
                reauth_codes=(401,)).intent)
Example #16
0
 def test_create_server_success_case(self):
     """
     :obj:`CreateServer.as_effect`, when it results in a successful create,
     returns with :obj:`StepResult.RETRY`.
     """
     eff = CreateServer(
         server_config=freeze({'server': {'flavorRef': '1'}})).as_effect()
     seq = [
         (Func(generate_server_name), lambda _: 'random-name'),
         (service_request(
             ServiceType.CLOUD_SERVERS,
             'POST',
             'servers',
             data={'server': {'name': 'random-name', 'flavorRef': '1'}},
             success_pred=has_code(202),
             reauth_codes=(401,)).intent,
          lambda _: (StubResponse(202, {}), {"server": {}})),
         (Log('request-create-server', ANY), lambda _: None)
     ]
     self.assertEqual(
         perform_sequence(seq, eff),
         (StepResult.RETRY,
          [ErrorReason.String('waiting for server to become active')]))
Example #17
0
 def _bare_effect(self):
     """
     Just the RCv3 bulk request effect, with no callbacks.
     """
     return _rackconnect_bulk_request(self.lb_node_pairs, "POST",
                                      success_pred=has_code(201, 409))