Skip to content

Commit 73daa26

Browse files
authored
Merge pull request #938 from rackerlabs/sync-all-projects
fix: Sync all openstack projects to nautobot, not just the user ones
2 parents 4f0d781 + b001130 commit 73daa26

File tree

7 files changed

+53
-118
lines changed

7 files changed

+53
-118
lines changed

python/understack-workflows/tests/conftest.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,15 @@ def _get_project(project_id):
6060
"id": project_id,
6161
"domain_id": project_data["domain_id"].hex,
6262
}
63-
return openstack.identity.v3.project.Project(**data)
64-
raise openstack.exceptions.NotFoundException
63+
elif project_id == project_data["domain_id"].hex:
64+
data = {
65+
**project_data,
66+
"id": project_data["domain_id"].hex,
67+
"domain_id": "default",
68+
}
69+
else:
70+
raise openstack.exceptions.NotFoundException
71+
return openstack.identity.v3.project.Project(**data)
6572

6673
conn = MagicMock(spec_set=openstack.connection.Connection)
6774
conn.identity.get_project.side_effect = _get_project
@@ -111,4 +118,6 @@ def nautobot(requests_mock, nautobot_url: str, tenant_data: dict) -> Nautobot:
111118
requests_mock.get(tenant_data["url"], json=tenant_data)
112119
requests_mock.delete(tenant_data["url"])
113120
requests_mock.post(f"{nautobot_url}/api/tenancy/tenants/", json=tenant_data)
121+
requests_mock.patch(tenant_data["url"], json=tenant_data)
122+
114123
return Nautobot(nautobot_url, "blah")

python/understack-workflows/tests/test_sync_keystone.py

Lines changed: 10 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44
import pytest
55
from pytest_lazy_fixtures import lf
66

7-
from understack_workflows.domain import DefaultDomain
87
from understack_workflows.main.sync_keystone import Event
98
from understack_workflows.main.sync_keystone import argument_parser
109
from understack_workflows.main.sync_keystone import do_action
11-
from understack_workflows.main.sync_keystone import is_valid_domain
1210

1311

1412
@pytest.mark.parametrize(
@@ -23,16 +21,14 @@
2321
),
2422
(
2523
[
26-
"--only-domain",
27-
lf("domain_id"),
2824
"identity.project.created",
2925
lf("project_id"),
3026
],
3127
nullcontext(),
3228
lf("project_id"),
3329
),
3430
(
35-
["--only-domain", "default", "identity.project.created", lf("project_id")],
31+
["identity.project.created", lf("project_id")],
3632
nullcontext(),
3733
lf("project_id"),
3834
),
@@ -46,69 +42,34 @@ def test_parse_object_id(arg_list, context, expected_id):
4642
assert args.object == expected_id
4743

4844

49-
@pytest.mark.parametrize(
50-
"only_domain,expected",
51-
[
52-
(None, True),
53-
(DefaultDomain(), False),
54-
(lf("domain_id"), True),
55-
],
56-
)
57-
def test_is_valid_domain(os_conn, project_id, only_domain, expected):
58-
assert is_valid_domain(os_conn, project_id, only_domain) == expected
59-
60-
61-
@pytest.mark.parametrize(
62-
"only_domain",
63-
[
64-
None,
65-
lf("domain_id"),
66-
uuid.uuid4(),
67-
],
68-
)
6945
def test_create_project(
7046
os_conn,
7147
nautobot,
7248
project_id: uuid.UUID,
73-
only_domain: uuid.UUID | DefaultDomain | None,
49+
domain_id: uuid.UUID,
7450
):
75-
ret = do_action(os_conn, nautobot, Event.ProjectCreate, project_id, only_domain)
76-
os_conn.identity.get_project.assert_called_with(project_id.hex)
51+
ret = do_action(os_conn, nautobot, Event.ProjectCreate, project_id)
52+
os_conn.identity.get_project.assert_any_call(domain_id.hex)
53+
os_conn.identity.get_project.assert_any_call(project_id.hex)
7754
assert ret == 0
7855

7956

