#
# 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.
"""
Client side of the networking RPC API.
"""
from oslo_log import log
import oslo_messaging as messaging
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common.json_rpc import client as json_rpc
from ironic.common import release_mappings as versions
from ironic.common import rpc
from ironic.conf import CONF
from ironic.networking import utils as networking_utils
from ironic.objects import base as objects_base
LOG = log.getLogger(__name__)
[docs]
class NetworkingAPI(object):
"""Client side of the networking RPC API.
API version history:
| 1.0 - Initial version.
"""
# NOTE(alegacy): This must be in sync with manager.NetworkingManager's.
RPC_API_VERSION = "1.0"
def __init__(self, topic=None):
super(NetworkingAPI, self).__init__()
self.topic = topic
if self.topic is None:
if networking_utils.rpc_transport() == "json-rpc":
# Use host_ip and port from the JSON-RPC config for topic
host_ip = CONF.ironic_networking_json_rpc.host_ip
port = CONF.ironic_networking_json_rpc.port
topic_host = f"{host_ip}:{port}"
self.topic = f"ironic.{topic_host}"
else:
self.topic = rpc.NETWORKING_TOPIC
serializer = objects_base.IronicObjectSerializer()
release_ver = versions.RELEASE_MAPPING.get(CONF.pin_release_version)
version_cap = (
release_ver.get("networking_rpc")
if release_ver else self.RPC_API_VERSION
)
if networking_utils.rpc_transport() == "json-rpc":
# Use a dedicated configuration group for networking JSON-RPC
self.client = json_rpc.Client(
serializer=serializer,
version_cap=version_cap,
conf_group="ironic_networking_json_rpc",
)
# Keep the original topic for JSON-RPC (needed for host extraction)
elif networking_utils.rpc_transport() != "none":
target = messaging.Target(topic=self.topic, version="1.0")
self.client = rpc.get_client(
target, version_cap=version_cap, serializer=serializer
)
else:
self.client = None
def _prepare_call(self, topic, version=None):
"""Prepare an RPC call.
:param topic: RPC topic to send to.
:param version: RPC API version to require.
"""
topic = topic or self.topic
# A safeguard for the case someone uses rpc_transport=None
if self.client is None:
raise exception.ServiceUnavailable(
_("Cannot use 'none' RPC to connect to networking service")
)
# Normal RPC path
return self.client.prepare(topic=topic, version=version)
[docs]
def get_topic(self):
"""Get RPC topic name for the networking service."""
return self.topic
[docs]
def update_port(
self,
context,
switch_id,
port_name,
description,
mode,
native_vlan,
allowed_vlans=None,
default_vlan=None,
lag_name=None,
topic=None,
):
"""Update a port configuration on a switch.
:param context: request context.
:param switch_id: Identifier for the switch.
:param port_name: Name of the port on the switch.
:param description: Description to set for the port.
:param mode: Port mode ('access', 'trunk', or 'hybrid').
:param native_vlan: VLAN ID to be set on the port.
:param allowed_vlans: List of allowed VLAN IDs to be added(optional).
:param default_vlan: VLAN ID to removed from the port(optional).
:param lag_name: LAG name if port is part of a link aggregation group.
:param topic: RPC topic. Defaults to self.topic.
:raises: InvalidParameterValue if validation fails.
:raises: NetworkError if the network operation fails.
:returns: Dictionary containing the updated port configuration.
"""
cctxt = self._prepare_call(topic=topic, version="1.0")
return cctxt.call(
context,
"update_port",
switch_id=switch_id,
port_name=port_name,
description=description,
mode=mode,
native_vlan=native_vlan,
allowed_vlans=allowed_vlans,
lag_name=lag_name,
default_vlan=default_vlan,
)
[docs]
def reset_port(
self,
context,
switch_id,
port_name,
native_vlan,
allowed_vlans=None,
default_vlan=None,
topic=None,
):
"""Reset a network switch port to default configuration.
:param context: request context.
:param switch_id: Identifier for the switch.
:param port_name: Name of the port on the switch.
:param native_vlan: VLAN ID to be removed from the port.
:param allowed_vlans: List of allowed VLAN IDs to be removed(optional).
:param default_vlan: VLAN ID to restore onto the port(optional).
:param topic: RPC topic. Defaults to self.topic.
:raises: InvalidParameterValue if validation fails.
:raises: NetworkError if the network operation fails.
:returns: Dictionary containing the reset port configuration.
"""
cctxt = self._prepare_call(topic=topic, version="1.0")
return cctxt.call(
context,
"reset_port",
switch_id=switch_id,
port_name=port_name,
native_vlan=native_vlan,
allowed_vlans=allowed_vlans,
default_vlan=default_vlan,
)
[docs]
def get_switches(self, context, topic=None):
"""Get information about all configured switches.
:param context: request context.
:param topic: RPC topic. Defaults to self.topic.
:raises: NetworkError if the network operation fails.
:returns: Dictionary with switch_id as key and switch_info as value.
"""
cctxt = self._prepare_call(topic=topic, version="1.0")
return cctxt.call(context, "get_switches")
[docs]
def update_lag(
self,
context,
switch_ids,
lag_name,
description,
mode,
native_vlan,
aggregation_mode,
allowed_vlans=None,
default_vlan=None,
topic=None,
):
"""Update a link aggregation group (LAG) configuration.
:param context: request context.
:param switch_ids: List of switch identifiers.
:param lag_name: Name of the LAG.
:param description: Description for the LAG.
:param mode: LAG mode ('access' or 'trunk').
:param native_vlan: VLAN ID to be set for the LAG.
:param aggregation_mode: Aggregation mode (e.g., 'lacp', 'static').
:param allowed_vlans: List of allowed VLAN IDs to be added (optional).
:param default_vlan: VLAN ID to removed from the port(optional).
:param topic: RPC topic. Defaults to self.topic.
:raises: InvalidParameterValue if validation fails.
:raises: NetworkError if the network operation fails.
:returns: Dictionary containing the updated LAG configuration.
"""
cctxt = self._prepare_call(topic=topic, version="1.0")
return cctxt.call(
context,
"update_lag",
switch_ids=switch_ids,
lag_name=lag_name,
description=description,
mode=mode,
native_vlan=native_vlan,
aggregation_mode=aggregation_mode,
allowed_vlans=allowed_vlans,
default_vlan=default_vlan,
)
[docs]
def delete_lag(
self, context, switch_ids, lag_name, topic=None
):
"""Delete a link aggregation group (LAG) configuration.
:param context: request context.
:param switch_ids: List of switch identifiers.
:param lag_name: Name of the LAG to delete.
:param topic: RPC topic. Defaults to self.topic.
:raises: InvalidParameterValue if validation fails.
:raises: NetworkError if the network operation fails.
:returns: Dictionary containing the deletion status.
"""
cctxt = self._prepare_call(topic=topic, version="1.0")
return cctxt.call(
context,
"delete_lag",
switch_ids=switch_ids,
lag_name=lag_name,
)