--- /dev/null
+# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
+# Copyright (C) 2011 Isaku Yamahata <yamahata at valinux co jp>
+#
+# 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.
+
+import collections
+
+from ryu.base import app_manager
+import ryu.exception as ryu_exc
+from ryu.controller import event
+from ryu.exception import NetworkNotFound, NetworkAlreadyExist
+from ryu.exception import PortAlreadyExist, PortNotFound, PortUnknown
+
+
+NW_ID_UNKNOWN = '__NW_ID_UNKNOWN__'
+
+
+class MacAddressAlreadyExist(ryu_exc.RyuException):
+ message = 'port (%(dpid)s, %(port)s) has already mac %(mac_address)s'
+
+
+class EventNetworkDel(event.EventBase):
+ """
+ An event class for network deletion.
+
+ This event is generated when a network is deleted by the REST API.
+ An instance has at least the following attributes.
+
+ ========== ===================================================================
+ Attribute Description
+ ========== ===================================================================
+ network_id Network ID
+ ========== ===================================================================
+ """
+
+ def __init__(self, network_id):
+ super(EventNetworkDel, self).__init__()
+ self.network_id = network_id
+
+
+class EventNetworkPort(event.EventBase):
+ """
+ An event class for notification of port arrival and deperture.
+
+ This event is generated when a port is introduced to or removed from a
+ network by the REST API.
+ An instance has at least the following attributes.
+
+ ========== ================================================================
+ Attribute Description
+ ========== ================================================================
+ network_id Network ID
+ dpid OpenFlow Datapath ID of the switch to which the port belongs.
+ port_no OpenFlow port number of the port
+ add_del True for adding a port. False for removing a port.
+ ========== ================================================================
+ """
+
+ def __init__(self, network_id, dpid, port_no, add_del):
+ super(EventNetworkPort, self).__init__()
+ self.network_id = network_id
+ self.dpid = dpid
+ self.port_no = port_no
+ self.add_del = add_del
+
+
+class EventMacAddress(event.EventBase):
+ """
+ An event class for end-point MAC address registration.
+
+ This event is generated when a end-point MAC address is updated
+ by the REST API.
+ An instance has at least the following attributes.
+
+ =========== ===============================================================
+ Attribute Description
+ =========== ===============================================================
+ network_id Network ID
+ dpid OpenFlow Datapath ID of the switch to which the port belongs.
+ port_no OpenFlow port number of the port
+ mac_address The old MAC address of the port if add_del is False. Otherwise
+ the new MAC address.
+ add_del False if this event is a result of a port removal. Otherwise
+ True.
+ =========== ===============================================================
+ """
+
+ def __init__(self, dpid, port_no, network_id, mac_address, add_del):
+ super(EventMacAddress, self).__init__()
+ assert network_id is not None
+ assert mac_address is not None
+ self.dpid = dpid
+ self.port_no = port_no
+ self.network_id = network_id
+ self.mac_address = mac_address
+ self.add_del = add_del
+
+
+class Networks(dict):
+ "network_id -> set of (dpid, port_no)"
+
+ def __init__(self, f):
+ super(Networks, self).__init__()
+ self.send_event = f
+
+ def list_networks(self):
+ return list(self.keys())
+
+ def has_network(self, network_id):
+ return network_id in self
+
+ def update_network(self, network_id):
+ self.setdefault(network_id, set())
+
+ def create_network(self, network_id):
+ if network_id in self:
+ raise NetworkAlreadyExist(network_id=network_id)
+
+ self[network_id] = set()
+
+ def remove_network(self, network_id):
+ try:
+ ports = self[network_id]
+ except KeyError:
+ raise NetworkNotFound(network_id=network_id)
+
+ while ports:
+ (dpid, port_no) = ports.pop()
+ self._remove_event(network_id, dpid, port_no)
+ if self.pop(network_id, None) is not None:
+ self.send_event(EventNetworkDel(network_id))
+
+ def list_ports(self, network_id):
+ try:
+ # use list() to keep compatibility for output
+ # set() isn't json serializable
+ return list(self[network_id])
+ except KeyError:
+ raise NetworkNotFound(network_id=network_id)
+
+ def add_raw(self, network_id, dpid, port_no):
+ self[network_id].add((dpid, port_no))
+
+ def add_event(self, network_id, dpid, port_no):
+ self.send_event(
+ EventNetworkPort(network_id, dpid, port_no, True))
+
+ # def add(self, network_id, dpid, port_no):
+ # self.add_raw(network_id, dpid, port_no)
+ # self.add_event(network_id, dpid, port_no)
+
+ def _remove_event(self, network_id, dpid, port_no):
+ self.send_event(EventNetworkPort(network_id, dpid, port_no, False))
+
+ def remove_raw(self, network_id, dpid, port_no):
+ ports = self[network_id]
+ if (dpid, port_no) in ports:
+ ports.remove((dpid, port_no))
+ self._remove_event(network_id, dpid, port_no)
+
+ def remove(self, network_id, dpid, port_no):
+ try:
+ self.remove_raw(network_id, dpid, port_no)
+ except KeyError:
+ raise NetworkNotFound(network_id=network_id)
+ except ValueError:
+ raise PortNotFound(network_id=network_id, dpid=dpid, port=port_no)
+
+ def has_port(self, network_id, dpid, port):
+ return (dpid, port) in self[network_id]
+
+ def get_dpids(self, network_id):
+ try:
+ ports = self[network_id]
+ except KeyError:
+ return set()
+
+ # python 2.6 doesn't support set comprehension
+ # port = (dpid, port_no)
+ return set([port[0] for port in ports])
+
+
+class Port(object):
+ def __init__(self, port_no, network_id, mac_address=None):
+ super(Port, self).__init__()
+ self.port_no = port_no
+ self.network_id = network_id
+ self.mac_address = mac_address
+
+
+class DPIDs(dict):
+ """dpid -> port_no -> Port(port_no, network_id, mac_address)"""
+
+ def __init__(self, f, nw_id_unknown):
+ super(DPIDs, self).__init__()
+ self.send_event = f
+ self.nw_id_unknown = nw_id_unknown
+
+ def setdefault_dpid(self, dpid):
+ return self.setdefault(dpid, {})
+
+ def _setdefault_network(self, dpid, port_no, default_network_id):
+ dp = self.setdefault_dpid(dpid)
+ return dp.setdefault(port_no, Port(port_no=port_no,
+ network_id=default_network_id))
+
+ def setdefault_network(self, dpid, port_no):
+ self._setdefault_network(dpid, port_no, self.nw_id_unknown)
+
+ def update_port(self, dpid, port_no, network_id):
+ port = self._setdefault_network(dpid, port_no, network_id)
+ port.network_id = network_id
+
+ def remove_port(self, dpid, port_no):
+ try:
+ # self.dpids[dpid][port_no] can be already deleted by
+ # port_deleted()
+ port = self[dpid].pop(port_no, None)
+ if port and port.network_id and port.mac_address:
+ self.send_event(EventMacAddress(dpid, port_no,
+ port.network_id,
+ port.mac_address,
+ False))
+ except KeyError:
+ raise PortNotFound(dpid=dpid, port=port_no, network_id=None)
+
+ def get_ports(self, dpid, network_id=None, mac_address=None):
+ if network_id is None:
+ return list(self.get(dpid, {}).values())
+ if mac_address is None:
+ return [p for p in self.get(dpid, {}).values()
+ if p.network_id == network_id]
+
+ # live-migration: There can be two ports that have same mac address.
+ return [p for p in self.get(dpid, {}).values()
+ if p.network_id == network_id and p.mac_address == mac_address]
+
+ def get_port(self, dpid, port_no):
+ try:
+ return self[dpid][port_no]
+ except KeyError:
+ raise PortNotFound(dpid=dpid, port=port_no, network_id=None)
+
+ def get_network(self, dpid, port_no):
+ try:
+ return self[dpid][port_no].network_id
+ except KeyError:
+ raise PortUnknown(dpid=dpid, port=port_no)
+
+ def get_networks(self, dpid):
+ return set(self[dpid].values())
+
+ def get_network_safe(self, dpid, port_no):
+ port = self.get(dpid, {}).get(port_no)
+ if port is None:
+ return self.nw_id_unknown
+ return port.network_id
+
+ def get_mac(self, dpid, port_no):
+ port = self.get_port(dpid, port_no)
+ return port.mac_address
+
+ def _set_mac(self, network_id, dpid, port_no, port, mac_address):
+ if not (port.network_id is None or
+ port.network_id == network_id or
+ port.network_id == self.nw_id_unknown):
+ raise PortNotFound(network_id=network_id, dpid=dpid, port=port_no)
+
+ port.network_id = network_id
+ port.mac_address = mac_address
+ if port.network_id and port.mac_address:
+ self.send_event(EventMacAddress(
+ dpid, port_no, port.network_id, port.mac_address,
+ True))
+
+ def set_mac(self, network_id, dpid, port_no, mac_address):
+ port = self.get_port(dpid, port_no)
+ if port.mac_address is not None:
+ raise MacAddressAlreadyExist(dpid=dpid, port=port_no,
+ mac_address=mac_address)
+ self._set_mac(network_id, dpid, port_no, port, mac_address)
+
+ def update_mac(self, network_id, dpid, port_no, mac_address):
+ port = self.get_port(dpid, port_no)
+ if port.mac_address is None:
+ self._set_mac(network_id, dpid, port_no, port, mac_address)
+ return
+
+ # For now, we don't allow changing mac address.
+ if port.mac_address != mac_address:
+ raise MacAddressAlreadyExist(dpid=dpid, port=port_no,
+ mac_address=port.mac_address)
+
+
+MacPort = collections.namedtuple('MacPort', ('dpid', 'port_no'))
+
+
+class MacToPort(collections.defaultdict):
+ """mac_address -> set of MacPort(dpid, port_no)"""
+
+ def __init__(self):
+ super(MacToPort, self).__init__(set)
+
+ def add_port(self, dpid, port_no, mac_address):
+ self[mac_address].add(MacPort(dpid, port_no))
+
+ def remove_port(self, dpid, port_no, mac_address):
+ ports = self[mac_address]
+ ports.discard(MacPort(dpid, port_no))
+ if not ports:
+ del self[mac_address]
+
+ def get_ports(self, mac_address):
+ return self[mac_address]
+
+
+class MacAddresses(dict):
+ """network_id -> mac_address -> set of (dpid, port_no)"""
+
+ def add_port(self, network_id, dpid, port_no, mac_address):
+ mac2port = self.setdefault(network_id, MacToPort())
+ mac2port.add_port(dpid, port_no, mac_address)
+
+ def remove_port(self, network_id, dpid, port_no, mac_address):
+ mac2port = self.get(network_id)
+ if mac2port is None:
+ return
+ mac2port.remove_port(dpid, port_no, mac_address)
+ if not mac2port:
+ del self[network_id]
+
+ def get_ports(self, network_id, mac_address):
+ mac2port = self.get(network_id)
+ if not mac2port:
+ return set()
+ return mac2port.get_ports(mac_address)
+
+
+class Network(app_manager.RyuApp):
+ def __init__(self, nw_id_unknown=NW_ID_UNKNOWN):
+ super(Network, self).__init__()
+ self.name = 'network'
+ self.nw_id_unknown = nw_id_unknown
+ self.networks = Networks(self.send_event_to_observers)
+ self.dpids = DPIDs(self.send_event_to_observers, nw_id_unknown)
+ self.mac_addresses = MacAddresses()
+
+ def _check_nw_id_unknown(self, network_id):
+ if network_id == self.nw_id_unknown:
+ raise NetworkAlreadyExist(network_id=network_id)
+
+ def list_networks(self):
+ return self.networks.list_networks()
+
+ def update_network(self, network_id):
+ self._check_nw_id_unknown(network_id)
+ self.networks.update_network(network_id)
+
+ def create_network(self, network_id):
+ self._check_nw_id_unknown(network_id)
+ self.networks.create_network(network_id)
+
+ def remove_network(self, network_id):
+ self.networks.remove_network(network_id)
+
+ def list_ports(self, network_id):
+ return self.networks.list_ports(network_id)
+
+ def list_ports_noraise(self, network_id):
+ try:
+ return self.list_ports(network_id)
+ except NetworkNotFound:
+ return []
+
+ def _update_port(self, network_id, dpid, port, port_may_exist):
+ def _known_nw_id(nw_id):
+ return nw_id is not None and nw_id != self.nw_id_unknown
+
+ queue_add_event = False
+ self._check_nw_id_unknown(network_id)
+ try:
+ old_network_id = self.dpids.get_network_safe(dpid, port)
+ if (self.networks.has_port(network_id, dpid, port) or
+ _known_nw_id(old_network_id)):
+ if not port_may_exist:
+ raise PortAlreadyExist(network_id=network_id,
+ dpid=dpid, port=port)
+
+ if old_network_id != network_id:
+ queue_add_event = True
+ self.networks.add_raw(network_id, dpid, port)
+ if _known_nw_id(old_network_id):
+ self.networks.remove_raw(old_network_id, dpid, port)
+ except KeyError:
+ raise NetworkNotFound(network_id=network_id)
+
+ self.dpids.update_port(dpid, port, network_id)
+ if queue_add_event:
+ self.networks.add_event(network_id, dpid, port)
+
+ def create_port(self, network_id, dpid, port):
+ self._update_port(network_id, dpid, port, False)
+
+ def update_port(self, network_id, dpid, port):
+ self._update_port(network_id, dpid, port, True)
+
+ def _get_old_mac(self, network_id, dpid, port_no):
+ try:
+ port = self.dpids.get_port(dpid, port_no)
+ except PortNotFound:
+ pass
+ else:
+ if port.network_id == network_id:
+ return port.mac_address
+ return None
+
+ def remove_port(self, network_id, dpid, port_no):
+ # generate event first, then do the real task
+ old_mac_address = self._get_old_mac(network_id, dpid, port_no)
+
+ self.dpids.remove_port(dpid, port_no)
+ try:
+ self.networks.remove(network_id, dpid, port_no)
+ except NetworkNotFound:
+ # port deletion can be called after network deletion
+ # due to Openstack auto deletion port.(dhcp/router port)
+ pass
+ if old_mac_address is not None:
+ self.mac_addresses.remove_port(network_id, dpid, port_no,
+ old_mac_address)
+
+ #
+ # methods for gre tunnel
+ #
+
+ def get_dpids(self, network_id):
+ return self.networks.get_dpids(network_id)
+
+ def has_network(self, network_id):
+ return self.networks.has_network(network_id)
+
+ def get_networks(self, dpid):
+ return self.dpids.get_networks(dpid)
+
+ def create_mac(self, network_id, dpid, port_no, mac_address):
+ self.mac_addresses.add_port(network_id, dpid, port_no, mac_address)
+ self.dpids.set_mac(network_id, dpid, port_no, mac_address)
+
+ def update_mac(self, network_id, dpid, port_no, mac_address):
+ old_mac_address = self._get_old_mac(network_id, dpid, port_no)
+
+ self.dpids.update_mac(network_id, dpid, port_no, mac_address)
+ if old_mac_address is not None:
+ self.mac_addresses.remove_port(network_id, dpid, port_no,
+ old_mac_address)
+ self.mac_addresses.add_port(network_id, dpid, port_no, mac_address)
+
+ def get_mac(self, dpid, port_no):
+ return self.dpids.get_mac(dpid, port_no)
+
+ def list_mac(self, dpid, port_no):
+ mac_address = self.dpids.get_mac(dpid, port_no)
+ if mac_address is None:
+ return []
+ return [mac_address]
+
+ def get_ports(self, dpid, network_id=None, mac_address=None):
+ return self.dpids.get_ports(dpid, network_id, mac_address)
+
+ def get_port(self, dpid, port_no):
+ return self.dpids.get_port(dpid, port_no)
+
+ def get_ports_with_mac(self, network_id, mac_address):
+ return self.mac_addresses.get_ports(network_id, mac_address)
+
+ #
+ # methods for simple_isolation
+ #
+
+ def same_network(self, dpid, nw_id, out_port, allow_nw_id_external=None):
+ assert nw_id != self.nw_id_unknown
+ out_nw = self.dpids.get_network_safe(dpid, out_port)
+
+ if nw_id == out_nw:
+ return True
+
+ if (allow_nw_id_external is not None and
+ (allow_nw_id_external == nw_id or
+ allow_nw_id_external == out_nw)):
+ # allow external network -> known network id
+ return True
+
+ self.logger.debug('blocked dpid %s nw_id %s out_port %d out_nw %s'
+ 'external %s',
+ dpid, nw_id, out_port, out_nw, allow_nw_id_external)
+ return False
+
+ def get_network(self, dpid, port):
+ return self.dpids.get_network(dpid, port)
+
+ def add_datapath(self, ofp_switch_features):
+ datapath = ofp_switch_features.datapath
+ dpid = ofp_switch_features.datapath_id
+ ports = ofp_switch_features.ports
+ self.dpids.setdefault_dpid(dpid)
+ for port_no in ports:
+ self.port_added(datapath, port_no)
+
+ def port_added(self, datapath, port_no):
+ if port_no == 0 or port_no >= datapath.ofproto.OFPP_MAX:
+ # skip fake output ports
+ return
+
+ self.dpids.setdefault_network(datapath.id, port_no)
+
+ def port_deleted(self, dpid, port_no):
+ self.dpids.remove_port(dpid, port_no)
+
+ def filter_ports(self, dpid, in_port, nw_id, allow_nw_id_external=None):
+ assert nw_id != self.nw_id_unknown
+ ret = []
+
+ for port in self.get_ports(dpid):
+ nw_id_ = port.network_id
+ if port.port_no == in_port:
+ continue
+
+ if nw_id_ == nw_id:
+ ret.append(port.port_no)
+ elif (allow_nw_id_external is not None and
+ nw_id_ == allow_nw_id_external):
+ ret.append(port.port_no)
+
+ return ret