Skip to content

Commit e0d0f12

Browse files
committed
PUC-740: comparing data from Openstack Networks and Nautobot UCVNIs
1 parent 1c4859e commit e0d0f12

File tree

19 files changed

+1555
-0
lines changed

19 files changed

+1555
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
cd python/diff-nautobot-understack
3+
python3 -m venv .venv
4+
source .venv/bin/activate
5+
poetry lock
6+
poetry install
7+
8+
export NB_TOKEN=<get_token_from_nautobot_dev>
9+
poetry run diff-network
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
clouds:
2+
uc-dev-infra:
3+
auth_type: v3websso
4+
identity_provider: sso
5+
protocol: openid
6+
auth:
7+
auth_url: https://keystone.dev.undercloud.rackspace.net/v3
8+
project_domain_name: infra
9+
project_name: baremetal
10+
uc-dev:
11+
auth_type: v3websso
12+
identity_provider: sso
13+
protocol: openid
14+
auth:
15+
auth_url: https://keystone.dev.undercloud.rackspace.net/v3
16+
project_domain_name: Default
17+
uc-staging-infra:
18+
auth_type: v3websso
19+
identity_provider: sso
20+
protocol: openid
21+
auth:
22+
auth_url: https://keystone.staging.undercloud.rackspace.net/v3
23+
project_domain_name: infra
24+
project_name: baremetal
25+
uc-staging:
26+
auth_type: v3websso
27+
identity_provider: sso
28+
protocol: openid
29+
auth:
30+
auth_url: https://keystone.staging.undercloud.rackspace.net/v3
31+
project_domain_name: Default

python/diff-nautobot-understack/diff_nautobot_understack/__init__.py

Whitespace-only changes.

python/diff-nautobot-understack/diff_nautobot_understack/clients/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import os
2+
3+
import requests
4+
import logging
5+
import inspect
6+
from urllib.parse import urljoin
7+
8+
NB_URL = os.environ.get("NB_URL", "https://nautobot.dev.undercloud.rackspace.net")
9+
NB_TOKEN = os.environ.get("NB_TOKEN")
10+
11+
12+
class API:
13+
CALLER_FRAME = 1
14+
15+
def __init__(self):
16+
self.base_url = NB_URL
17+
self.s = requests.Session()
18+
self.token = NB_TOKEN
19+
self.s.headers.update({"Authorization": f"Token {NB_TOKEN}"})
20+
21+
def make_api_request(
22+
self, url: str, payload: dict | None = None, paginated: bool = False
23+
) -> dict | list:
24+
endpoint_url = urljoin(self.base_url, url)
25+
caller_function = inspect.stack()[self.CALLER_FRAME].function
26+
27+
logging.debug(
28+
"%(caller_function)s payload: %(payload)s",
29+
{"payload": payload, "caller_function": caller_function},
30+
)
31+
32+
if paginated:
33+
return self._fetch_paginated_data(endpoint_url, payload, caller_function)
34+
else:
35+
resp = self.s.get(endpoint_url, timeout=10, json=payload)
36+
return self._process_response(resp, caller_function)
37+
38+
def _fetch_paginated_data(
39+
self, endpoint_url: str, payload: dict | None, caller_function: str
40+
) -> list:
41+
response_items = []
42+
url = endpoint_url
43+
44+
while url is not None:
45+
resp = self.s.get(url, timeout=10, json=payload)
46+
resp_data = self._process_response(resp, caller_function)
47+
48+
response_items.extend(resp_data.get("results", []))
49+
url = resp_data.get("next")
50+
51+
return response_items
52+
53+
def _process_response(self, resp, caller_function: str) -> dict:
54+
if resp.content:
55+
resp_data = resp.json()
56+
else:
57+
resp_data = {"status_code": resp.status_code}
58+
59+
logging.debug(
60+
"%(caller_function)s resp: %(resp)s",
61+
{"resp": resp_data, "caller_function": caller_function},
62+
)
63+
64+
self._log_and_raise_for_status(resp)
65+
return resp_data
66+
67+
def _log_and_raise_for_status(self, resp):
68+
try:
69+
resp.raise_for_status()
70+
except Exception as e:
71+
logging.error(f"HTTP error occurred: {e}")
72+
raise
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import os
2+
3+
import openstack
4+
5+
OS_CLOUD = os.environ.get("OS_CLOUD", "uc-dev-infra")
6+
7+
8+
class API:
9+
def __init__(self):
10+
self.cloud_connection = openstack.connect(cloud=OS_CLOUD)

