
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>
240 lines
7.0 KiB
Python
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)
|