aci adapter

Features

The module allows to configure resources on aci. It can be used in two fashions:

  1. Using the generated resources: clearly typed resources, but only show a subset of aci capabilities –> aci::RelativeResource sub entities.

  2. Using generic resources: untyped resources, the structure of the resource is the responsibility of the module user –> aci::StandaloneResource.

Api configuration

Aci api is protected by authentication, the adapter needs to receive some authentication info in order to function properly. Mainly the adapter needs to know:

  1. Where the api is (what is the url?) –> aci::Api, base_url attribute

  2. Who should be managing resource on the api (username) –> aci::Api, username, or username_env_var attribute

  3. How can the user authenticate to the api (password) –> aci::Api, password, or password_env_var attribute

:bulb: For username and password, either the raw value or the environment variable should provided. If the environment variable is used, the value the attribute should receive is the name of the environment variable that the agent has access to, and in which the expected value (username or password value) can be found.

Usage example

Here are two equivalent models illustrating the usage of the module, using the generated resources and the generic ones.

  1. Generated resources:

import aci
import aci::pol
import aci::fv

api = aci::Api(
    name="api",
    base_url="https://192.168.26.200",
    username_env_var="ACI_API_USERNAME",
    password_env_var="ACI_API_PASSWORD",
    verify=false,
    retry_max_attempts=5,
    retry_interval=3,
)

tenant = aci::fv::Tenant(
    parent=aci::pol::Uni(parent=aci::Root(api=api, managed=false), managed=false),
    name="test",
    managed=false,
)

vrf = aci::fv::Ctx(
    parent=tenant,
    name="test",
    purged=false,
)

app = aci::fv::Ap(
    parent=tenant,
    name="test",
    purged=false,
)

bd = aci::fv::BD(
    parent=tenant,
    name="test",
    purged=false,
)

rs_ctx = aci::fv::RsCtx(
    parent=bd,
    tnFvCtxName=vrf.name,
    requires=[bd, vrf],
)

epg = aci::fv::AEPg(
    parent=app,
    name="test",
    descr="10",
    fvRsBd=aci::fv::RsBd(
        tnFvBDName=bd.name,
        requires=[epg, bd],
    ),
    fvRsPathAtt=aci::fv::RsPathAtt(
        tDn="topology/pod-1/paths-101/pathep-[eth1/5]",
        encap="vlan-11",
        requires=epg,
    ),
    requires=app,
)
  1. Generic resources

import aci

api = aci::Api(
    name="api",
    base_url="https://192.168.26.200",
    username_env_var="ACI_API_USERNAME",
    password_env_var="ACI_API_PASSWORD",
    verify=false,
    retry_max_attempts=5,
    retry_interval=3,
)

tenant = aci::StandaloneResource(
    api=api,
    mo_class_name="fvTenant",
    rn_format="tn-%(name)s",
    parent_dn="uni",
    attributes={
        "name": "test",
    },
    managed=false,
)

vrf = aci::StandaloneResource(
    api=api,
    mo_class_name="fvCtx",
    rn_format="ctx-%(name)s",
    parent_dn=tenant.dn,
    attributes={
        "name": "test",
    },
    requires=tenant,
    purged=false,
)

app = aci::StandaloneResource(
    api=api,
    mo_class_name="fvAp",
    rn_format="ap-%(name)s",
    parent_dn=tenant.dn,
    attributes={
        "name": "test",
    },
    requires=tenant,
    purged=false,
)

bd = aci::StandaloneResource(
    api=api,
    mo_class_name="fvBD",
    rn_format="BD-%(name)s",
    parent_dn=tenant.dn,
    attributes={
        "name": "test",
    },
    requires=tenant,
    purged=false,
)

rs_ctx = aci::StandaloneResource(
    api=api,
    mo_class_name="fvRsCtx",
    rn_format="rsctx",
    parent_dn=bd.dn,
    attributes={
        "tnFvCtxName": vrf.attributes["name"],
    },
    requires=[bd, vrf],
)

epg = aci::StandaloneResource(
    api=api,
    parent_dn=app.dn,
    rn_format="epg-%(name)s",
    mo_class_name="fvAEPg",
    attributes={
        "name": "test",
        "descr": "10",
    },
    requires=app,
)

rs_bd = aci::StandaloneResource(
    api=api,
    parent_dn=epg.dn,
    rn_format="rsbd",
    mo_class_name="fvRsBd",
    attributes={
        "tnFvBDName": bd.attributes["name"],
    },
    requires=[epg, bd],
)

static_port = aci::StandaloneResource(
    api=api,
    parent_dn=epg.dn,
    rn_format="rspathAtt-[%(tDn)s]",
    mo_class_name="fvRsPathAtt",
    attributes={
        "tDn": "topology/pod-1/paths-101/pathep-[eth1/5]",
        "encap": "vlan-11",
    },
    requires=[epg],
)

Aggregating resources

When a model defines many aci resources, exporting and deploying each of them as an individual orchestrator resource can be expensive. The aci::AggregateResource lets you group many resources into a single orchestrator resource.

Every resource whose aggregate_resource relation points to an aci::AggregateResource is managed by that aggregate instead of being deployed on its own. All of its children (and their children, recursively) are automatically pulled into the same aggregate, so attaching a single common ancestor is enough to aggregate a whole configuration tree.

Resources that belong to the same configuration tree are both read and pushed to the api in a single request each: the aggregate handler reads the whole tree with one subtree query (query-target=subtree) and deploys it (creations, modifications and deletions at once) with one POST of the corresponding managed-object subtree. Attaching a common ancestor (e.g. a aci::fv::Tenant, even an unmanaged one used only as a container) is therefore enough to read and deploy its whole subtree in a single request each.

import aci
import aci::pol
import aci::fv

api = aci::Api(
    name="api",
    base_url="https://192.168.26.200",
    username_env_var="ACI_API_USERNAME",
    password_env_var="ACI_API_PASSWORD",
    verify=false,
)

aggregate = aci::AggregateResource(api=api, name="my-tenant")

# Attaching the tenant is enough: vrf, app, bd, epg, ... are pulled in automatically and
# the whole tree is deployed in a single api request.
tenant = aci::fv::Tenant(
    parent=aci::pol::Uni(parent=aci::Root(api=api, managed=false), managed=false),
    name="test",
    managed=false,
    aggregate_resource=aggregate,
)

vrf = aci::fv::Ctx(parent=tenant, name="test")
app = aci::fv::Ap(parent=tenant, name="test")
bd = aci::fv::BD(parent=tenant, name="test")
rs_ctx = aci::fv::RsCtx(parent=bd, tnFvCtxName=vrf.name, requires=[bd, vrf])
epg = aci::fv::AEPg(
    parent=app,
    name="test",
    descr="10",
    fvRsBd=aci::fv::RsBd(tnFvBDName=bd.name, requires=[epg, bd]),
    requires=app,
)

:bulb: Dependencies (requires/provides) between a resource outside of the aggregate and any aggregated resource — including auto-discovered children — are preserved: they are reflected onto the aggregate resource itself. Dependencies between two resources of the same aggregate need no ordering: the whole tree is configured in a single atomic api request.

:bulb: report_only is honored per resource: if a resource that needs a change is in report_only mode, the aggregate reports it as non-compliant instead of applying it.

Automatic conversion

To automatically convert an ACI json file into a model

  1. install acicobra and acimodel python modules (can be obtained from an ACI instance on https://[apic_ip]/cobra/_downloads)

  2. run aci_instance_generator some_file.json