---
title: Using Resource Managers
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from "@theme/CodeBlock";

The goal of this guide is to show you how to create a resource pool using the Python SDK, and how you can allocated resources with them.

:::note

The guide makes the assumption that we start with an Infrahub instance that doesn't have any data or schema loaded.

:::

## Loading a schema

Save the following schema in a file on your local system. The location or filename are not that important, but in this guide will be using `/tmp/schema.yml`

```yaml
---
version: "1.0"
nodes:
  - name: IPPrefix
    namespace: Ipam
    include_in_menu: false
    inherit_from:
      - "BuiltinIPPrefix"
    description: "IPv4 or IPv6 network"
    label: "IP Prefix"
  - name: IPAddress
    namespace: Ipam
    include_in_menu: false
    inherit_from:
      - "BuiltinIPAddress"
    description: "IP Address"
    label: "IP Address"
  - name: Device
    namespace: Infra
    description: "A Device"
    icon: "mdi:server"
    label: "Device"
    attributes:
      - name: name
        kind: Text
        label: Name
        optional: false
    relationships:
      - name: primary_ip
        label: "Primary IP Address"
        peer: IpamIPAddress
        optional: false
        kind: Attribute
        cardinality: one
```

Load the schema with the `infrahubctl` command.

```shell title="❯ infrahubctl schema load /tmp/schema.yml"
 schema '/tmp/schema.yml' loaded successfully
 1 schema processed in 6.846 seconds.
```

## Creating an IP Prefix object

Next we will be creating an IP Prefix object, which the resource manager will use as a resource to allocate resources from.

<Tabs groupId="async-sync">
  <TabItem value="Async" default>

  ```python
  from infrahub_sdk import InfrahubClient
  
  client = await InfrahubClient()
  prefix = await client.create(kind="IpamIPPrefix", prefix="10.0.0.0/24", member_type="address")
  await prefix.save()
  ```

  </TabItem>
  <TabItem value="Sync" default>

  ```python
  from infrahub_sdk import InfrahubClientSync
  
  client = InfrahubClient()
  prefix = client.create(kind="IpamIPPrefix", prefix="10.0.0.0/24", member_type="address")
  prefix.save()
  ```

  </TabItem>
</Tabs>

## Creating a resource manager

We can now create a resource manager of kind `CoreIPAddressPool`. The kind of the resource manager determines the kind of resource the manager will allocate.

We will create a `CoreIPaddressPool` with the following properties:

- Name: My IP address pool
- Default Address Type: `IpamIPAddress` (the kind of the IP address node defined in our schema)
- Default Prefix Size: 32
- Resources: 10.0.0.0/24
- IP Namespace: Namespace > Default

<Tabs groupId="async-sync">
  <TabItem value="Async" default>

  ```python
  pool = await client.create(
      kind="CoreIPAddressPool",
      name="My IP address pool",
      default_address_type="IpamIPAddress",
      default_prefix_size=32,
      resources=[prefix],
      is_pool=True,
      ip_namespace={"id": "default"}
  )
  await pool.save()
  ```

  </TabItem>
  <TabItem value="Sync" default>

  ```python
  pool = client.create(
      kind="CoreIPAddressPool",
      name="My IP address pool",
      default_address_type="IpamIPAddress",
      default_prefix_size=32,
      resources=[prefix],
      is_pool=True,
      ip_namespace={"id": "default"}
  )
  pool.save()
  ```

  </TabItem>
</Tabs>

## Allocating a resource out of the pool

We can now start allocating resources out of the `CoreIPAddressPool` we created.

We can use the resource manager to allocate resources out of a pool in 2 different ways:

1. Directly allocate a resource out of a pool. This is typically used when you need to allocate a resource that has no relation to other nodes. For example, allocating an IP address out of a pool that will be assigned to something that is not stored in Infrahub.
2. Allocate a resource out of a pool to a relationship of a node. For example, create a device and allocate an IP address out of a pool and assign it to the device

### Direct allocation of a resource

The Python SDK provides 2 methods that perform direct allocations:

- `allocate_next_ip_address` to allocate resources out of `CoreIPAddressPool` pools
- `allocate_next_ip_prefix` to allocated resources out of `CoreIPPrefixPool` pools

We will use `allocate_next_ip_address` to allocated an IP address out of the `CoreIPAddressPool` we created.

<Tabs groupId="async-sync">
  <TabItem value="Async" default>

  ```python
  ip_address = await client.allocate_next_ip_address(
      resource_pool=pool,
      data={"description": "my first allocated ip"}
  )
  ```

  </TabItem>
  <TabItem value="Sync" default>

  ```python
  ip_address = client.allocate_next_ip_address(
      resource_pool=pool,
      data={"description": "my first allocated ip"}
  )
  ```

  </TabItem>
</Tabs>

We provided a data argument to method. This data argument is used to pass attributes and relationships that we want to set on the allocated resource. In this case we are setting to the description of the allocated IP Address.

