Files
distcloud/distributedcloud/dcagent/tests/api/test_root_controller.py
Victor Romano 4ceac85048 Introduce dcagent API and periodic info gathering
This commit introduces the new dcagent package. It is comprised of a
periodic process that queries the necessary endpoints to gather the
audit data and an API running on port 8325 (internal) and 8326 (admin).
The api only has one endpoint /v1/dcaudit that accepts only PATCH and
will respond with 'in-sync' or 'out-of-sync' for dcmanager-audit based
on the RegionOne data provided or will return the subcloud data for the
requested endpoints for dcorch-audit.

The agent also supports a key 'use_cache' to be sent in the payload
that will determine if it should use the cache data gathered by the
periodic process or get new information on the fly.

Example of payload using cached data:
  {
    "base_audit": "",
    "firmware_audit": "<regionone-audit-data>",
    "kubernetes_audit": "<regionone-audit-data>",
    "kube_rootca_audit" : "<regionone-audit-data>",
    "software_audit": "<regionone-audit-data>"
  }

Example of payload requesting new information:
  {
    "certificates": "",
    "iuser": "",
    "fernet_repo": "",
    "use_cache": "false"
  }

NOTES:
  - As patch and load audits will be deprecated in the next major
    release, no effort was made to integrate both patch and load audit
    to dcagent.
  - All tests described below were executed applying [1] as well,
    to avoid retesting.

[1]: https://review.opendev.org/c/starlingx/distcloud/+/923351

Test plan:
  - PASS: Run dcmanager audit with dcagent. Verify only one call is
          made to audit the subcloud and the response include the
          correct sync status.
  - PASS: Run dcmanager audit without dcagent. Verify the audit
          works as expected querying each individual endpoint.

Story: 2011106
Task: 50559

Change-Id: I1820ca9688d5d05f8712f9a42f6012f2ec3e2d8a
Signed-off-by: Victor Romano <victor.gluzromano@windriver.com>
2024-07-24 12:39:00 -03:00

240 lines
7.0 KiB
Python

