aci adapter¶
Features¶
The module allows to configure resources on aci. It can be used in two fashions:
Using the generated resources: clearly typed resources, but only show a subset of aci capabilities –>
aci::RelativeResourcesub entities.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:
Where the api is (what is the url?) –>
aci::Api,base_urlattributeWho should be managing resource on the api (username) –>
aci::Api,username, orusername_env_varattributeHow can the user authenticate to the api (password) –>
aci::Api,password, orpassword_env_varattribute
: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.
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,
)
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],
)
Referencing objects we don’t manage¶
A resource is always identified relatively to its parent, so the whole chain up to the
root has to be part of the model. Those ancestors usually already exist on the api and
we don’t want to configure them, only to point at them. For that, every generated
resource comes with a matching reference entity, named after it with a Reference
suffix (aci::fv::TenantReference for aci::fv::Tenant). A reference only carries what
identifies the object – its naming attributes and the relation to its own parent – and
is never exported as an orchestrator resource.
References and resources are interchangeable: every relation of the generated model (to a parent, to a child, …) accepts either of them, so a reference can be used anywhere in the configuration tree.
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,
)
# The root, the policy universe and the tenant already exist: we only point at them.
tenant = aci::fv::TenantReference(
parent=aci::pol::UniReference(parent=aci::RootReference(api=api)),
name="test",
)
# Only the objects below the tenant are managed.
vrf = aci::fv::Ctx(parent=tenant, name="test")
app = aci::fv::Ap(parent=tenant, name="test")
:bulb: Prefer a reference over an unmanaged resource (
managed=false) for the objects you only want to point at. An unmanaged resource is still exported, so two parts of a model that share an ancestor also share the resource representing it – which is not possible when they are deployed in different resource sets. A reference exports nothing, so each of them can declare the shared ancestor on its own.
:bulb: A reference can co-exist with the resource managing the same object: the index is defined on the resource and on the reference separately, not on the base entity they share. One part of the model can therefore manage an object while another one only points at it.
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 object whose aggregate_resource relation points to an aci::AggregateResource
puts all of its children (and their children, recursively) in that aggregate: they are
managed by it instead of being deployed on their own, 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_onlyis honored per resource: if a resource that needs a change is inreport_onlymode, the aggregate reports it as non-compliant instead of applying it.
:bulb: An aggregate only contains resources. A reference can be attached to one – the subtree below it is then pulled in as usual – but the reference itself is not part of the aggregate and can not be the root of a deployed tree. Use an unmanaged resource (
managed=false) as the common ancestor when you want the whole subtree in a single request.
Checking for faults¶
Aci accepts any well-formed configuration and reports what it can not apply
asynchronously, as faults on the objects. aci::FaultCheck fails when any of the objects
it checks carries an active fault. It runs once the checked resources are deployed and
keeps polling for faults until settle_delay seconds have passed since the last change
of a checked object.
check = aci::FaultCheck(
api=api,
name="my-tenant",
aggregates=[aggregate],
min_severity="warning",
ignored_codes=[],
)
Automatic conversion¶
To automatically convert an ACI json file into a model
install
acicobraandacimodelpython modules (can be obtained from an ACI instance onhttps://[apic_ip]/cobra/_downloads)run
aci_instance_generator some_file.json