1 # Copyright (C) 2012 Nippon Telegraph and Telephone Corporation.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
20 from ryu.base import app_manager
21 from ryu.controller import ofp_event
22 from ryu.controller import dpset
23 from ryu.controller.handler import MAIN_DISPATCHER
24 from ryu.controller.handler import set_ev_cls
25 from ryu.exception import RyuException
26 from ryu.ofproto import ofproto_v1_0
27 from ryu.ofproto import ofproto_v1_2
28 from ryu.ofproto import ofproto_v1_3
29 from ryu.ofproto import ofproto_v1_4
30 from ryu.ofproto import ofproto_v1_5
31 from ryu.lib import ofctl_v1_0
32 from ryu.lib import ofctl_v1_2
33 from ryu.lib import ofctl_v1_3
34 from ryu.lib import ofctl_v1_4
35 from ryu.lib import ofctl_v1_5
36 from ryu.app.wsgi import ControllerBase
37 from ryu.app.wsgi import Response
38 from ryu.app.wsgi import WSGIApplication
40 LOG = logging.getLogger('ryu.app.ofctl_rest')
42 # supported ofctl versions in this restful app
44 ofproto_v1_0.OFP_VERSION: ofctl_v1_0,
45 ofproto_v1_2.OFP_VERSION: ofctl_v1_2,
46 ofproto_v1_3.OFP_VERSION: ofctl_v1_3,
47 ofproto_v1_4.OFP_VERSION: ofctl_v1_4,
48 ofproto_v1_5.OFP_VERSION: ofctl_v1_5,
54 # Retrieve the switch stats
56 # get the list of all switches
59 # get the desc stats of the switch
60 # GET /stats/desc/<dpid>
62 # get flows desc stats of the switch
63 # GET /stats/flowdesc/<dpid>
65 # get flows desc stats of the switch filtered by the fields
66 # POST /stats/flowdesc/<dpid>
68 # get flows stats of the switch
69 # GET /stats/flow/<dpid>
71 # get flows stats of the switch filtered by the fields
72 # POST /stats/flow/<dpid>
74 # get aggregate flows stats of the switch
75 # GET /stats/aggregateflow/<dpid>
77 # get aggregate flows stats of the switch filtered by the fields
78 # POST /stats/aggregateflow/<dpid>
80 # get table stats of the switch
81 # GET /stats/table/<dpid>
83 # get table features stats of the switch
84 # GET /stats/tablefeatures/<dpid>
86 # get ports stats of the switch
87 # GET /stats/port/<dpid>[/<port>]
88 # Note: Specification of port number is optional
90 # get queues stats of the switch
91 # GET /stats/queue/<dpid>[/<port>[/<queue_id>]]
92 # Note: Specification of port number and queue id are optional
93 # If you want to omitting the port number and setting the queue id,
94 # please specify the keyword "ALL" to the port number
95 # e.g. GET /stats/queue/1/ALL/1
97 # get queues config stats of the switch
98 # GET /stats/queueconfig/<dpid>[/<port>]
99 # Note: Specification of port number is optional
101 # get queues desc stats of the switch
102 # GET /stats/queuedesc/<dpid>[/<port>[/<queue_id>]]
103 # Note: Specification of port number and queue id are optional
104 # If you want to omitting the port number and setting the queue id,
105 # please specify the keyword "ALL" to the port number
106 # e.g. GET /stats/queuedesc/1/ALL/1
108 # get meter features stats of the switch
109 # GET /stats/meterfeatures/<dpid>
111 # get meter config stats of the switch
112 # GET /stats/meterconfig/<dpid>[/<meter_id>]
113 # Note: Specification of meter id is optional
115 # get meter desc stats of the switch
116 # GET /stats/meterdesc/<dpid>[/<meter_id>]
117 # Note: Specification of meter id is optional
119 # get meters stats of the switch
120 # GET /stats/meter/<dpid>[/<meter_id>]
121 # Note: Specification of meter id is optional
123 # get group features stats of the switch
124 # GET /stats/groupfeatures/<dpid>
126 # get groups desc stats of the switch
127 # GET /stats/groupdesc/<dpid>[/<group_id>]
128 # Note: Specification of group id is optional (OpenFlow 1.5 or later)
130 # get groups stats of the switch
131 # GET /stats/group/<dpid>[/<group_id>]
132 # Note: Specification of group id is optional
134 # get ports description of the switch
135 # GET /stats/portdesc/<dpid>[/<port_no>]
136 # Note: Specification of port number is optional (OpenFlow 1.5 or later)
138 # Update the switch stats
141 # POST /stats/flowentry/add
143 # modify all matching flow entries
144 # POST /stats/flowentry/modify
146 # modify flow entry strictly matching wildcards and priority
147 # POST /stats/flowentry/modify_strict
149 # delete all matching flow entries
150 # POST /stats/flowentry/delete
152 # delete flow entry strictly matching wildcards and priority
153 # POST /stats/flowentry/delete_strict
155 # delete all flow entries of the switch
156 # DELETE /stats/flowentry/clear/<dpid>
159 # POST /stats/meterentry/add
161 # modify a meter entry
162 # POST /stats/meterentry/modify
164 # delete a meter entry
165 # POST /stats/meterentry/delete
168 # POST /stats/groupentry/add
170 # modify a group entry
171 # POST /stats/groupentry/modify
173 # delete a group entry
174 # POST /stats/groupentry/delete
176 # modify behavior of the physical port
177 # POST /stats/portdesc/modify
179 # modify role of controller
183 # send a experimeter message
184 # POST /stats/experimenter/<dpid>
187 class CommandNotFoundError(RyuException):
188 message = 'No such command : %(cmd)s'
191 class PortNotFoundError(RyuException):
192 message = 'No such port info: %(port_no)s'
195 def stats_method(method):
196 def wrapper(self, req, dpid, *args, **kwargs):
197 # Get datapath instance from DPSet
199 dp = self.dpset.get(int(str(dpid), 0))
201 LOG.exception('Invalid dpid: %s', dpid)
202 return Response(status=400)
204 LOG.error('No such Datapath: %s', dpid)
205 return Response(status=404)
207 # Get lib/ofctl_* module
209 ofctl = supported_ofctl.get(dp.ofproto.OFP_VERSION)
211 LOG.exception('Unsupported OF version: %s',
212 dp.ofproto.OFP_VERSION)
213 return Response(status=501)
215 # Invoke StatsController method
217 ret = method(self, req, dp, ofctl, *args, **kwargs)
218 return Response(content_type='application/json',
219 body=json.dumps(ret))
221 LOG.exception('Invalid syntax: %s', req.body)
222 return Response(status=400)
223 except AttributeError:
224 LOG.exception('Unsupported OF request in this version: %s',
225 dp.ofproto.OFP_VERSION)
226 return Response(status=501)
231 def command_method(method):
232 def wrapper(self, req, *args, **kwargs):
233 # Parse request json body
236 # We use ast.literal_eval() to parse request json body
237 # instead of json.loads().
238 # Because we need to parse binary format body
239 # in send_experimenter().
240 body = ast.literal_eval(req.body.decode('utf-8'))
244 LOG.exception('Invalid syntax: %s', req.body)
245 return Response(status=400)
247 # Get datapath_id from request parameters
248 dpid = body.get('dpid', None)
251 dpid = kwargs.pop('dpid')
253 LOG.exception('Cannot get dpid from request parameters')
254 return Response(status=400)
256 # Get datapath instance from DPSet
258 dp = self.dpset.get(int(str(dpid), 0))
260 LOG.exception('Invalid dpid: %s', dpid)
261 return Response(status=400)
263 LOG.error('No such Datapath: %s', dpid)
264 return Response(status=404)
266 # Get lib/ofctl_* module
268 ofctl = supported_ofctl.get(dp.ofproto.OFP_VERSION)
270 LOG.exception('Unsupported OF version: version=%s',
271 dp.ofproto.OFP_VERSION)
272 return Response(status=501)
274 # Invoke StatsController method
276 method(self, req, dp, ofctl, body, *args, **kwargs)
277 return Response(status=200)
279 LOG.exception('Invalid syntax: %s', req.body)
280 return Response(status=400)
281 except AttributeError:
282 LOG.exception('Unsupported OF request in this version: %s',
283 dp.ofproto.OFP_VERSION)
284 return Response(status=501)
285 except CommandNotFoundError as e:
286 LOG.exception(e.message)
287 return Response(status=404)
288 except PortNotFoundError as e:
289 LOG.exception(e.message)
290 return Response(status=404)
295 class StatsController(ControllerBase):
296 def __init__(self, req, link, data, **config):
297 super(StatsController, self).__init__(req, link, data, **config)
298 self.dpset = data['dpset']
299 self.waiters = data['waiters']
301 def get_dpids(self, req, **_kwargs):
302 dps = list(self.dpset.dps.keys())
303 body = json.dumps(dps)
304 return Response(content_type='application/json', body=body)
307 def get_desc_stats(self, req, dp, ofctl, **kwargs):
308 return ofctl.get_desc_stats(dp, self.waiters)
311 def get_flow_desc(self, req, dp, ofctl, **kwargs):
312 flow = req.json if req.body else {}
313 return ofctl.get_flow_desc(dp, self.waiters, flow)
316 def get_flow_stats(self, req, dp, ofctl, **kwargs):
317 flow = req.json if req.body else {}
318 return ofctl.get_flow_stats(dp, self.waiters, flow)
321 def get_aggregate_flow_stats(self, req, dp, ofctl, **kwargs):
322 flow = req.json if req.body else {}
323 return ofctl.get_aggregate_flow_stats(dp, self.waiters, flow)
326 def get_table_stats(self, req, dp, ofctl, **kwargs):
327 return ofctl.get_table_stats(dp, self.waiters)
330 def get_table_features(self, req, dp, ofctl, **kwargs):
331 return ofctl.get_table_features(dp, self.waiters)
334 def get_port_stats(self, req, dp, ofctl, port=None, **kwargs):
338 return ofctl.get_port_stats(dp, self.waiters, port)
341 def get_queue_stats(self, req, dp, ofctl,
342 port=None, queue_id=None, **kwargs):
346 if queue_id == "ALL":
349 return ofctl.get_queue_stats(dp, self.waiters, port, queue_id)
352 def get_queue_config(self, req, dp, ofctl, port=None, **kwargs):
356 return ofctl.get_queue_config(dp, self.waiters, port)
359 def get_queue_desc(self, req, dp, ofctl,
360 port=None, queue=None, **_kwargs):
367 return ofctl.get_queue_desc(dp, self.waiters, port, queue)
370 def get_meter_features(self, req, dp, ofctl, **kwargs):
371 return ofctl.get_meter_features(dp, self.waiters)
374 def get_meter_config(self, req, dp, ofctl, meter_id=None, **kwargs):
375 if meter_id == "ALL":
378 return ofctl.get_meter_config(dp, self.waiters, meter_id)
381 def get_meter_desc(self, req, dp, ofctl, meter_id=None, **kwargs):
382 if meter_id == "ALL":
385 return ofctl.get_meter_desc(dp, self.waiters, meter_id)
388 def get_meter_stats(self, req, dp, ofctl, meter_id=None, **kwargs):
389 if meter_id == "ALL":
392 return ofctl.get_meter_stats(dp, self.waiters, meter_id)
395 def get_group_features(self, req, dp, ofctl, **kwargs):
396 return ofctl.get_group_features(dp, self.waiters)
399 def get_group_desc(self, req, dp, ofctl, group_id=None, **kwargs):
400 if dp.ofproto.OFP_VERSION < ofproto_v1_5.OFP_VERSION:
401 return ofctl.get_group_desc(dp, self.waiters)
403 return ofctl.get_group_desc(dp, self.waiters, group_id)
406 def get_group_stats(self, req, dp, ofctl, group_id=None, **kwargs):
407 if group_id == "ALL":
410 return ofctl.get_group_stats(dp, self.waiters, group_id)
413 def get_port_desc(self, req, dp, ofctl, port_no=None, **kwargs):
414 if dp.ofproto.OFP_VERSION < ofproto_v1_5.OFP_VERSION:
415 return ofctl.get_port_desc(dp, self.waiters)
417 return ofctl.get_port_desc(dp, self.waiters, port_no)
420 def get_role(self, req, dp, ofctl, **kwargs):
421 return ofctl.get_role(dp, self.waiters)
424 def mod_flow_entry(self, req, dp, ofctl, flow, cmd, **kwargs):
426 'add': dp.ofproto.OFPFC_ADD,
427 'modify': dp.ofproto.OFPFC_MODIFY,
428 'modify_strict': dp.ofproto.OFPFC_MODIFY_STRICT,
429 'delete': dp.ofproto.OFPFC_DELETE,
430 'delete_strict': dp.ofproto.OFPFC_DELETE_STRICT,
432 mod_cmd = cmd_convert.get(cmd, None)
434 raise CommandNotFoundError(cmd=cmd)
436 ofctl.mod_flow_entry(dp, flow, mod_cmd)
439 def delete_flow_entry(self, req, dp, ofctl, flow, **kwargs):
440 if ofproto_v1_0.OFP_VERSION == dp.ofproto.OFP_VERSION:
443 flow = {'table_id': dp.ofproto.OFPTT_ALL}
445 ofctl.mod_flow_entry(dp, flow, dp.ofproto.OFPFC_DELETE)
448 def mod_meter_entry(self, req, dp, ofctl, meter, cmd, **kwargs):
450 'add': dp.ofproto.OFPMC_ADD,
451 'modify': dp.ofproto.OFPMC_MODIFY,
452 'delete': dp.ofproto.OFPMC_DELETE,
454 mod_cmd = cmd_convert.get(cmd, None)
456 raise CommandNotFoundError(cmd=cmd)
458 ofctl.mod_meter_entry(dp, meter, mod_cmd)
461 def mod_group_entry(self, req, dp, ofctl, group, cmd, **kwargs):
463 'add': dp.ofproto.OFPGC_ADD,
464 'modify': dp.ofproto.OFPGC_MODIFY,
465 'delete': dp.ofproto.OFPGC_DELETE,
467 mod_cmd = cmd_convert.get(cmd, None)
469 raise CommandNotFoundError(cmd=cmd)
471 ofctl.mod_group_entry(dp, group, mod_cmd)
474 def mod_port_behavior(self, req, dp, ofctl, port_config, cmd, **kwargs):
475 port_no = port_config.get('port_no', None)
476 port_no = int(str(port_no), 0)
478 port_info = self.dpset.port_state[int(dp.id)].get(port_no)
480 port_config.setdefault('hw_addr', port_info.hw_addr)
481 if dp.ofproto.OFP_VERSION < ofproto_v1_4.OFP_VERSION:
482 port_config.setdefault('advertise', port_info.advertised)
484 port_config.setdefault('properties', port_info.properties)
486 raise PortNotFoundError(port_no=port_no)
489 raise CommandNotFoundError(cmd=cmd)
491 ofctl.mod_port_behavior(dp, port_config)
494 def send_experimenter(self, req, dp, ofctl, exp, **kwargs):
495 ofctl.send_experimenter(dp, exp)
498 def set_role(self, req, dp, ofctl, role, **kwargs):
499 ofctl.set_role(dp, role)
502 class RestStatsApi(app_manager.RyuApp):
503 OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION,
504 ofproto_v1_2.OFP_VERSION,
505 ofproto_v1_3.OFP_VERSION,
506 ofproto_v1_4.OFP_VERSION,
507 ofproto_v1_5.OFP_VERSION]
509 'dpset': dpset.DPSet,
510 'wsgi': WSGIApplication
513 def __init__(self, *args, **kwargs):
514 super(RestStatsApi, self).__init__(*args, **kwargs)
515 self.dpset = kwargs['dpset']
516 wsgi = kwargs['wsgi']
519 self.data['dpset'] = self.dpset
520 self.data['waiters'] = self.waiters
523 wsgi.registory['StatsController'] = self.data
525 uri = path + '/switches'
526 mapper.connect('stats', uri,
527 controller=StatsController, action='get_dpids',
528 conditions=dict(method=['GET']))
530 uri = path + '/desc/{dpid}'
531 mapper.connect('stats', uri,
532 controller=StatsController, action='get_desc_stats',
533 conditions=dict(method=['GET']))
535 uri = path + '/flowdesc/{dpid}'
536 mapper.connect('stats', uri,
537 controller=StatsController, action='get_flow_stats',
538 conditions=dict(method=['GET', 'POST']))
540 uri = path + '/flow/{dpid}'
541 mapper.connect('stats', uri,
542 controller=StatsController, action='get_flow_stats',
543 conditions=dict(method=['GET', 'POST']))
545 uri = path + '/aggregateflow/{dpid}'
546 mapper.connect('stats', uri,
547 controller=StatsController,
548 action='get_aggregate_flow_stats',
549 conditions=dict(method=['GET', 'POST']))
551 uri = path + '/table/{dpid}'
552 mapper.connect('stats', uri,
553 controller=StatsController, action='get_table_stats',
554 conditions=dict(method=['GET']))
556 uri = path + '/tablefeatures/{dpid}'
557 mapper.connect('stats', uri,
558 controller=StatsController, action='get_table_features',
559 conditions=dict(method=['GET']))
561 uri = path + '/port/{dpid}'
562 mapper.connect('stats', uri,
563 controller=StatsController, action='get_port_stats',
564 conditions=dict(method=['GET']))
566 uri = path + '/port/{dpid}/{port}'
567 mapper.connect('stats', uri,
568 controller=StatsController, action='get_port_stats',
569 conditions=dict(method=['GET']))
571 uri = path + '/queue/{dpid}'
572 mapper.connect('stats', uri,
573 controller=StatsController, action='get_queue_stats',
574 conditions=dict(method=['GET']))
576 uri = path + '/queue/{dpid}/{port}'
577 mapper.connect('stats', uri,
578 controller=StatsController, action='get_queue_stats',
579 conditions=dict(method=['GET']))
581 uri = path + '/queue/{dpid}/{port}/{queue_id}'
582 mapper.connect('stats', uri,
583 controller=StatsController, action='get_queue_stats',
584 conditions=dict(method=['GET']))
586 uri = path + '/queueconfig/{dpid}'
587 mapper.connect('stats', uri,
588 controller=StatsController, action='get_queue_config',
589 conditions=dict(method=['GET']))
591 uri = path + '/queueconfig/{dpid}/{port}'
592 mapper.connect('stats', uri,
593 controller=StatsController, action='get_queue_config',
594 conditions=dict(method=['GET']))
596 uri = path + '/queuedesc/{dpid}'
597 mapper.connect('stats', uri,
598 controller=StatsController, action='get_queue_desc',
599 conditions=dict(method=['GET']))
601 uri = path + '/queuedesc/{dpid}/{port}'
602 mapper.connect('stats', uri,
603 controller=StatsController, action='get_queue_desc',
604 conditions=dict(method=['GET']))
606 uri = path + '/queuedesc/{dpid}/{port}/{queue}'
607 mapper.connect('stats', uri,
608 controller=StatsController, action='get_queue_desc',
609 conditions=dict(method=['GET']))
611 uri = path + '/meterfeatures/{dpid}'
612 mapper.connect('stats', uri,
613 controller=StatsController, action='get_meter_features',
614 conditions=dict(method=['GET']))
616 uri = path + '/meterconfig/{dpid}'
617 mapper.connect('stats', uri,
618 controller=StatsController, action='get_meter_config',
619 conditions=dict(method=['GET']))
621 uri = path + '/meterconfig/{dpid}/{meter_id}'
622 mapper.connect('stats', uri,
623 controller=StatsController, action='get_meter_config',
624 conditions=dict(method=['GET']))
626 uri = path + '/meterdesc/{dpid}'
627 mapper.connect('stats', uri,
628 controller=StatsController, action='get_meter_desc',
629 conditions=dict(method=['GET']))
631 uri = path + '/meterdesc/{dpid}/{meter_id}'
632 mapper.connect('stats', uri,
633 controller=StatsController, action='get_meter_desc',
634 conditions=dict(method=['GET']))
636 uri = path + '/meter/{dpid}'
637 mapper.connect('stats', uri,
638 controller=StatsController, action='get_meter_stats',
639 conditions=dict(method=['GET']))
641 uri = path + '/meter/{dpid}/{meter_id}'
642 mapper.connect('stats', uri,
643 controller=StatsController, action='get_meter_stats',
644 conditions=dict(method=['GET']))
646 uri = path + '/groupfeatures/{dpid}'
647 mapper.connect('stats', uri,
648 controller=StatsController, action='get_group_features',
649 conditions=dict(method=['GET']))
651 uri = path + '/groupdesc/{dpid}'
652 mapper.connect('stats', uri,
653 controller=StatsController, action='get_group_desc',
654 conditions=dict(method=['GET']))
656 uri = path + '/groupdesc/{dpid}/{group_id}'
657 mapper.connect('stats', uri,
658 controller=StatsController, action='get_group_desc',
659 conditions=dict(method=['GET']))
661 uri = path + '/group/{dpid}'
662 mapper.connect('stats', uri,
663 controller=StatsController, action='get_group_stats',
664 conditions=dict(method=['GET']))
666 uri = path + '/group/{dpid}/{group_id}'
667 mapper.connect('stats', uri,
668 controller=StatsController, action='get_group_stats',
669 conditions=dict(method=['GET']))
671 uri = path + '/portdesc/{dpid}'
672 mapper.connect('stats', uri,
673 controller=StatsController, action='get_port_desc',
674 conditions=dict(method=['GET']))
676 uri = path + '/portdesc/{dpid}/{port_no}'
677 mapper.connect('stats', uri,
678 controller=StatsController, action='get_port_desc',
679 conditions=dict(method=['GET']))
681 uri = path + '/role/{dpid}'
682 mapper.connect('stats', uri,
683 controller=StatsController, action='get_role',
684 conditions=dict(method=['GET']))
686 uri = path + '/flowentry/{cmd}'
687 mapper.connect('stats', uri,
688 controller=StatsController, action='mod_flow_entry',
689 conditions=dict(method=['POST']))
691 uri = path + '/flowentry/clear/{dpid}'
692 mapper.connect('stats', uri,
693 controller=StatsController, action='delete_flow_entry',
694 conditions=dict(method=['DELETE']))
696 uri = path + '/meterentry/{cmd}'
697 mapper.connect('stats', uri,
698 controller=StatsController, action='mod_meter_entry',
699 conditions=dict(method=['POST']))
701 uri = path + '/groupentry/{cmd}'
702 mapper.connect('stats', uri,
703 controller=StatsController, action='mod_group_entry',
704 conditions=dict(method=['POST']))
706 uri = path + '/portdesc/{cmd}'
707 mapper.connect('stats', uri,
708 controller=StatsController, action='mod_port_behavior',
709 conditions=dict(method=['POST']))
711 uri = path + '/experimenter/{dpid}'
712 mapper.connect('stats', uri,
713 controller=StatsController, action='send_experimenter',
714 conditions=dict(method=['POST']))
717 mapper.connect('stats', uri,
718 controller=StatsController, action='set_role',
719 conditions=dict(method=['POST']))
721 @set_ev_cls([ofp_event.EventOFPStatsReply,
722 ofp_event.EventOFPDescStatsReply,
723 ofp_event.EventOFPFlowStatsReply,
724 ofp_event.EventOFPAggregateStatsReply,
725 ofp_event.EventOFPTableStatsReply,
726 ofp_event.EventOFPTableFeaturesStatsReply,
727 ofp_event.EventOFPPortStatsReply,
728 ofp_event.EventOFPQueueStatsReply,
729 ofp_event.EventOFPQueueDescStatsReply,
730 ofp_event.EventOFPMeterStatsReply,
731 ofp_event.EventOFPMeterFeaturesStatsReply,
732 ofp_event.EventOFPMeterConfigStatsReply,
733 ofp_event.EventOFPGroupStatsReply,
734 ofp_event.EventOFPGroupFeaturesStatsReply,
735 ofp_event.EventOFPGroupDescStatsReply,
736 ofp_event.EventOFPPortDescStatsReply
738 def stats_reply_handler(self, ev):
742 if dp.id not in self.waiters:
744 if msg.xid not in self.waiters[dp.id]:
746 lock, msgs = self.waiters[dp.id][msg.xid]
750 if dp.ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION:
751 flags = dp.ofproto.OFPSF_REPLY_MORE
752 elif dp.ofproto.OFP_VERSION == ofproto_v1_2.OFP_VERSION:
753 flags = dp.ofproto.OFPSF_REPLY_MORE
754 elif dp.ofproto.OFP_VERSION >= ofproto_v1_3.OFP_VERSION:
755 flags = dp.ofproto.OFPMPF_REPLY_MORE
757 if msg.flags & flags:
759 del self.waiters[dp.id][msg.xid]
762 @set_ev_cls([ofp_event.EventOFPSwitchFeatures,
763 ofp_event.EventOFPQueueGetConfigReply,
764 ofp_event.EventOFPRoleReply,
766 def features_reply_handler(self, ev):
770 if dp.id not in self.waiters:
772 if msg.xid not in self.waiters[dp.id]:
774 lock, msgs = self.waiters[dp.id][msg.xid]
777 del self.waiters[dp.id][msg.xid]