#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import http.client
import uuid
import mock
from oslo_config import cfg
from oslo_config import fixture as fixture_config
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
import pecan
from pecan.configuration import set_config
from pecan.testing import load_test_app
from dcagent.api import api_config
from dcagent.common import config
from dcagent.tests.base import DCAgentTestCase
from dcagent.tests.common import consts as test_consts
config.register_options()
OPT_GROUP_NAME = "keystone_authtoken"
cfg.CONF.import_group(OPT_GROUP_NAME, "keystonemiddleware.auth_token")
class DCAgentApiTest(DCAgentTestCase):
def setUp(self):
super().setUp()
self.addCleanup(set_config, {}, overwrite=True)
api_config.test_init()
config_fixture = fixture_config.Config()
self.CONF = self.useFixture(config_fixture).conf
config_fixture.set_config_dirs([])
self.CONF.set_override("auth_strategy", "noauth")
self.app = self._make_app()
self._mock_pecan()
self.url = "/"
# The put method is used as a default value, leading to the generic
# implementation on controllers in case the method is not specified
self.method = self.app.put
self.params = {}
self.upload_files = None
self.verb = None
self.headers = {
"X-Tenant-Id": str(uuid.uuid4()),
"X_ROLE": "admin,member,reader",
"X-Identity-Status": "Confirmed",
"X-Project-Name": "admin",
}
def _make_app(self, enable_acl=False):
self.config_fixture = {
"app": {
"root": "dcagent.api.controllers.root.RootController",
"modules": ["dcagent.api"],
"enable_acl": enable_acl,
"errors": {400: "/error", "__force_dict__": True},
},
}
return load_test_app(self.config_fixture)
def _send_request(self):
"""Send a request to a url"""
kwargs = {}
if self.upload_files:
kwargs = {"upload_files": self.upload_files}
return self.method(
self.url,
headers=self.headers,
params=self.params,
expect_errors=True,
**kwargs,
)
def _assert_response(
self,
response,
status_code=http.client.OK,
content_type=test_consts.APPLICATION_JSON,
expected_response_text=None,
):
"""Assert the response for a request"""
self.assertEqual(response.status_code, status_code)
self.assertEqual(response.content_type, content_type)
if expected_response_text:
self.assertEqual(response.text, expected_response_text)
def _mock_pecan(self):
"""Mock pecan's abort"""
mock_patch_object = mock.patch.object(pecan, "abort", wraps=pecan.abort)
self.mock_pecan_abort = mock_patch_object.start()
self.addCleanup(mock_patch_object.stop)
def _assert_pecan(self, http_status, content=None, call_count=1):
"""Assert pecan was called with the correct arguments"""
self.assertEqual(self.mock_pecan_abort.call_count, call_count)
if content:
self.mock_pecan_abort.assert_called_with(http_status, content)
else:
self.mock_pecan_abort.assert_called_with(http_status)
def _assert_pecan_and_response(
self,
response,
http_status,
content=None,
call_count=1,
content_type=test_consts.TEXT_PLAIN,
):
"""Assert the response and pecan abort for a failed request"""
self._assert_pecan(http_status, content, call_count=call_count)
self._assert_response(response, http_status, content_type)
def tearDown(self):
super(DCAgentApiTest, self).tearDown()
pecan.set_config({}, overwrite=True)
class TestRootController(DCAgentApiTest):
"""Test version listing on root URI."""
def setUp(self):
super(TestRootController, self).setUp()
self.url = "/"
self.method = self.app.get
def _test_method_returns_405(self, method, content_type=test_consts.TEXT_PLAIN):
self.method = method
response = self._send_request()
self._assert_pecan_and_response(
response, http.client.METHOD_NOT_ALLOWED, content_type=content_type
)
def test_get(self):
"""Test get request succeeds with correct versions"""
response = self._send_request()
self._assert_response(response)
json_body = jsonutils.loads(response.body)
versions = json_body.get("versions")
self.assertEqual(1, len(versions))
def test_request_id(self):
"""Test request for root returns the correct request id"""
response = self._send_request()
self._assert_response(response)
self.assertIn("x-openstack-request-id", response.headers)
self.assertTrue(response.headers["x-openstack-request-id"].startswith("req-"))
id_part = response.headers["x-openstack-request-id"].split("req-")[1]
self.assertTrue(uuidutils.is_uuid_like(id_part))
def test_post(self):
"""Test post request is not allowed on root"""
self._test_method_returns_405(self.app.post)
def test_put(self):
"""Test put request is not allowed on root"""
self._test_method_returns_405(self.app.put)
def test_patch(self):
"""Test patch request is not allowed on root"""
self._test_method_returns_405(self.app.patch)
def test_delete(self):
"""Test delete request is not allowed on root"""
self._test_method_returns_405(self.app.delete)
def test_head(self):
"""Test head request is not allowed on root"""
self._test_method_returns_405(self.app.head, content_type=test_consts.TEXT_HTML)
class TestErrors(DCAgentApiTest):
def setUp(self):
super(TestErrors, self).setUp()
cfg.CONF.set_override("admin_tenant", "fake_tenant_id", group="cache")
def test_404(self):
self.url = "/assert_called_once"
self.method = self.app.get
response = self._send_request()
self._assert_response(
response, http.client.NOT_FOUND, content_type=test_consts.TEXT_PLAIN
)
def test_version_1_root_controller(self):
self.url = f"/v1/{uuidutils.generate_uuid()}/bad_method"
self.method = self.app.patch
response = self._send_request()
self._assert_pecan_and_response(response, http.client.NOT_FOUND)
class TestKeystoneAuth(DCAgentApiTest):
"""Test requests using keystone as the authentication strategy"""
def setUp(self):
super(TestKeystoneAuth, self).setUp()
cfg.CONF.set_override("auth_strategy", "keystone")
self.method = self.app.get
def test_auth_not_enforced_for_root(self):
"""Test authentication is not enforced for root url"""
response = self._send_request()
self._assert_response(response)