Repository: netbox-community/netbox

Issue:
available_prefixes method doesn't allow to create prefix in Global if there are intersected prefixes in VRF 
### NetBox version

3.2.8

### Python version

3.10

### Steps to Reproduce

**1. Create container 10.182.0.0/15 in Global**

```
curl -X 'GET' \
  'https://ipam.local/api/ipam/prefixes/58204/' \
  -H 'accept: application/json' \
  -H 'Authorization: ***' \
  -H 'X-CSRFToken: ***'
```

```
{
  "id": 58204,
  "url": "https://ipam.local/api/ipam/prefixes/58204/",
  "display": "10.182.0.0/15",
  "family": {
    "value": 4,
    "label": "IPv4"
  },
  "prefix": "10.182.0.0/15",
  "site": null,
  "vrf": null,
  "tenant": null,
  "vlan": null,
  "status": {
    "value": "container",
    "label": "Container"
  },
  "role": null,
  "is_pool": false,
  "mark_utilized": false,
  "description": "",
  "tags": [],
  "custom_fields": {},
  "created": "2022-06-28T03:00:00+03:00",
  "last_updated": "2022-08-23T14:50:58.455367+03:00",
  "children": 0,
  "_depth": 1
}
```
**2. Create VRF test**

```
curl -X 'GET' \
  'https://ipam.local/api/ipam/vrfs/?id=80' \
  -H 'accept: application/json' \
  -H 'Authorization: ***' \
  -H 'X-CSRFToken: ***'
```

```
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 80,
      "url": "https://ipam.local/api/ipam/vrfs/80/",
      "display": "test",
      "name": "test",
      "rd": null,
      "tenant": null,
      "enforce_unique": true,
      "description": "",
      "import_targets": [],
      "export_targets": [],
      "tags": [],
      "custom_fields": {},
      "created": "2022-08-23T14:34:42.756926+03:00",
      "last_updated": "2022-08-23T14:34:42.756948+03:00",
      "ipaddress_count": 0,
      "prefix_count": 2
    }
  ]
}
```

**3. Create container 10.182.0.0/16 in VRF test**

```
curl -X 'GET' \
  'https://ipam.local/api/ipam/prefixes/60003/' \
  -H 'accept: application/json' \
  -H 'Authorization: ***' \
  -H 'X-CSRFToken: ***'
```

```
{
  "id": 60003,
  "url": "https://ipam.local/api/ipam/prefixes/60003/",
  "display": "10.182.0.0/16",
  "family": {
    "value": 4,
    "label": "IPv4"
  },
  "prefix": "10.182.0.0/16",
  "site": null,
  "vrf": {
    "id": 80,
    "url": "https://ipam.local/api/ipam/vrfs/80/",
    "display": "test",
    "name": "test",
    "rd": null
  },
  "tenant": null,
  "vlan": null,
  "status": {
    "value": "container",
    "label": "Container"
  },
  "role": null,
  "is_pool": false,
  "mark_utilized": false,
  "description": "I'm in VRF",
  "tags": [],
  "custom_fields": {},
  "created": "2022-08-23T14:35:15.263966+03:00",
  "last_updated": "2022-08-23T14:37:11.287683+03:00",
  "children": 0,
  "_depth": 0
}
```

**4. Create container 10.183.0.0/16 in VRF test**

```
curl -X 'GET' \
  'https://ipam.local/api/ipam/prefixes/60004/' \
  -H 'accept: application/json' \
  -H 'Authorization: ***' \
  -H 'X-CSRFToken: ***'
```

```
{
  "id": 60004,
  "url": "https://ipam.local/api/ipam/prefixes/60004/",
  "display": "10.183.0.0/16",
  "family": {
    "value": 4,
    "label": "IPv4"
  },
  "prefix": "10.183.0.0/16",
  "site": null,
  "vrf": {
    "id": 80,
    "url": "https://ipam.local/api/ipam/vrfs/80/",
    "display": "test",
    "name": "test",
    "rd": null
  },
  "tenant": null,
  "vlan": null,
  "status": {
    "value": "container",
    "label": "Container"
  },
  "role": null,
  "is_pool": false,
  "mark_utilized": false,
  "description": "I'm in VRF",
  "tags": [],
  "custom_fields": {},
  "created": "2022-08-23T14:35:25.692611+03:00",
  "last_updated": "2022-08-23T14:37:00.913414+03:00",
  "children": 0,
  "_depth": 0
}  
```

