1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
|
# Copyright (C) 2012, 2013 Nippon Telegraph and Telephone Corporation.
# Copyright (C) 2012 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.
"""
Manage switches.
Planned to be replaced by os_ken/topology.
"""
import logging
import warnings
from os_ken.base import app_manager
from os_ken.controller import event
from os_ken.controller import handler
from os_ken.controller import ofp_event
from os_ken.controller.handler import set_ev_cls
import os_ken.exception as os_ken_exc
from os_ken.lib.dpid import dpid_to_str
LOG = logging.getLogger('os_ken.controller.dpset')
DPSET_EV_DISPATCHER = "dpset"
class EventDPBase(event.EventBase):
def __init__(self, dp):
super(EventDPBase, self).__init__()
self.dp = dp
class EventDP(EventDPBase):
"""
An event class to notify connect/disconnect of a switch.
For OpenFlow switches, one can get the same notification by observing
os_ken.controller.ofp_event.EventOFPStateChange.
An instance has at least the following attributes.
========= =================================================================
Attribute Description
========= =================================================================
dp A os_ken.controller.controller.Datapath instance of the switch
enter True when the switch connected to our controller. False for
disconnect.
ports A list of port instances.
========= =================================================================
"""
def __init__(self, dp, enter_leave):
# enter_leave
# True: dp entered
# False: dp leaving
super(EventDP, self).__init__(dp)
self.enter = enter_leave
self.ports = [] # port list when enter or leave
class EventDPReconnected(EventDPBase):
def __init__(self, dp):
super(EventDPReconnected, self).__init__(dp)
# port list, which should not change across reconnects
self.ports = []
class EventPortBase(EventDPBase):
def __init__(self, dp, port):
super(EventPortBase, self).__init__(dp)
self.port = port
class EventPortAdd(EventPortBase):
"""
An event class for switch port status "ADD" notification.
This event is generated when a new port is added to a switch.
For OpenFlow switches, one can get the same notification by observing
os_ken.controller.ofp_event.EventOFPPortStatus.
An instance has at least the following attributes.
========= =================================================================
Attribute Description
========= =================================================================
dp A os_ken.controller.controller.Datapath instance of the switch
port port number
========= =================================================================
"""
def __init__(self, dp, port):
super(EventPortAdd, self).__init__(dp, port)
class EventPortDelete(EventPortBase):
"""
An event class for switch port status "DELETE" notification.
This event is generated when a port is removed from a switch.
For OpenFlow switches, one can get the same notification by observing
os_ken.controller.ofp_event.EventOFPPortStatus.
An instance has at least the following attributes.
========= =================================================================
Attribute Description
========= =================================================================
dp A os_ken.controller.controller.Datapath instance of the switch
port port number
========= =================================================================
"""
def __init__(self, dp, port):
super(EventPortDelete, self).__init__(dp, port)
class EventPortModify(EventPortBase):
"""
An event class for switch port status "MODIFY" notification.
This event is generated when some attribute of a port is changed.
For OpenFlow switches, one can get the same notification by observing
os_ken.controller.ofp_event.EventOFPPortStatus.
An instance has at least the following attributes.
========= ====================================================================
Attribute Description
========= ====================================================================
dp A os_ken.controller.controller.Datapath instance of the switch
port port number
========= ====================================================================
"""
def __init__(self, dp, new_port):
super(EventPortModify, self).__init__(dp, new_port)
class PortState(dict):
def __init__(self):
super(PortState, self).__init__()
def add(self, port_no, port):
self[port_no] = port
def remove(self, port_no):
del self[port_no]
def modify(self, port_no, port):
self[port_no] = port
# this depends on controller::Datapath and dispatchers in handler
class DPSet(app_manager.OSKenApp):
"""
DPSet application manages a set of switches (datapaths)
connected to this controller.
Usage Example::
# ...(snip)...
from os_ken.controller import dpset
class MyApp(app_manager.OSKenApp):
_CONTEXTS = {
'dpset': dpset.DPSet,
}
def __init__(self, *args, **kwargs):
super(MyApp, self).__init__(*args, **kwargs)
# Stores DPSet instance to call its API in this app
self.dpset = kwargs['dpset']
def _my_handler(self):
# Get the datapath object which has the given dpid
dpid = 1
dp = self.dpset.get(dpid)
if dp is None:
self.logger.info('No such datapath: dpid=%d', dpid)
"""
def __init__(self, *args, **kwargs):
super(DPSet, self).__init__(*args, **kwargs)
self.name = 'dpset'
self.dps = {} # datapath_id => class Datapath
self.port_state = {} # datapath_id => ports
def _register(self, dp):
LOG.debug('DPSET: register datapath %s', dp)
assert dp.id is not None
# while dpid should be unique, we need to handle duplicates here
# because it's entirely possible for a switch to reconnect us
# before we notice the drop of the previous connection.
# in that case,
# - forget the older connection as it likely will disappear soon
# - do not send EventDP leave/enter events
# - keep the PortState for the dpid
send_dp_reconnected = False
if dp.id in self.dps:
self.logger.warning('DPSET: Multiple connections from %s',
dpid_to_str(dp.id))
self.logger.debug('DPSET: Forgetting datapath %s', self.dps[dp.id])
(self.dps[dp.id]).close()
self.logger.debug('DPSET: New datapath %s', dp)
send_dp_reconnected = True
self.dps[dp.id] = dp
if dp.id not in self.port_state:
self.port_state[dp.id] = PortState()
ev = EventDP(dp, True)
with warnings.catch_warnings():
warnings.simplefilter('ignore')
for port in dp.ports.values():
self._port_added(dp, port)
ev.ports.append(port)
self.send_event_to_observers(ev)
if send_dp_reconnected:
ev = EventDPReconnected(dp)
ev.ports = self.port_state.get(dp.id, {}).values()
self.send_event_to_observers(ev)
def _unregister(self, dp):
# see the comment in _register().
if dp not in self.dps.values():
return
LOG.debug('DPSET: unregister datapath %s', dp)
assert self.dps[dp.id] == dp
# Now datapath is already dead, so port status change event doesn't
# interfere us.
ev = EventDP(dp, False)
for port in list(self.port_state.get(dp.id, {}).values()):
self._port_deleted(dp, port)
ev.ports.append(port)
self.send_event_to_observers(ev)
del self.dps[dp.id]
del self.port_state[dp.id]
def get(self, dp_id):
"""
This method returns the os_ken.controller.controller.Datapath
instance for the given Datapath ID.
"""
return self.dps.get(dp_id)
def get_all(self):
"""
This method returns a list of tuples which represents
instances for switches connected to this controller.
The tuple consists of a Datapath ID and an instance of
os_ken.controller.controller.Datapath.
A return value looks like the following::
[ (dpid_A, Datapath_A), (dpid_B, Datapath_B), ... ]
"""
return list(self.dps.items())
def _port_added(self, datapath, port):
self.port_state[datapath.id].add(port.port_no, port)
def _port_deleted(self, datapath, port):
self.port_state[datapath.id].remove(port.port_no)
@set_ev_cls(ofp_event.EventOFPStateChange,
[handler.MAIN_DISPATCHER, handler.DEAD_DISPATCHER])
def dispatcher_change(self, ev):
datapath = ev.datapath
assert datapath is not None
if ev.state == handler.MAIN_DISPATCHER:
self._register(datapath)
elif ev.state == handler.DEAD_DISPATCHER:
self._unregister(datapath)
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, handler.CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
# ofp_handler.py does the following so we could remove...
if datapath.ofproto.OFP_VERSION < 0x04:
datapath.ports = msg.ports
@set_ev_cls(ofp_event.EventOFPPortStatus, handler.MAIN_DISPATCHER)
def port_status_handler(self, ev):
msg = ev.msg
reason = msg.reason
datapath = msg.datapath
port = msg.desc
ofproto = datapath.ofproto
if reason == ofproto.OFPPR_ADD:
LOG.debug('DPSET: A port was added.' +
'(datapath id = %s, port number = %s)',
dpid_to_str(datapath.id), port.port_no)
self._port_added(datapath, port)
self.send_event_to_observers(EventPortAdd(datapath, port))
elif reason == ofproto.OFPPR_DELETE:
LOG.debug('DPSET: A port was deleted.' +
'(datapath id = %s, port number = %s)',
dpid_to_str(datapath.id), port.port_no)
self._port_deleted(datapath, port)
self.send_event_to_observers(EventPortDelete(datapath, port))
else:
assert reason == ofproto.OFPPR_MODIFY
LOG.debug('DPSET: A port was modified.' +
'(datapath id = %s, port number = %s)',
dpid_to_str(datapath.id), port.port_no)
self.port_state[datapath.id].modify(port.port_no, port)
self.send_event_to_observers(EventPortModify(datapath, port))
def get_port(self, dpid, port_no):
"""
This method returns the os_ken.controller.dpset.PortState
instance for the given Datapath ID and the port number.
Raises os_ken_exc.PortNotFound if no such a datapath connected to
this controller or no such a port exists.
"""
try:
return self.port_state[dpid][port_no]
except KeyError:
raise os_ken_exc.PortNotFound(dpid=dpid, port=port_no,
network_id=None)
def get_ports(self, dpid):
"""
This method returns a list of os_ken.controller.dpset.PortState
instances for the given Datapath ID.
Raises KeyError if no such a datapath connected to this controller.
"""
return list(self.port_state[dpid].values())
handler.register_service('os_ken.controller.dpset')
|