From 2e67eac5e352cf0e7c54d68bfc7b104a8e8184d9 Mon Sep 17 00:00:00 2001 From: AnishMudaraddi Date: Thu, 6 Nov 2025 13:58:45 +0000 Subject: [PATCH] add exiting flavor to projet action --- actions/project.add.flavor.yaml | 31 ++++++++++ lib/apis/openstack_api/openstack_project.py | 16 ++++++ .../openstack_api/test_openstack_project.py | 56 ++++++++++++++++++- 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 actions/project.add.flavor.yaml diff --git a/actions/project.add.flavor.yaml b/actions/project.add.flavor.yaml new file mode 100644 index 000000000..1b2de1bc0 --- /dev/null +++ b/actions/project.add.flavor.yaml @@ -0,0 +1,31 @@ +--- +description: Add an existing flavor to a project +enabled: true +entry_point: src/openstack_actions.py +name: project.add.flavor +parameters: + lib_entry_point: + default: apis.openstack_api.openstack_project.add_flavor_to_project + immutable: true + type: string + requires_openstack: + default: true + immutable: true + type: boolean + cloud_account: + description: The clouds.yaml account to use whilst performing this action + required: true + type: string + default: "dev" + enum: + - "dev" + - "prod" + project_identifier: + description: Project Name or ID to add the flavor to + required: true + type: string + flavor_identifier: + description: Flavor Name or ID to add + required: true + type: string +runner_type: python-script diff --git a/lib/apis/openstack_api/openstack_project.py b/lib/apis/openstack_api/openstack_project.py index 0227d5910..4a6495e69 100644 --- a/lib/apis/openstack_api/openstack_project.py +++ b/lib/apis/openstack_api/openstack_project.py @@ -67,6 +67,22 @@ def delete_project(conn: Connection, project_identifier: str) -> bool: return result is None # Where None == success +def add_flavor_to_project( + conn: Connection, project_identifier: str, flavor_identifier: str +) -> bool: + """ + Add a flavor to the given project + :param conn: openstack connection object + :param project_identifier: The name or Openstack ID for the project + :param flavor_identifier: The name or Openstack ID for the flavor + :return: + """ + project = conn.identity.find_project(project_identifier, ignore_missing=False) + flavor = conn.compute.find_flavor(flavor_identifier, ignore_missing=False) + res = conn.compute.flavor_add_tenant_access(flavor, project) + return res is None + + def _is_project_safe_to_delete(project: Project) -> bool: """ Returns true if the project is safe to delete - avoid deleting admin project for instance diff --git a/tests/lib/apis/openstack_api/test_openstack_project.py b/tests/lib/apis/openstack_api/test_openstack_project.py index 67a4dbcec..745769321 100644 --- a/tests/lib/apis/openstack_api/test_openstack_project.py +++ b/tests/lib/apis/openstack_api/test_openstack_project.py @@ -3,7 +3,11 @@ from meta.exceptions.missing_mandatory_param_error import MissingMandatoryParamError from openstack.exceptions import ConflictException -from apis.openstack_api.openstack_project import create_project, delete_project +from apis.openstack_api.openstack_project import ( + create_project, + delete_project, + add_flavor_to_project, +) def test_create_project_name_missing_throws(): @@ -204,3 +208,53 @@ def test_delete_protected_project_fails(protected_project_id): mock_project_identifier, ignore_missing=False ) mock_conn.identity.delete_project.assert_not_called() + + +def test_add_flavor_to_project_success(): + """tests that add flavor to project calls appropriate API functions with correct args""" + mock_conn = MagicMock() + mock_project_identifier = NonCallableMock() + mock_flavor_identifier = NonCallableMock() + + res = add_flavor_to_project( + mock_conn, mock_project_identifier, mock_flavor_identifier + ) + + mock_conn.identity.find_project.assert_called_once_with( + mock_project_identifier, ignore_missing=False + ) + mock_conn.compute.find_flavor.assert_called_once_with( + mock_flavor_identifier, ignore_missing=False + ) + mock_conn.compute.flavor_add_tenant_access( + mock_conn.compute.find_flavor.return_value, + mock_conn.compute.find_project.return_value, + ) + assert res is False # mock is not None + + +def test_add_flavor_to_project_fail(): + """tests that add flavor to project calls appropriate API functions with correct args""" + mock_conn = MagicMock() + mock_project_identifier = NonCallableMock() + mock_flavor_identifier = NonCallableMock() + + mock_conn.compute.flavor_add_tenant_access.return_value = ( + None # to mock failure of adding flavor to project + ) + + res = add_flavor_to_project( + mock_conn, mock_project_identifier, mock_flavor_identifier + ) + + mock_conn.identity.find_project.assert_called_once_with( + mock_project_identifier, ignore_missing=False + ) + mock_conn.compute.find_flavor.assert_called_once_with( + mock_flavor_identifier, ignore_missing=False + ) + mock_conn.compute.flavor_add_tenant_access( + mock_conn.compute.find_flavor.return_value, + mock_conn.compute.find_project.return_value, + ) + assert res is True # mock is not None