80-
@pytest.mark.parametrize(
81-
"only_domain",
82-
[
83-
None,
84-
lf("domain_id"),
85-
uuid.uuid4(),
86-
],
87-
)
8857
def test_update_project(
8958
os_conn,
9059
nautobot,
9160
project_id: uuid.UUID,
92-
only_domain: uuid.UUID | DefaultDomain | None,
61+
domain_id: uuid.UUID,
9362
):
94-
ret = do_action(os_conn, nautobot, Event.ProjectUpdate, project_id, only_domain)
95-
os_conn.identity.get_project.assert_called_with(project_id.hex)
63+
ret = do_action(os_conn, nautobot, Event.ProjectUpdate, project_id)
64+
os_conn.identity.get_project.assert_any_call(domain_id.hex)
65+
os_conn.identity.get_project.assert_any_call(project_id.hex)
9666
assert ret == 0
9767

9868

99-
@pytest.mark.parametrize(
100-
"only_domain",
101-
[
102-
None,
103-
lf("domain_id"),
104-
uuid.uuid4(),
105-
],
106-
)
10769
def test_delete_project(
10870
os_conn,
10971
nautobot,
11072
project_id: uuid.UUID,
111-
only_domain: uuid.UUID | DefaultDomain | None,
11273
):
113-
ret = do_action(os_conn, nautobot, Event.ProjectDelete, project_id, only_domain)
74+
ret = do_action(os_conn, nautobot, Event.ProjectDelete, project_id)
11475
assert ret == 0

python/understack-workflows/understack_workflows/bmc_chassis_info.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,7 @@ def combine_lldp(lldp, interface) -> InterfaceInfo:
100100
lldp_entry = lldp.get(name, lldp.get(alternate_name, {}))
101101
if not lldp_entry:
102102
logger.info(
103-
"LLDP info from BMC is missing for %s or %s, "
104-
"we only have LLDP info for %s",
103+
"LLDP info from BMC is missing for %s or %s, we only have LLDP info for %s",
105104
name,
106105
alternate_name,
107106
list(lldp.keys()),

python/understack-workflows/understack_workflows/bmc_credentials.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def _redfish_request(
144144
)
145145
if response.status_code >= 400:
146146
raise Exception(
147-
f"Redfish HTTP {response.status_code} " f"from {uri}: {response.text}"
147+
f"Redfish HTTP {response.status_code} from {uri}: {response.text}"
148148
)
149149

150150
else:

python/understack-workflows/understack_workflows/domain.py

Lines changed: 0 additions & 13 deletions
This file was deleted.

python/understack-workflows/understack_workflows/main/sync_keystone.py

Lines changed: 30 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import uuid
44
from enum import StrEnum
55

6-
from understack_workflows.domain import DefaultDomain
7-
from understack_workflows.domain import domain_id
86
from understack_workflows.helpers import credential
97
from understack_workflows.helpers import parser_nautobot_args
108
from understack_workflows.helpers import setup_logger
@@ -39,11 +37,6 @@ def argument_parser():
3937
help="Cloud to load. default: %(default)s",
4038
)
4139

42-
parser.add_argument(
43-
"--only-domain",
44-
type=domain_id,
45-
help="Only operate on projects from specified domain",
46-
)
4740
parser.add_argument("event", type=Event, choices=[item.value for item in Event])
4841
parser.add_argument(
4942
"object", type=uuid.UUID, help="Keystone ID of object the event happened on"
@@ -53,25 +46,6 @@ def argument_parser():
5346
return parser
5447

5548

56-
def is_valid_domain(
57-
conn: Connection,
58-
project_id: uuid.UUID,
59-
only_domain: uuid.UUID | DefaultDomain | None,
60-
) -> bool:
61-
if only_domain is None:
62-
return True
63-
project = conn.identity.get_project(project_id.hex) # type: ignore
64-
ret = project.domain_id == only_domain.hex
65-
if not ret:
66-
logger.info(
67-
"keystone project %s part of domain %s and not %s",
68-
project_id,
69-
project.domain_id,
70-
only_domain,
71-
)
72-
return ret
73-
74-
7549
def _create_outside_network(conn: Connection, project_id: uuid.UUID):
7650
network = _find_outside_network(conn, project_id.hex)
7751
if network:
@@ -120,44 +94,60 @@ def _find_outside_network(conn: Connection, project_id: str):
12094
)
12195

12296