You can allocate resources in an idempotent way by passing an identifier argument to the allocation method. This identifier links the resource pool with the allocated resource allowing us to create idempotent allocation behavior. This is crucial when you want to allocate resources in an idempotent way using [generators](https://docs.infrahub.app/topics/generator).

In this example we are executing the `allocate_next_ip_address` method 2 times, using the same identifier.

<Tabs groupId="async-sync">
  <TabItem value="Async" default>

  ```python
  ip_address2 = await client.allocate_next_ip_address(resource_pool=pool, identifier="my-allocated-ip")
  ip_address3 = await client.allocate_next_ip_address(resource_pool=pool, identifier="my-allocated-ip")

  assert ip_address2.id == ip_address3.id
  assert ip_address2.address.value == ip_address3.address.value
  ```

  </TabItem>
  <TabItem value="Sync" default>

  ```python
  ip_address2 = client.allocate_next_ip_address(resource_pool=pool, identifier="my-allocated-ip")
  ip_address3 = client.allocate_next_ip_address(resource_pool=pool, identifier="my-allocated-ip")

  assert ip_address2.id == ip_address3.id
  assert ip_address2.address.value == ip_address3.address.value
  ```

  </TabItem>
</Tabs>

### Allocating resources to a relationship of a node

Another way we can use resource managers is in situations where we create a node that has a relationship and we want to use a resource manager to allocate a new resource for that relationship. For example, we want to create a new device (or server) and assign an IP address to the device out of a pool.

The Python SDK allows you to set the relationship of a Node to the resource pool that you want to allocate from. The resource will be allocated when you `save` the node.

<Tabs groupId="async-sync">
  <TabItem value="Async" default>

  ```python
  device = await client.create(kind="InfraDevice", name="dev-123", primary_ip=pool)
  await device.save()
  ```

  </TabItem>
  <TabItem value="Sync" default>

  ```python
  device = client.create(kind="InfraDevice", name="dev-123", primary_ip=pool)
  device.save()
  ```

  </TabItem>
</Tabs>

When you allocated to a relationship of a node, the resource allocation happens in an idempotent way. An identifier is automatically assigned to the resource allocation in this case.

## Branch agnostic resource allocation

Resource managers have to allocate resources in a branch agnostic way. For example if we allocate a resource in a branch, then that resource should also be allocated in the main branch, even if the resource object does not yet exist in the main branch.

Without this behavior we could allocate the resource multiple times, which should not be possible.

Create a branch named `test`

<Tabs groupId="async-sync">
  <TabItem value="Async" default>

  ```python
  await client.branch.create("test")
  ```

  </TabItem>
  <TabItem value="Sync" default>

  ```python
  client.branch.create("test")
  ```

  </TabItem>
</Tabs>

Allocate a new IP address in the `test` branch.

<Tabs groupId="async-sync">
  <TabItem value="Async" default>

  ```python
  ip_address4 = await client.allocate_next_ip_address(
     resource_pool=pool,
     branch="test"
  )
  ```

  </TabItem>
  <TabItem value="Sync" default>

  ```python
  ip_address4 = client.allocate_next_ip_address(
      resource_pool=pool,
      branch="test"
  )
  ```

  </TabItem>
</Tabs>

Currently there is no dedicated functionality in the Python SDK to do this, so for now we have to use a GraphQL query.

```graphql
query {
  InfrahubResourcePoolAllocated(pool_id: "<id of the pool>", resource_id: "<id of the prefix>") {
    edges {
      node {
        display_label
        branch
      }
    }
  }
  IpamIPAddress {
    edges {
      node {
        display_label
      }
    }
  }
}
```

Notice that we have one IP address allocated by the Resource manager in the test branch. The query in the main branch shows us this allocation, indicating that it has been allocated and the resource cannot be allocated again. However, the IP address does not exist itself within the main branch.

## CoreNumberPool and attribute allocation

`CoreNumberPool` allocates integer values (such as VLAN IDs or AS numbers) directly to node attributes. The pool assigns the integer value at the moment the node is created on the server.

```python
vlan = await client.create(
    kind="InfraVLAN",
    name="VLAN-100",
    vlan_id={"from_pool": {"id": pool_id}},
)
await vlan.save()
```

### Limitation: `allow_upsert=True` with a pool-sourced HFID attribute

`CoreNumberPool` assigns the integer value at server creation time. The SDK does not know the assigned value before the node exists. When a node's human-friendly identifier (HFID) includes a pool-sourced attribute, the SDK cannot construct the HFID needed to look up an existing node for upsert.

:::warning

Calling `save(allow_upsert=True)` on a node whose HFID contains a `CoreNumberPool`-sourced attribute raises `ValidationError` before any network call is made.

```python
# Schema has human_friendly_id: ["vlan_id__value"]
vlan = await client.create(
    kind="InfraVLAN",
    name="VLAN-100",
    vlan_id={"from_pool": {"id": pool_id}},
)

# This raises ValidationError — the pool-assigned vlan_id is unknown client-side
await vlan.save(allow_upsert=True)
```

**Alternatives:**

- **Two-step pattern** — create the node first, then update it in a separate call:

  ```python
  await vlan.save()           # creates node, pool assigns vlan_id
  # later, if you need to update:
  vlan.name.value = "VLAN-100-updated"
  await vlan.save()           # now _existing=True, calls update
  ```

- **Explicit id** — if you already know the node's UUID, set `node.id` before saving. The upsert will use the UUID directly and skip HFID lookup:

  ```python
  vlan.id = "known-uuid"
  await vlan.save(allow_upsert=True)  # guard bypassed
  ```

- **Deterministic identifier** — if possible, design your schema so the HFID uses a non-pool attribute (for example, a human-assigned `name`) and keep `vlan_id` out of the HFID.

:::
