opsmill / infrahub

Infrahub - A new approach to Infrastructure Management
https://opsmill.com/
GNU Affero General Public License v3.0
202 stars 13 forks source link

bug: creating multiple IP prefixes concurrently might lead to an incorrect prefix hierarchy #4523

Open wvandeun opened 1 month ago

wvandeun commented 1 month ago

Component

API Server / GraphQL

Infrahub version

0.16.2

Current Behavior

When you try to create multiple IP prefixes (in a hierarchy), and you create multiple IP prefix objects concurrently, it could lead to a scenario where the hierarchy of the prefixes is not correctly calculated.

A situation in which this can occur is when you have a generator/script that leverages the batches in the SDK.

In the example we want to create the following prefix hierarchy 10.0.0.0/8 10.0.0.0/16 10.0.0.0/24 10.0.1.0/24 10.0.2.0/24 10.0.3.0/24 10.1.0.0/16 10.1.0.0/24 10.1.1.0/24 10.1.2.0/24 10.1.3.0/24 10.2.0.0/16 10.2.0.0/24 10.2.1.0/24 10.2.2.0/24 10.2.3.0/24 10.3.0.0/16 10.3.0.0/24 10.3.1.0/24 10.3.2.0/24 10.3.3.0/24 .... 10.255.0.0/16 10.255.0.0/24 10.255.1.0/24 10.255.2.0/24 10.255.3.0/24

We could do that with the following script:

import ipaddress
import logging
from infrahub_sdk import InfrahubClient

async def run(client: InfrahubClient, log: logging.Logger, branch: str, num_devices: int=30) -> None:
    prefixes_batch  = await client.create_batch() 
    network_8 = ipaddress.IPv4Network("10.0.0.0/8")
    networks_16 = list(network_8.subnets(new_prefix=16))

    networks_24 = []
    for network in networks_16:
        tmp = list(network.subnets(new_prefix=24))[:4]
        networks_24.extend(tmp)

    networks = [network_8] + networks_16 + networks_24

    for network in networks:
        prefix = await client.create("IpamIPPrefix", prefix=f"{network}")
        prefixes_batch.add(task=prefix.save, node=prefix, allow_upsert=True)
        log.info(f"  Added prefix {network}")

    async for node, result in prefixes_batch.execute():
        print(f"prefix {node.prefix.value} was created in Infrahub succesfully")

The result will be that 10.0.0.0/16, 10.1.0.0/16 and 10.2.0.0/16 (and its children) will not have 10.0.0.0/8 as the parent prefix. Starting with 10.3.0.0/16 (and the other /16's) will all correctly have 10.0.0.0/8 as the parent.

This issue doesn't happen when you don't leverage the batch functionality

Expected Behavior

The prefixes should have the correct parent

Steps to Reproduce

async def run(client: InfrahubClient, log: logging.Logger, branch: str, num_devices: int=30) -> None: prefixes_batch = await client.create_batch() network_8 = ipaddress.IPv4Network("10.0.0.0/8") networks_16 = list(network_8.subnets(new_prefix=16))

networks_24 = []
for network in networks_16:
    tmp = list(network.subnets(new_prefix=24))[:4]
    networks_24.extend(tmp)

networks = [network_8] + networks_16 + networks_24

for network in networks:
    prefix = await client.create("IpamIPPrefix", prefix=f"{network}")
    prefixes_batch.add(task=prefix.save, node=prefix, allow_upsert=True)
    log.info(f"  Added prefix {network}")

async for node, result in prefixes_batch.execute():
    print(f"prefix {node.prefix.value} was created in Infrahub succesfully")

- Navigate to the IP Prefixes in the frontend

### Additional Information

_No response_
exalate-issue-sync[bot] commented 4 days ago

[From lykinsbd]

Discussed in Weekly Planning today (Oct 29th). Suggestions:

Further discussion is required.