97+
def _tenant_attrs(conn: Connection, project_id: uuid.UUID) -> tuple[str, str]:
98+
project = conn.identity.get_project(project_id.hex) # type: ignore
99+
domain_id = project.domain_id
100+
101+
if domain_id == "default":
102+
domain_name = "default"
103+
else:
104+
domain = conn.identity.get_project(domain_id) # type: ignore
105+
domain_name = domain.name
106+
107+
tenant_name = f"{domain_name}:{project.name}"
108+
return tenant_name, str(project.description)
109+
110+
123111
def handle_project_create(
124112
conn: Connection, nautobot: Nautobot, project_id: uuid.UUID
125113
) -> int:
126-
logger.info("got request to create tenant %s", project_id)
127-
project = conn.identity.get_project(project_id.hex) # type: ignore
128-
ten_api = nautobot.session.tenancy.tenants
114+
logger.info("got request to create tenant %s", project_id.hex)
115+
tenant_name, tenant_description = _tenant_attrs(conn, project_id)
116+
117+
nautobot_tenant_api = nautobot.session.tenancy.tenants
129118
try:
130-
ten = ten_api.create(
131-
id=str(project_id), name=project.name, description=project.description
119+
tenant = nautobot_tenant_api.create(
120+
id=str(project_id), name=tenant_name, description=tenant_description
132121
)
133122
_create_outside_network(conn, project_id)
134123
except Exception:
135124
logger.exception(
136-
"Unable to create project %s / %s", str(project_id), project.name
125+
"Unable to create project %s / %s", str(project_id), tenant_name
137126
)
138127
return _EXIT_API_ERROR
139128

140-
logger.info("tenant %s created %s", project_id, ten.created) # type: ignore
129+
logger.info("tenant %s created %s", project_id, tenant.created) # type: ignore
141130
return _EXIT_SUCCESS
142131

143132

144133
def handle_project_update(
145134
conn: Connection, nautobot: Nautobot, project_id: uuid.UUID
146135
) -> int:
147-
logger.info("got request to update tenant %s", project_id)
148-
project = conn.identity.get_project(project_id.hex) # type: ignore
149-
tenant_api = nautobot.session.tenancy.tenants
136+
logger.info("got request to update tenant %s", project_id.hex)
137+
tenant_name, tenant_description = _tenant_attrs(conn, project_id)
150138

139+
tenant_api = nautobot.session.tenancy.tenants
151140
existing_tenant = tenant_api.get(project_id)
152141
logger.info("existing_tenant: %s", existing_tenant)
153142
try:
154143
if existing_tenant is None:
155144
new_tenant = tenant_api.create(
156-
id=str(project_id), name=project.name, description=project.description
145+
id=str(project_id), name=tenant_name, description=tenant_description
157146
)
158147
logger.info("tenant %s created %s", project_id, new_tenant.created) # type: ignore
159148
else:
160-
existing_tenant.description = project.description # type: ignore
149+
existing_tenant.name = tenant_name # type: ignore
150+
existing_tenant.description = tenant_description # type: ignore
161151
existing_tenant.save() # type: ignore
162152
logger.info(
163153
"tenant %s last updated %s",
@@ -168,7 +158,7 @@ def handle_project_update(
168158
_create_outside_network(conn, project_id)
169159
except Exception:
170160
logger.exception(
171-
"Unable to update project %s / %s", str(project_id), project.name
161+
"Unable to update project %s / %s", str(project_id), tenant_name
172162
)
173163
return _EXIT_API_ERROR
174164
return _EXIT_SUCCESS
@@ -194,16 +184,7 @@ def do_action(
194184
nautobot: Nautobot,
195185
event: Event,
196186
project_id: uuid.UUID,
197-
only_domain: uuid.UUID | DefaultDomain | None,
198187
) -> int:
199-
if event in [Event.ProjectCreate, Event.ProjectUpdate] and not is_valid_domain(
200-
conn, project_id, only_domain
201-
):
202-
logger.info(
203-
"keystone project %s not part of %s, skipping", project_id, only_domain
204-
)
205-
return _EXIT_SUCCESS
206-
207188
match event:
208189
case Event.ProjectCreate:
209190
return handle_project_create(conn, nautobot, project_id)
@@ -224,4 +205,4 @@ def main() -> int:
224205
nb_token = args.nautobot_token or credential("nb-token", "token")
225206
nautobot = Nautobot(args.nautobot_url, nb_token, logger=logger)
226207

227-
return do_action(conn, nautobot, args.event, args.object, args.only_domain)
208+
return do_action(conn, nautobot, args.event, args.object)

workflows/argo-events/workflowtemplates/keystone-event-project.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ spec:
3333
command:
3434
- sync-keystone
3535
args:
36-
- "--only-domain"
37-
- "default"
3836
- "{{inputs.parameters.event_type}}"
3937
- "{{inputs.parameters.project_uuid}}"
4038
volumeMounts:

0 commit comments

Comments
 (0)