Skip to content

New Example06 - IP Prefixes #111

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions examples/06-ip-prefixes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Example 06 - IP Prefixes

This example shows how to play around to IPAM systems which have a different implementation of an IP Prefix.

These IPAM systems, IPAM A and IPAM B, are simulated using two YAML files within the `data` folder. These files are dynamic, and they will be loaded and updated from diffsync.

## Test the example

You could simply run the `main.py` file, but to run step by step.

### Set up the environment

Install the dependencies (recommended into a virtual environment)

```
pip3 install -r requirements.txt
```

and go into a `python` interactive session:

```
python3
>>>
```

### Import the DiffSync adapters

```py
>>> from adapter_ipam_a import IpamA
>>> from adapter_ipam_b import IpamB
```

### Initialize and load adapter for IPAM A

```py
>>> ipam_a = IpamA()
>>> ipam_a.load()
```

You can check the content loaded from IPAM A. Notice that the data has been transformed into the DiffSync model, which is different from the original YAML data.

```py
>>> import pprint
>>> pprint.pprint(ipam_a.dict())
{'prefix': {'10.10.10.10/24': {'prefix': '10.10.10.10/24',
'vlan_id': 10,
'vrf': 'data'},
'10.20.20.20/24': {'prefix': '10.20.20.20/24',
'tenant': 'ABC corp',
'vlan_id': 20,
'vrf': 'voice'},
'172.18.0.0/16': {'prefix': '172.18.0.0/16', 'vlan_id': 18}}}
```

### Initialize and load adapter for IPAM B

```py
>>> ipam_b = IpamB()
>>> ipam_b.load()
```

You can check the content loaded from IPAM B. Notice that the data has been transformed into the DiffSync model, which again is different from the original YAML format.

```py
>>> pprint.pprint(ipam_b.dict())
{'prefix': {'10.10.10.10/24': {'prefix': '10.10.10.10/24', 'vlan_id': 123},
'2001:DB8::/32': {'prefix': '2001:DB8::/32',
'tenant': 'XYZ Corporation',
'vlan_id': 10,
'vrf': 'data'}}}
```

### Check the difference

We can use `diff_to` or `diff_from` to select, from the perspective of the calling adapter, who is the authoritative in each case.

```py
>>> diff = ipam_a.diff_to(ipam_b)
```

From this `diff`, we can check the summary of what would happen.

```py
>>> diff.summary()
{'create': 2, 'update': 1, 'delete': 1, 'no-change': 0}
```

And, also go into the details. We can see how the `'+'` and + `'-'` represent the actual changes in the target adapter: create, delete or update (when both symbols appear).

```py
>>> pprint.pprint(diff.dict())
{'prefix': {'10.10.10.10/24': {'+': {'vlan_id': 10, 'vrf': 'data'},
'-': {'vlan_id': 123, 'vrf': None}},
'10.20.20.20/24': {'+': {'tenant': 'ABC corp',
'vlan_id': 20,
'vrf': 'voice'}},
'172.18.0.0/16': {'+': {'tenant': None,
'vlan_id': 18,
'vrf': None}},
'2001:DB8::/32': {'-': {'tenant': 'XYZ Corporation',
'vlan_id': 10,
'vrf': 'data'}}}}
```

### Enforce synchronization

Simply transforming the `diff_to` to `sync_to`, we are going to change the state of the destination target.

```py
>>> ipam_a.sync_to(ipam_b)
```

### Validate synchronization

Now, if we reload the IPAM B, and try to check the difference, we should see no differences.