![image](https://user-images.githubusercontent.com/91027081/186163019-b8333b1d-93b1-4807-a8ef-9ccfd459514f.png)

**4. Use available_prefixes method to create a child inside 10.182.0.0/15 in Global**

```
curl -X 'GET' \
  'https://ipam.local/api/ipam/prefixes/58204/available-prefixes/' \
  -H 'accept: application/json' \
  -H 'Authorization: ***' \
  -H 'X-CSRFToken: ***'
```
	
**Response body**
[]  

```
curl -X 'POST' \
  'https://ipam.local/api/ipam/prefixes/58204/available-prefixes/' \
  -H 'accept: application/json' \
  -H 'Authorization: ***' \
  -H 'Content-Type: application/json' \
  -H 'X-CSRFToken: ***' \
  -d '{
  "prefix_length": 24
}'
```
	
**Error: response status is 409
Response body**

```
{
  "detail": "Insufficient space is available to accommodate the requested prefix size(s)"
}
```

Despite there is a lot of available space in 10.182.0.0/15 in Global we could not create a child.

### Expected Behavior

A new prefix 10.182.0.0/24 should have been created in Global 

### Observed Behavior

"Insufficient space is available to accommodate the requested prefix size(s)" error was returned

But if we use strict method (POST api/ipam/prefixes/) to create a prefix we will succeed

```
curl -X 'POST' \
  'https://ipam.local/api/ipam/prefixes/' \
  -H 'accept: application/json' \
  -H 'Authorization: ***' \
  -H 'Content-Type: application/json' \
  -H 'X-CSRFToken: ***' \
  -d '{
  "prefix": "10.182.0.0/24",
  "status": "active",
  "custom_fields": {}
}'
```

```
{
  "id": 60006,
  "url": "https://ipam.local/api/ipam/prefixes/60006/",
  "display": "10.182.0.0/24",
  "family": {
    "value": 4,
    "label": "IPv4"
  },
  "prefix": "10.182.0.0/24",
  "site": null,
  "vrf": null,
  "tenant": null,
  "vlan": null,
  "status": {
    "value": "active",
    "label": "Active"
  },
  "role": null,
  "is_pool": false,
  "mark_utilized": false,
  "description": "",
  "tags": [],
  "custom_fields": {},
  "created": "2022-08-23T16:28:04.574840+03:00",
  "last_updated": "2022-08-23T16:28:04.574862+03:00",
  "children": 0,
  "_depth": 0
}
```

Unfortunately this method is not protected by advisory lock as "available-prefixes"

![image](https://user-images.githubusercontent.com/91027081/186171050-9b4f9c40-660b-4c1c-b728-70081a896813.png)




Discussion:
Had to do a bit of digging for this one. The root issue is that the Prefix model's [`get_child_prefixes()`](https://github.com/netbox-community/netbox/blob/984d8b8ee6adc6a6e1b7713a133af312d67b883d/netbox/ipam/models/ip.py#L474) method specifically includes child prefixes from _any_ VRF for containers in the global table. This was discussed back in #1073, before the available IPs endpoint was introduced. I'm honestly not sure if that behavior is still valid, but that's a tangent of this bug.
To avoid any potentially breaking changes to the existing behavior, I'm going to work around the `get_child_prefixes()` method for available prefix calculations.

Resolving patch:
diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py
--- a/netbox/ipam/models/ip.py
+++ b/netbox/ipam/models/ip.py
@@ -35,13 +35,16 @@ class GetAvailablePrefixesMixin:
 
     def get_available_prefixes(self):
         """
-        Return all available Prefixes within this aggregate as an IPSet.
+        Return all available prefixes within this Aggregate or Prefix as an IPSet.
         """
-        prefix = netaddr.IPSet(self.prefix)
-        child_prefixes = netaddr.IPSet([child.prefix for child in self.get_child_prefixes()])
-        available_prefixes = prefix - child_prefixes
+        params = {
+            'prefix__net_contained': str(self.prefix)
+        }
+        if hasattr(self, 'vrf'):
+            params['vrf'] = self.vrf
 
-        return available_prefixes
+        child_prefixes = Prefix.objects.filter(**params).values_list('prefix', flat=True)
+        return netaddr.IPSet(self.prefix) - netaddr.IPSet(child_prefixes)
 
     def get_first_available_prefix(self):
         """


Test patch: