Trait Based Networking

Introduction

Trait Based Networking, or TBN for short, is an Ironic feature that allows an Openstack installation utilizing Ironic, Neutron, and Nova to dynamically configure port scheduling for Ironic nodes.

Enable and Configure

To configure and enable TBN for your Ironic installation please see Configure Trait Based Networking to Plan Networking Related Operations at vif Attach

Terminology

First some terms:

  • Node: An Ironic node.

  • Trait: A set of actions referred to by a name.

  • Action or Trait Action: A defined operation to apply to a node if the action’s Filter Expression matches network objects associated with a node.

  • Filter Expression: A boolean expression which filters for specific network objects like a port, portgroup, network or VIF.

  • Port: A network interface.

  • Portgroup: A set of ports, grouped together.

  • Dynamic Portgroup: An ephemeral portgroup that is created by a trait’s action and is subsequently destroyed once detached.

  • VIF: A virtual interface, which is the Neutron port supplied by the user. Only the VIF’s id, name and tags are available to Filter Expressions.

  • Network: The Neutron network that a VIF is attached to. Only a subset of the network is available to Filter Expressions: its name and tags.

Trait Actions

The core of TBN functionality lies in actions that each trait defines. Each action allows the operator to define how exactly TBN will setup networking for the node.

The following actions are available:

  • Attach port: Attach one or more ports belonging to a node to a network.

  • Attach portgroup: Attach one or more portgroups belonging to a node to a network.

  • Group and attach ports: Create a dynamic portgroup and attach it to a network.

In the future more actions may be added. See Available Actions for details on each action.

Configuration File Reference

Trait Based Networking’s trait configuration file is a YAML format file which defines a set of traits, and their corresponding actions. This file is ingested and validated by the Ironic conductor at start-up.

Trait Layout

Below is a valid YAML example trait:

CUSTOM_TRAIT_NAME:
  order: 1
  actions:
    - action: group_and_attach_ports
      filter: port.vendor == 'vendor_string'
      min_count: 2
    - action: attach_portgroup
      filter: port.is_portgroup && port.category == 'category_string'
      max_count: 1

CUSTOM_TRAIT_NAME is the trait’s name. Each trait is identified by a name which must start with CUSTOM. It’s order is 1. Ordering is ascending, so lower orders will apply first.

actions is a list of actions to apply if this trait matches one defined in a node’s instance_info.traits field.

Each action has the following necessary keys:

  • action - The action to take.

  • filter - The Filter Expression to apply with this action.

Note

Refer to Filter Expression Reference for detailed explanations on how to write valid filter expressions.

and the following optional keys:

  • max_count - The maximum number of objects that can match this action. There is no default maximum.

  • min_count - The minimum number of objects that must match before this action applies. The default minimum is effectively 1.

Available Actions

The following actions are currently available:

  • attach_port - Attach (port, network) pairs that pass this action’s filter expression. Only ports are considered; portgroups are never matched by this action.

  • attach_portgroup - Attach (portgroup, network) pairs that pass this action’s filter expression. Only portgroups are considered; use this action, not attach_port, with filters such as port.is_portgroup.

  • group_and_attach_ports - Select a set of ports. Create a dynamic portgroup comprised of the set of ports. Then attach the newly created dynamic portgroup to a suitable network. This action must set a min_count of at least 2. Also note that all ports selected for the portgroup must have the same physical_network.

Future actions are planned. This document will be updated as they become available.

Example Configuration File

An example Trait Based Networking configuration file is shipped with Ironic. A copy is available here. While backwards compatibility breaking changes are generally avoided where possible, please be aware that the linked copy may not be compatible with your version of Ironic.

Filter Expression Reference

Note

If this document disagrees with ironic.common.trait_based_networking.grammar.parser.FILTER_EXPRESSION_GRAMMAR then this document is wrong. FILTER_EXPRESSION_GRAMMAR is the ultimate source of truth regarding the grammar and parsing of TBN filter expressions.

A filter expression is a boolean expression which evaluates to True if the objects under consideration match the filter, and False otherwise.

Filter expressions allow Ironic operators to create custom filtering logic for traits which will apply specific network actions or operations to nodes.

Filter expressions consider three basic network objects:

  1. portlike: (aka port in this document) which can be either an Ironic port or portgroup.

  2. network: A subset of a Neutron network. Only the network’s name and tags are available to filter expressions.

  3. vif: The vif (virtual interface) being attached, which is the Neutron port supplied by the user. Only the vif’s id, name and tags are available to filter expressions.

A filter expression that evaluates to True for a given tuple of (portlike, network, vif) would cause a match to occur for the trait the filter belongs to. The trait’s defined actions would then apply if enough matches occur to satisfy the action’s requirements.

