Source code for ironic.drivers.modules.network.switchport_config

#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

"""SwitchPortConfig - parsed representation of switchport configuration.

This module provides a dataclass for representing and parsing the switchport
configuration format used by the Ironic Networking interface:

    {access|trunk|hybrid}/native_vlan=VLAN_ID[/allowed_vlans=V1,V2,Vn-Vm,...]
"""

import dataclasses
from typing import Optional

from ironic.common import exception
from ironic.common.i18n import _
from ironic.networking import utils as net_utils

VALID_MODES = ('access', 'trunk', 'hybrid')


[docs] @dataclasses.dataclass(frozen=True) class SwitchPortConfig: """Parsed representation of a switchport configuration value. Instances can be created from a configuration string via :meth:`from_string` or from a switchport dict via :meth:`from_switchport`. """ mode: str native_vlan: Optional[int] = None allowed_vlans: Optional[list] = None
[docs] @classmethod def from_string(cls, value, network_type='unknown'): """Parse a network config string into a SwitchPortConfig. :param value: String in format ``{access|trunk|hybrid}/native_vlan=N[/allowed_vlans=V1,V2,Vn-Vm]`` :param network_type: Network type label used in error messages. :returns: A SwitchPortConfig instance. :raises: InvalidParameterValue if the format is invalid. """ try: parts = value.split('/') mode = parts[0].strip() if not mode: raise ValueError("mode is empty") if mode not in VALID_MODES: raise ValueError( f"mode must be one of {VALID_MODES}, " f"got '{mode}'") if len(parts) < 2: raise ValueError( "expected at least mode/native_vlan=N") native_vlan = None allowed_vlans = None for part in parts[1:]: key, sep, val = part.partition('=') if not sep: raise ValueError( f"expected key=value, got '{part}'") key = key.strip() val = val.strip() if key == 'native_vlan': native_vlan = int(val) elif key == 'allowed_vlans': allowed_vlans = cls._parse_allowed_vlans( val) except (ValueError, IndexError) as e: raise exception.InvalidParameterValue( _("Invalid %(network_type)s network value '%(value)s'. " "Expected: {access|trunk|hybrid}/native_vlan=VLAN_ID" "[/allowed_vlans=V1,V2,Vn-Vm,...]. " "Error: %(error)s") % {'network_type': network_type, 'value': value, 'error': e}) return cls(mode=mode, native_vlan=native_vlan, allowed_vlans=allowed_vlans)
@staticmethod def _parse_allowed_vlans(val): """Parse the allowed_vlans value string. Each element may be a single VLAN ID (``"100"``) or a range (``"100-200"``). Ranges are expanded into individual VLAN IDs using :func:`ironic.networking.utils.parse_vlan_ranges`. :param val: Comma-separated string, e.g. ``"1,2,4-7,9"``. :returns: Sorted list of integer VLAN IDs. :raises: InvalidParameterValue if any element is not a valid VLAN ID or range. """ vlan_set = net_utils.parse_vlan_ranges(val) return sorted(vlan_set)
[docs] @classmethod def from_switchport(cls, switchport): """Create a SwitchPortConfig from a switchport configuration dict. :param switchport: A dict with optional keys ``mode``, ``native_vlan``, and ``allowed_vlans``. :returns: A SwitchPortConfig instance, or None if the dict does not contain a ``mode`` key. :raises: InvalidParameterValue if ``mode`` is not a valid mode or ``native_vlan`` is not an integer. """ mode = switchport.get('mode') if not mode: return None if mode not in VALID_MODES: raise exception.InvalidParameterValue( _("Invalid switchport mode '%(mode)s'. " "Must be one of %(valid)s.") % {'mode': mode, 'valid': VALID_MODES}) native_vlan = switchport.get('native_vlan') if native_vlan is not None and not isinstance( native_vlan, int): raise exception.InvalidParameterValue( _("native_vlan must be an integer, " "got %(type)s.") % {'type': type(native_vlan).__name__}) return cls( mode=mode, native_vlan=native_vlan, allowed_vlans=switchport.get('allowed_vlans'), )
@property def is_valid(self): """True if the config has the required fields for its mode. For ``access`` mode, ``native_vlan`` must be set. For ``trunk`` or ``hybrid`` mode, ``allowed_vlans`` must be set. """ if not self.mode: return False if self.mode == 'access': return self.native_vlan is not None # trunk / hybrid return self.allowed_vlans is not None