```py
>>> new_ipam_b = IpamB()
>>> new_ipam_b.load()
>>> diff = ipam_a.diff_to(new_ipam_b)
>>> diff.summary()
{'create': 0, 'update': 0, 'delete': 0, 'no-change': 3}
```
83 changes: 83 additions & 0 deletions examples/06-ip-prefixes/adapter_ipam_a.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""IPAM A adapter."""
import os
import ipaddress
import yaml
from models import Prefix # pylint: disable=no-name-in-module
from diffsync import DiffSync

dirname = os.path.dirname(os.path.realpath(__file__))


class IpamAPrefix(Prefix):
"""Implementation of Prefix create/update/delete methods for IPAM A."""

@classmethod
def create(cls, diffsync, ids, attrs):
"""Create a Prefix record in IPAM A."""
diffsync.data.append(
{
"cidr": ids["prefix"],
"family": ipaddress.ip_address(ids["prefix"].split("/")[0]).version,
"vrf": attrs["vrf"],
"vlan": f'VLAN{attrs["vlan_id"]}',
"customer_id": attrs["tenant"] if attrs["tenant"] else None,
}
)

return super().create(diffsync, ids=ids, attrs=attrs)

def update(self, attrs):
"""Update a Prefix record in IPAM A."""
for elem in self.diffsync.data:
if elem["cidr"] == self.prefix:
if "vrf" in attrs:
elem["vrf"] = attrs["vrf"]
if "vlan_id" in attrs:
elem["vlan_id"] = f'VLAN{attrs["vlan_id"]}'
if "tenant" in attrs:
elem["customer_id"] = attrs["tenant"]
break

return super().update(attrs)

def delete(self):
"""Delete a Prefix record in IPAM A."""
for index, elem in enumerate(self.diffsync.data):
if elem["cidr"] == self.prefix:
del self.diffsync.data[index]
break

return super().delete()


class IpamA(DiffSync):
"""IPAM A DiffSync adapter implementation."""

prefix = IpamAPrefix

top_level = ["prefix"]

def __init__(self, *args, **kwargs):
"""Initialize the IPAM A Adapter."""
super().__init__(*args, **kwargs)

with open(os.path.join(dirname, "data", "ipam_a.yml"), encoding="utf-8") as data_file:
self.data = yaml.safe_load(data_file)

def load(self):
"""Load prefixes from IPAM A."""
for subnet in self.data:
prefix = self.prefix(
prefix=subnet["cidr"],
vrf=subnet["vrf"],
vlan_id=int(subnet["vlan"].lstrip("VLAN")),
tenant=subnet["customer_id"],
)
self.add(prefix)

def sync_complete(self, source, *args, **kwargs):
"""Clean up function for DiffSync sync."""
with open(os.path.join(dirname, "data", "ipam_a.yml"), encoding="utf-8", mode="w") as data_file:
yaml.safe_dump(self.data, data_file)

return super().sync_complete(source, *args, **kwargs)
88 changes: 88 additions & 0 deletions examples/06-ip-prefixes/adapter_ipam_b.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""IPAM B adapter."""
import os
import yaml
from models import Prefix # pylint: disable=no-name-in-module
from diffsync import DiffSync

dirname = os.path.dirname(os.path.realpath(__file__))


class IpamBPrefix(Prefix):
"""Implementation of Prefix create/update/delete methods for IPAM B."""

@classmethod
def create(cls, diffsync, ids, attrs):
"""Create a Prefix record in IPAM B."""
diffsync.data.append(
{
"network": ids["prefix"].split("/")[0],
"prefix_length": int(ids["prefix"].split("/")[1]),
"vrf": attrs["vrf"],
"vlan_id": attrs["vlan_id"],
"tenant": attrs["tenant"] if attrs["tenant"] else None,
}
)

return super().create(diffsync, ids=ids, attrs=attrs)

def update(self, attrs):
"""Update a Prefix record in IPAM B."""
network = self.prefix.split("/")[0]
prefix_length = int(self.prefix.split("/")[1])

for elem in self.diffsync.data:
if elem["network"] == network and elem["prefix_length"] == prefix_length:
if "vrf" in attrs:
elem["vrf"] = attrs["vrf"]
if "vlan_id" in attrs:
elem["vlan_id"] = attrs["vlan_id"]
if "tenant" in attrs:
elem["tenant"] = attrs["tenant"]
break