Single Expression

A single expression has the form of:

variable_name comparator string_literal

Where variable_name is one of the available variables, comparator is one of the available comparators, and string_literal is a valid string literal.

A full example of a single expression:

port.category == 'public'

Which would evaluate to True whenever a portlike is considered that has a category that exactly equals public. In plain English: ‘filter for ports and portgroups whose category is exactly the string “public”’.

Function Expression

A function expression has the form of:

function

See Functions for available functions.

Compound Expression

A compound expression consists of two expressions joined by a boolean operator.

port.category == 'public' && port.vendor == 'green'

In plain English: ‘filter for ports whose category is “public” and whose vendor is “green”’. Because vendor only exists on ports, this expression can never match a portgroup.

Parenthesis

Parenthesis can be used to group expressions together to guarantee evaluation precedence. For example:

port.category == 'private' || (port.vendor == 'purple' && network.name == 'hypernet')

Would cause the right-hand side of || to be evaluated together before evaluating the result against the left side of ||.

Comparators

Comparators allow comparisons between variables and string literals.

Comparator

Name

Explanation

==

Equality

Check for exact matches.

!=

Inequality

Check for any difference.

>=

Greater than or equal

Is the variable greater than or equal to the string literal?

>

Greater than

Is the variable greater than the string literal?

<=

Less than or equal

Is the variable less than or equal to the string literal?

<

Less than

Is the variable less than the string literal?

=~

Prefix match

Does the beginning of the variable match the string literal?

Examples

port.vendor == 'purple'

If a port’s vendor is exactly purple then this expression will evaluate to True and False otherwise.

port.category =~ 'green'

If a port’s category starts with the string green then this expression will evaluate to True and False otherwise.

network.name != 'private'

Match only networks if their name is NOT private.

vif.tags == 'storage'

Match only if the vif (the Neutron port supplied by the user) has the tag storage.

Boolean Operators

Used to join expressions to create complex filtering logic.

Operator

Name

Explanation

&&

And

If both expressions are True, then return True.

||

Or

If either expression is True, then return True.

Examples

port.vendor == 'purple' && port.category == 'private'

Will match a port if it’s vendor is purple and it’s category is private.

port.vendor == 'purple' || port.vendor == 'green'

Will match a port if it’s vendor is purple or green.

Functions

Functions allow basic querying of TBN related objects.

Function

Explanation

port.is_port

Returns True if the portlike under consideration is a port.

port.is_portgroup

Returns True if the portlike under consideration is a portgroup.

Examples

port.is_port

Will match portlikes which are a port.

Function expressions can also be linked with other expressions:

port.is_port && port.vendor == 'green'

String literal

String literals are enclosed by single quotes: '. Double quotes are not valid. String literals only allow alphanumeric characters, underscores, dashes, and periods.

The following regular expression encompasses valid string literals: /\'[A-Za-z0-9_\-\.]*\'/

Variables

Variables allow basic querying of network related objects in filter expressions. Available variables are listed below:

  • network.name

  • network.tags

  • vif.id

  • vif.name

  • vif.tags

  • port.address

  • port.category

  • port.physical_network

  • port.vendor

network.* variables describe the Neutron network that the vif is attached to, while vif.* variables describe the vif itself, which is the Neutron port supplied by the user. network.tags and vif.tags are collections: == matches if the string is one of the tags, != matches if it is not, and =~ matches if any tag starts with the string. Ordering comparators (<, <=, >, >=) are not supported on tags.

Why both network and vif?

A network is shared: many ports are attached to it and its name and tags describe the network as a whole, for example every port on a network named storage. A VIF is the single Neutron port the user supplied when attaching, so its name and tags describe that one port. A user creating a port can give it tags such as bonded or storage-replication that say how that specific interface should be wired, even though the port lives on a network used for other purposes. Filtering only on network.* cannot tell two ports on the same network apart, so vif.* variables allow an operator to select ports by what the user asked for, while network.* variables select by where the port is attached.

For example, to attach only ports tagged storage by the user, and only when they are attached to a network named backend:

vif.tags == 'storage' && network.name == 'backend'

A variable whose value is unset, such as port.category on a port without a category, never matches, regardless of comparator. This includes !=, so port.category != 'public' does not match a port without a category.

Note

port.vendor only applies to ports. Portgroups have no vendor field, so port.vendor is always unset for a portgroup and never matches. Use the port.is_portgroup or port.is_port functions to handle portgroups explicitly. For example, to match portgroups as well as ports from a given vendor:

port.is_portgroup || port.vendor == 'purple'