python/diff-nautobot-understack/diff_nautobot_understack/network/__init__.py

Whitespace-only changes.

python/diff-nautobot-understack/diff_nautobot_understack/network/adapters/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from diffsync import Adapter
2+
from diff_nautobot_understack.clients.openstack import API
3+
4+
from diff_nautobot_understack.network import models
5+
6+
7+
class Network(Adapter):
8+
network = models.NetworkModel
9+
10+
top_level = ["network"]
11+
type = "OpenstackNetwork"
12+
13+
def __init__(self, **kwargs):
14+
super().__init__(**kwargs)
15+
openstack_api = API()
16+
self.cloud = openstack_api.cloud_connection
17+
18+
def load(self):
19+
for network in self.cloud.network.networks():
20+
self.add(
21+
self.network(
22+
id=network.id,
23+
name=network.name,
24+
status=network.status.lower(),
25+
provider_physical_network=network.provider_physical_network,
26+
vni_id=network.provider_segmentation_id,
27+
)
28+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from diffsync import Adapter
2+
from pydantic import BaseModel
3+
from diff_nautobot_understack.clients.nautobot import API
4+
5+
from diff_nautobot_understack.network import models
6+
7+
8+
class UcvniDetails(BaseModel):
9+
id: str
10+
name: str
11+
status: str
12+
ucvni_id: int
13+
ucvni_group: str
14+
vlan_group: str
15+
vlan_id: int
16+
17+
18+
class NautobotError(Exception):
19+
message = "Nautobot error"
20+
21+
22+
class Network(Adapter):
23+
CALLER_FRAME = 1
24+
network = models.NetworkModel
25+
26+
top_level = ["network"]
27+
type = "UCVNI"
28+
29+
def __init__(self, **kwargs):
30+
super().__init__(**kwargs)
31+
self.api_client = API()
32+
33+
def load(self):
34+
ucvni_data: list[UcvniDetails] = self.ucvni_get()
35+
for ucvni_item in ucvni_data:
36+
network = self.network(
37+
id=ucvni_item.id,
38+
name=ucvni_item.name,
39+
vni_id=ucvni_item.vlan_id,
40+
provider_physical_network=ucvni_item.vlan_group,
41+
status=ucvni_item.status,
42+
)
43+
self.add(network)
44+
45+
def ucvni_get(
46+
self,
47+
) -> list[UcvniDetails]:
48+
ucvni_detail_list: list[UcvniDetails] = []
49+
50+
url = "/api/plugins/undercloud-vni/ucvnis/?include=relationship"
51+
52+
ucvnis_response = self.api_client.make_api_request(
53+
f"{url}/?include=relationships", paginated=True
54+
)
55+
56+
for ucvni_item in ucvnis_response:
57+
ucvni_group = self.api_client.make_api_request(
58+
url=ucvni_item.get("ucvni_group", {}).get("url")
59+
)
60+
status = self.api_client.make_api_request(
61+
url=ucvni_item.get("status", {}).get("url")
62+
)
63+
vlan_uuid_objects = (
64+
ucvni_item.get("relationships", {})
65+
.get("ucvni_vlans", {})
66+
.get("destination")
67+
.get("objects")
68+
)
69+
vlan_details = self.get_vlan_details(vlan_uuid_objects)
70+
vlan_group, vlan_ids = next(iter(vlan_details.items()))
71+
ucvni_details = UcvniDetails(
72+
id=ucvni_item.get("id"),
73+
name=ucvni_item.get("name"),
74+
ucvni_id=ucvni_item.get("ucvni_id"),
75+
ucvni_group=ucvni_group.get("name"),
76+
status=status.get("name").lower(),
77+
vlan_group=vlan_group,
78+
vlan_id=int(vlan_ids[0]),
79+
)
80+
ucvni_detail_list.append(ucvni_details)
81+
return ucvni_detail_list
82+
83+
def get_vlan_details(self, vlan_uuid_objects):
84+
vlan_uuids = [vlan_uuid_object["id"] for vlan_uuid_object in vlan_uuid_objects]
85+
vlan_details = {}
86+
87+
vlan_uuids_query_params = "&".join(f"id={value}" for value in vlan_uuids)
88+
vlan_url = f"/api/ipam/vlans/?{vlan_uuids_query_params}"
89+
90+
vlans_response = self.api_client.make_api_request(url=vlan_url, paginated=True)
91+
92+
for vlan_response in vlans_response:
93+
vlan_group_url = vlan_response.get("vlan_group", {}).get("url")
94+
95+
if vlan_group_url:
96+
vlan_group_response = self.api_client.make_api_request(
97+
url=vlan_group_url
98+
)
99+
vlan_group_name = vlan_group_response.get("name")
100+
vlan_id = vlan_response.get("vid")
101+
102+
if vlan_group_name:
103+
vlan_details.setdefault(vlan_group_name, []).append(vlan_id)
104+
105+
return vlan_details
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from pprint import pprint
2+
from diffsync import Diff
3+
from diffsync.enum import DiffSyncFlags
4+
from diff_nautobot_understack.network.adapters.openstack_network import (
5+
Network as OpenstackNetwork,
6+
)
7+
from diff_nautobot_understack.network.adapters.ucvni import Network as UcvniNetwork
8+
9+
10+
class MyDiff(Diff):
11+
"""Custom Diff class to control the order of the site objects."""
12+
13+
@classmethod
14+
def order_children_site(cls, children):
15+
"""Return the site children ordered in alphabetical order."""
16+
keys = sorted(children.keys(), reverse=False)
17+
for key in keys:
18+
yield children[key]
19+
20+
21+
def openstack_network_diff_from_ucvni_network():
22+
openstack_network = OpenstackNetwork()
23+
openstack_network.load()
24+
25+
ucvni_network = UcvniNetwork()
26+
ucvni_network.load()
27+
openstack_network_destination_ucvni_source = openstack_network.diff_from(
28+
ucvni_network, flags=DiffSyncFlags.CONTINUE_ON_FAILURE
29+
)
30+
pprint(" Nautobot ucvnis ⟹ Openstack networks ")
31+
summary = openstack_network_destination_ucvni_source.summary()
32+
pprint(summary, width=120)
33+
pprint(openstack_network_destination_ucvni_source.dict(), width=120)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from diffsync import DiffSyncModel
2+
3+
4+
class NetworkModel(DiffSyncModel):
5+
_modelname = "network"
6+
_identifiers = ("id",)
7+
_attributes = (
8+
"name",
9+
"status",
10+
"provider_physical_network",
11+
"vni_id",
12+
)
13+
14+
id: str
15+
name: str
16+
status: str
17+
provider_physical_network: str
18+
vni_id: int

python/diff-nautobot-understack/diff_nautobot_understack/project/__init__.py

Whitespace-only changes.

python/diff-nautobot-understack/diff_nautobot_understack/project/adapters/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import openstack
2+
3+
openstack.enable_logging(debug=True)
4+
5+
6+
def list_projects(conn):
7+
print("List Projects:")
8+
9+
for project in conn.identity.projects():
10+
print(project)
11+
12+
13+
if __name__ == "__main__":
14+
cloud_connection = openstack.connect(os_cloud="uc-dev-infra")
15+
list_projects(cloud_connection)

python/diff-nautobot-understack/diff_nautobot_understack/project/models.py

Whitespace-only changes.

0 commit comments

Comments
 (0)