return super().update(attrs)

def delete(self):
"""Update a Prefix record in IPAM B."""
network = self.prefix.split("/")[0]
prefix_length = int(self.prefix.split("/")[1])

for index, elem in enumerate(self.diffsync.data):
if elem["network"] == network and elem["prefix_length"] == prefix_length:
del self.diffsync.data[index]
break

return super().delete()


class IpamB(DiffSync):
"""IPAM A DiffSync adapter implementation."""

prefix = IpamBPrefix

top_level = ["prefix"]

def __init__(self, *args, **kwargs):
"""Initialize the IPAM B Adapter."""
super().__init__(*args, **kwargs)

with open(os.path.join(dirname, "data", "ipam_b.yml"), encoding="utf-8") as data_file:
self.data = yaml.safe_load(data_file)

def load(self):
"""Initialize the Ipam B Object by loading from DATA."""
for prefix_data in self.data:
prefix = self.prefix(
prefix=f"{prefix_data['network']}/{prefix_data['prefix_length']}",
vrf=prefix_data["vrf"],
vlan_id=prefix_data["vlan_id"],
tenant=prefix_data["tenant"],
)
self.add(prefix)

def sync_complete(self, source, *args, **kwargs):
"""Clean up function for DiffSync sync."""
with open(os.path.join(dirname, "data", "ipam_b.yml"), encoding="utf-8", mode="w") as data_file:
yaml.safe_dump(self.data, data_file)

return super().sync_complete(source, *args, **kwargs)
16 changes: 16 additions & 0 deletions examples/06-ip-prefixes/data/ipam_a.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
- cidr: "10.10.10.10/24"
family: 4
vrf: "data"
vlan: "VLAN10"
customer_id: null
- cidr: "10.20.20.20/24"
family: 4
vrf: "voice"
vlan: "VLAN20"
customer_id: "ABC corp"
- cidr: "172.18.0.0/16"
family: 4
vrf: null
vlan: "VLAN18"
customer_id: null
11 changes: 11 additions & 0 deletions examples/06-ip-prefixes/data/ipam_b.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
- network: "10.10.10.10"
prefix_length: 24
tenant: null
vlan_id: 123
vrf: "voice"
- network: "2001:DB8::"
prefix_length: 32
tenant: "XYZ Corporation"
vlan_id: 10
vrf: "data"
13 changes: 13 additions & 0 deletions examples/06-ip-prefixes/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env python
"""Main example."""
from adapter_ipam_a import IpamA
from adapter_ipam_b import IpamB


if __name__ == "__main__":
ipam_a = IpamA()
ipam_b = IpamB()
ipam_a.load()
ipam_b.load()
diff = ipam_a.diff_to(ipam_b)
# ipam_a.sync_to(ipam_b)
16 changes: 16 additions & 0 deletions examples/06-ip-prefixes/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""DiffSync models."""
from typing import Optional
from diffsync import DiffSyncModel


class Prefix(DiffSyncModel):
"""Example model of a Prefix."""

_modelname = "prefix"
_identifiers = ("prefix",)
_attributes = ("vrf", "vlan_id", "tenant")

prefix: str
vrf: Optional[str]
vlan_id: Optional[int]
tenant: Optional[str]
2 changes: 2 additions & 0 deletions examples/06-ip-prefixes/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
diffsync
pyyaml
8 changes: 8 additions & 0 deletions tests/unit/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,11 @@ def test_example_4():
example4_main = join(example4_dir, "main.py")
# Run it and make sure it doesn't raise an exception or otherwise exit with a non-zero code.
subprocess.run(example4_main, cwd=example4_dir, check=True)


def test_example_6():
"""Test that the "example6" script runs successfully."""
example6_dir = join(EXAMPLES, "06-ip-prefixes")
example6_main = join(example6_dir, "main.py")
# Run it and make sure it doesn't raise an exception or otherwise exit with a non-zero code.
subprocess.run(example6_main, cwd=example6_dir, check=True)