1 # Copyright (C) 2014 Kiyonari Harigae <lakshmi at cloudysunny14 org>
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.
21 from ryu.app import conf_switch_key as cs_key
22 from ryu.app.wsgi import ControllerBase
23 from ryu.app.wsgi import Response
24 from ryu.app.wsgi import route
25 from ryu.app.wsgi import WSGIApplication
26 from ryu.base import app_manager
27 from ryu.controller import conf_switch
28 from ryu.controller import ofp_event
29 from ryu.controller import dpset
30 from ryu.controller.handler import set_ev_cls
31 from ryu.controller.handler import MAIN_DISPATCHER
32 from ryu.exception import OFPUnknownVersion
33 from ryu.lib import dpid as dpid_lib
34 from ryu.lib import mac
35 from ryu.lib import ofctl_v1_0
36 from ryu.lib import ofctl_v1_2
37 from ryu.lib import ofctl_v1_3
38 from ryu.lib.ovs import bridge
39 from ryu.ofproto import ofproto_v1_0
40 from ryu.ofproto import ofproto_v1_2
41 from ryu.ofproto import ofproto_v1_3
42 from ryu.ofproto import ofproto_v1_3_parser
43 from ryu.ofproto import ether
44 from ryu.ofproto import inet
47 # =============================
49 # =============================
51 # Note: specify switch and vlan group, as follows.
52 # {switch-id} : 'all' or switchID
53 # {vlan-id} : 'all' or vlanID
58 # GET /qos/queue/status/{switch-id}
61 # get a queue configurations
62 # GET /qos/queue/{switch-id}
64 # set a queue to the switches
65 # POST /qos/queue/{switch-id}
67 # request body format:
68 # {"port_name":"<name of port>",
69 # "type": "<linux-htb or linux-other>",
70 # "max-rate": "<int>",
71 # "queues":[{"max_rate": "<int>", "min_rate": "<int>"},...]}
73 # Note: This operation override
74 # previous configurations.
75 # Note: Queue configurations are available for
77 # Note: port_name is optional argument.
78 # If does not pass the port_name argument,
79 # all ports are target for configuration.
82 # DELETE /qos/queue/{swtich-id}
84 # Note: This operation delete relation of qos record from
85 # qos colum in Port table. Therefore,
86 # QoS records and Queue records will remain.
92 # GET /qos/rules/{switch-id}
94 # * for specific vlan group
95 # GET /qos/rules/{switch-id}/{vlan-id}
99 # QoS rules will do the processing pipeline,
100 # which entries are register the first table (by default table id 0)
101 # and process will apply and go to next table.
104 # POST /qos/{switch-id}
106 # * for specific vlan group
107 # POST /qos/{switch-id}/{vlan-id}
109 # request body format:
110 # {"priority": "<value>",
111 # "match": {"<field1>": "<value1>", "<field2>": "<value2>",...},
112 # "actions": {"<action1>": "<value1>", "<action2>": "<value2>",...}
120 # Note: When "priority" has not been set up,
121 # "priority: 1" is set to "priority".
125 # "in_port" : "<int>"
126 # "dl_src" : "<xx:xx:xx:xx:xx:xx>"
127 # "dl_dst" : "<xx:xx:xx:xx:xx:xx>"
128 # "dl_type" : "<ARP or IPv4 or IPv6>"
129 # "nw_src" : "<A.B.C.D/M>"
130 # "nw_dst" : "<A.B.C.D/M>"
131 # "ipv6_src": "<xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/M>"
132 # "ipv6_dst": "<xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/M>"
133 # "nw_proto": "<TCP or UDP or ICMP or ICMPv6>"
136 # "ip_dscp" : "<int>"
140 # "mark": <dscp-value>
141 # sets the IPv4 ToS/DSCP field to tos.
142 # "meter": <meter-id>
144 # "queue": <queue-id>
145 # register queue specified by queue-id
147 # Note: When "actions" has not been set up,
148 # "queue: 0" is set to "actions".
152 # DELETE /qos/rule/{switch-id}
154 # * for specific vlan group
155 # DELETE /qos/{switch-id}/{vlan-id}
157 # request body format:
158 # {"<field>":"<value>"}
161 # "qos_id" : "<int>" or "all"
163 # about meter entries
166 # POST /qos/meter/{switch-id}
168 # request body format:
169 # {"meter_id": <int>,
170 # "bands":[{"action": "<DROP or DSCP_REMARK>",
171 # "flag": "<KBPS or PKTPS or BURST or STATS"
172 # "burst_size": <int>,
174 # "prec_level": <int>},...]}
176 # delete a meter entry
177 # DELETE /qos/meter/{switch-id}
179 # request body format:
180 # {"<field>":"<value>"}
183 # "meter_id" : "<int>"
187 SWITCHID_PATTERN = dpid_lib.DPID_PATTERN + r'|all'
188 VLANID_PATTERN = r'[0-9]{1,4}|all'
193 REST_SWITCHID = 'switch_id'
194 REST_COMMAND_RESULT = 'command_result'
195 REST_PRIORITY = 'priority'
196 REST_VLANID = 'vlan_id'
197 REST_PORT_NAME = 'port_name'
198 REST_QUEUE_TYPE = 'type'
199 REST_QUEUE_MAX_RATE = 'max_rate'
200 REST_QUEUE_MIN_RATE = 'min_rate'
201 REST_QUEUES = 'queues'
203 REST_QOS_ID = 'qos_id'
204 REST_COOKIE = 'cookie'
207 REST_IN_PORT = 'in_port'
208 REST_SRC_MAC = 'dl_src'
209 REST_DST_MAC = 'dl_dst'
210 REST_DL_TYPE = 'dl_type'
211 REST_DL_TYPE_ARP = 'ARP'
212 REST_DL_TYPE_IPV4 = 'IPv4'
213 REST_DL_TYPE_IPV6 = 'IPv6'
214 REST_DL_VLAN = 'dl_vlan'
215 REST_SRC_IP = 'nw_src'
216 REST_DST_IP = 'nw_dst'
217 REST_SRC_IPV6 = 'ipv6_src'
218 REST_DST_IPV6 = 'ipv6_dst'
219 REST_NW_PROTO = 'nw_proto'
220 REST_NW_PROTO_TCP = 'TCP'
221 REST_NW_PROTO_UDP = 'UDP'
222 REST_NW_PROTO_ICMP = 'ICMP'
223 REST_NW_PROTO_ICMPV6 = 'ICMPv6'
224 REST_TP_SRC = 'tp_src'
225 REST_TP_DST = 'tp_dst'
226 REST_DSCP = 'ip_dscp'
228 REST_ACTION = 'actions'
229 REST_ACTION_QUEUE = 'queue'
230 REST_ACTION_MARK = 'mark'
231 REST_ACTION_METER = 'meter'
233 REST_METER_ID = 'meter_id'
234 REST_METER_BURST_SIZE = 'burst_size'
235 REST_METER_RATE = 'rate'
236 REST_METER_PREC_LEVEL = 'prec_level'
237 REST_METER_BANDS = 'bands'
238 REST_METER_ACTION_DROP = 'drop'
239 REST_METER_ACTION_REMARK = 'remark'
241 DEFAULT_FLOW_PRIORITY = 0
242 QOS_PRIORITY_MAX = ofproto_v1_3_parser.UINT16_MAX - 1
248 COOKIE_SHIFT_VLANID = 32
251 REQUIREMENTS = {'switchid': SWITCHID_PATTERN,
252 'vlanid': VLANID_PATTERN}
254 LOG = logging.getLogger(__name__)
257 class RestQoSAPI(app_manager.RyuApp):
259 OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION,
260 ofproto_v1_2.OFP_VERSION,
261 ofproto_v1_3.OFP_VERSION]
264 'dpset': dpset.DPSet,
265 'conf_switch': conf_switch.ConfSwitchSet,
266 'wsgi': WSGIApplication}
268 def __init__(self, *args, **kwargs):
269 super(RestQoSAPI, self).__init__(*args, **kwargs)
272 QoSController.set_logger(self.logger)
273 self.cs = kwargs['conf_switch']
274 self.dpset = kwargs['dpset']
275 wsgi = kwargs['wsgi']
278 self.data['dpset'] = self.dpset
279 self.data['waiters'] = self.waiters
280 wsgi.registory['QoSController'] = self.data
281 wsgi.register(QoSController, self.data)
283 def stats_reply_handler(self, ev):
287 if dp.id not in self.waiters:
289 if msg.xid not in self.waiters[dp.id]:
291 lock, msgs = self.waiters[dp.id][msg.xid]
295 if dp.ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION or \
296 dp.ofproto.OFP_VERSION == ofproto_v1_2.OFP_VERSION:
297 flags = dp.ofproto.OFPSF_REPLY_MORE
298 elif dp.ofproto.OFP_VERSION == ofproto_v1_3.OFP_VERSION:
299 flags = dp.ofproto.OFPMPF_REPLY_MORE
301 if msg.flags & flags:
303 del self.waiters[dp.id][msg.xid]
306 @set_ev_cls(conf_switch.EventConfSwitchSet)
307 def conf_switch_set_handler(self, ev):
308 if ev.key == cs_key.OVSDB_ADDR:
309 QoSController.set_ovsdb_addr(ev.dpid, ev.value)
311 QoSController._LOGGER.debug("unknown event: %s", ev)
313 @set_ev_cls(conf_switch.EventConfSwitchDel)
314 def conf_switch_del_handler(self, ev):
315 if ev.key == cs_key.OVSDB_ADDR:
316 QoSController.delete_ovsdb_addr(ev.dpid)
318 QoSController._LOGGER.debug("unknown event: %s", ev)
320 @set_ev_cls(dpset.EventDP, dpset.DPSET_EV_DISPATCHER)
321 def handler_datapath(self, ev):
323 QoSController.regist_ofs(ev.dp, self.CONF)
325 QoSController.unregist_ofs(ev.dp)
327 # for OpenFlow version1.0
328 @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER)
329 def stats_reply_handler_v1_0(self, ev):
330 self.stats_reply_handler(ev)
332 # for OpenFlow version1.2 or later
333 @set_ev_cls(ofp_event.EventOFPStatsReply, MAIN_DISPATCHER)
334 def stats_reply_handler_v1_2(self, ev):
335 self.stats_reply_handler(ev)
337 # for OpenFlow version1.2 or later
338 @set_ev_cls(ofp_event.EventOFPQueueStatsReply, MAIN_DISPATCHER)
339 def queue_stats_reply_handler_v1_2(self, ev):
340 self.stats_reply_handler(ev)
342 # for OpenFlow version1.2 or later
343 @set_ev_cls(ofp_event.EventOFPMeterStatsReply, MAIN_DISPATCHER)
344 def meter_stats_reply_handler_v1_2(self, ev):
345 self.stats_reply_handler(ev)
348 class QoSOfsList(dict):
351 super(QoSOfsList, self).__init__()
353 def get_ofs(self, dp_id):
355 raise ValueError('qos sw is not connected.')
358 if dp_id == REST_ALL:
362 dpid = dpid_lib.str_to_dpid(dp_id)
364 raise ValueError('Invalid switchID.')
367 dps = {dpid: self[dpid]}
369 msg = 'qos sw is not connected. : switchID=%s' % dp_id
370 raise ValueError(msg)
375 class QoSController(ControllerBase):
377 _OFS_LIST = QoSOfsList()
380 def __init__(self, req, link, data, **config):
381 super(QoSController, self).__init__(req, link, data, **config)
382 self.dpset = data['dpset']
383 self.waiters = data['waiters']
386 def set_logger(cls, logger):
388 cls._LOGGER.propagate = False
389 hdlr = logging.StreamHandler()
390 fmt_str = '[QoS][%(levelname)s] %(message)s'
391 hdlr.setFormatter(logging.Formatter(fmt_str))
392 cls._LOGGER.addHandler(hdlr)
395 def regist_ofs(dp, CONF):
396 if dp.id in QoSController._OFS_LIST:
399 dpid_str = dpid_lib.dpid_to_str(dp.id)
401 f_ofs = QoS(dp, CONF)
402 f_ofs.set_default_flow()
403 except OFPUnknownVersion as message:
404 QoSController._LOGGER.info('dpid=%s: %s',
408 QoSController._OFS_LIST.setdefault(dp.id, f_ofs)
409 QoSController._LOGGER.info('dpid=%s: Join qos switch.',
413 def unregist_ofs(dp):
414 if dp.id in QoSController._OFS_LIST:
415 del QoSController._OFS_LIST[dp.id]
416 QoSController._LOGGER.info('dpid=%s: Leave qos switch.',
417 dpid_lib.dpid_to_str(dp.id))
420 def set_ovsdb_addr(dpid, value):
421 ofs = QoSController._OFS_LIST.get(dpid, None)
423 ofs.set_ovsdb_addr(dpid, value)
426 def delete_ovsdb_addr(dpid):
427 ofs = QoSController._OFS_LIST.get(dpid, None)
429 ofs.set_ovsdb_addr(dpid, None)
431 @route('qos_switch', BASE_URL + '/queue/{switchid}',
432 methods=['GET'], requirements=REQUIREMENTS)
433 def get_queue(self, req, switchid, **_kwargs):
434 return self._access_switch(req, switchid, VLANID_NONE,
437 @route('qos_switch', BASE_URL + '/queue/{switchid}',
438 methods=['POST'], requirements=REQUIREMENTS)
439 def set_queue(self, req, switchid, **_kwargs):
440 return self._access_switch(req, switchid, VLANID_NONE,
443 @route('qos_switch', BASE_URL + '/queue/{switchid}',
444 methods=['DELETE'], requirements=REQUIREMENTS)
445 def delete_queue(self, req, switchid, **_kwargs):
446 return self._access_switch(req, switchid, VLANID_NONE,
447 'delete_queue', None)
449 @route('qos_switch', BASE_URL + '/queue/status/{switchid}',
450 methods=['GET'], requirements=REQUIREMENTS)
451 def get_status(self, req, switchid, **_kwargs):
452 return self._access_switch(req, switchid, VLANID_NONE,
453 'get_status', self.waiters)
455 @route('qos_switch', BASE_URL + '/rules/{switchid}',
456 methods=['GET'], requirements=REQUIREMENTS)
457 def get_qos(self, req, switchid, **_kwargs):
458 return self._access_switch(req, switchid, VLANID_NONE,
459 'get_qos', self.waiters)
461 @route('qos_switch', BASE_URL + '/rules/{switchid}/{vlanid}',
462 methods=['GET'], requirements=REQUIREMENTS)
463 def get_vlan_qos(self, req, switchid, vlanid, **_kwargs):
464 return self._access_switch(req, switchid, vlanid,
465 'get_qos', self.waiters)
467 @route('qos_switch', BASE_URL + '/rules/{switchid}',
468 methods=['POST'], requirements=REQUIREMENTS)
469 def set_qos(self, req, switchid, **_kwargs):
470 return self._access_switch(req, switchid, VLANID_NONE,
471 'set_qos', self.waiters)
473 @route('qos_switch', BASE_URL + '/rules/{switchid}/{vlanid}',
474 methods=['POST'], requirements=REQUIREMENTS)
475 def set_vlan_qos(self, req, switchid, vlanid, **_kwargs):
476 return self._access_switch(req, switchid, vlanid,
477 'set_qos', self.waiters)
479 @route('qos_switch', BASE_URL + '/rules/{switchid}',
480 methods=['DELETE'], requirements=REQUIREMENTS)
481 def delete_qos(self, req, switchid, **_kwargs):
482 return self._access_switch(req, switchid, VLANID_NONE,
483 'delete_qos', self.waiters)
485 @route('qos_switch', BASE_URL + '/rules/{switchid}/{vlanid}',
486 methods=['DELETE'], requirements=REQUIREMENTS)
487 def delete_vlan_qos(self, req, switchid, vlanid, **_kwargs):
488 return self._access_switch(req, switchid, vlanid,
489 'delete_qos', self.waiters)
491 @route('qos_switch', BASE_URL + '/meter/{switchid}',
492 methods=['GET'], requirements=REQUIREMENTS)
493 def get_meter(self, req, switchid, **_kwargs):
494 return self._access_switch(req, switchid, VLANID_NONE,
495 'get_meter', self.waiters)
497 @route('qos_switch', BASE_URL + '/meter/{switchid}',
498 methods=['POST'], requirements=REQUIREMENTS)
499 def set_meter(self, req, switchid, **_kwargs):
500 return self._access_switch(req, switchid, VLANID_NONE,
501 'set_meter', self.waiters)
503 @route('qos_switch', BASE_URL + '/meter/{switchid}',
504 methods=['DELETE'], requirements=REQUIREMENTS)
505 def delete_meter(self, req, switchid, **_kwargs):
506 return self._access_switch(req, switchid, VLANID_NONE,
507 'delete_meter', self.waiters)
509 def _access_switch(self, req, switchid, vlan_id, func, waiters):
511 rest = req.json if req.body else {}
513 QoSController._LOGGER.debug('invalid syntax %s', req.body)
514 return Response(status=400)
517 dps = self._OFS_LIST.get_ofs(switchid)
518 vid = QoSController._conv_toint_vlanid(vlan_id)
519 except ValueError as message:
520 return Response(status=400, body=str(message))
523 for f_ofs in dps.values():
524 function = getattr(f_ofs, func)
526 if waiters is not None:
527 msg = function(rest, vid, waiters)
529 msg = function(rest, vid)
530 except ValueError as message:
531 return Response(status=400, body=str(message))
534 body = json.dumps(msgs)
535 return Response(content_type='application/json', body=body)
538 def _conv_toint_vlanid(vlan_id):
539 if vlan_id != REST_ALL:
540 vlan_id = int(vlan_id)
541 if (vlan_id != VLANID_NONE and
542 (vlan_id < VLANID_MIN or VLANID_MAX < vlan_id)):
543 msg = 'Invalid {vlan_id} value. Set [%d-%d]' % (VLANID_MIN,
545 raise ValueError(msg)
551 _OFCTL = {ofproto_v1_0.OFP_VERSION: ofctl_v1_0,
552 ofproto_v1_2.OFP_VERSION: ofctl_v1_2,
553 ofproto_v1_3.OFP_VERSION: ofctl_v1_3}
555 def __init__(self, dp, CONF):
556 super(QoS, self).__init__()
558 self.vlan_list[VLANID_NONE] = 0 # for VLAN=None
560 self.version = dp.ofproto.OFP_VERSION
561 # Dictionary of port name to Queue config.
563 # self.queue_list = {
567 # "max-rate": "600000"
572 # "min-rate": "900000"
579 self.ovsdb_addr = None
580 self.ovs_bridge = None
582 if self.version not in self._OFCTL:
583 raise OFPUnknownVersion(version=self.version)
585 self.ofctl = self._OFCTL[self.version]
587 def set_default_flow(self):
588 if self.version == ofproto_v1_0.OFP_VERSION:
592 priority = DEFAULT_FLOW_PRIORITY
593 actions = [{'type': 'GOTO_TABLE',
594 'table_id': QOS_TABLE_ID + 1}]
595 flow = self._to_of_flow(cookie=cookie,
600 cmd = self.dp.ofproto.OFPFC_ADD
601 self.ofctl.mod_flow_entry(self.dp, flow, cmd)
603 def set_ovsdb_addr(self, dpid, ovsdb_addr):
604 old_address = self.ovsdb_addr
605 if old_address == ovsdb_addr:
607 elif ovsdb_addr is None:
608 # Determine deleting OVSDB address was requested.
610 self.ovs_bridge = None
613 ovs_bridge = bridge.OVSBridge(self.CONF, dpid, ovsdb_addr)
617 raise ValueError('ovsdb addr is not available.')
618 self.ovsdb_addr = ovsdb_addr
619 self.ovs_bridge = ovs_bridge
621 def _update_vlan_list(self, vlan_list):
622 for vlan_id in self.vlan_list.keys():
623 if vlan_id is not VLANID_NONE and vlan_id not in vlan_list:
624 del self.vlan_list[vlan_id]
626 def _get_cookie(self, vlan_id):
627 if vlan_id == REST_ALL:
628 vlan_ids = self.vlan_list.keys()
633 for vlan_id in vlan_ids:
634 self.vlan_list.setdefault(vlan_id, 0)
635 self.vlan_list[vlan_id] += 1
636 self.vlan_list[vlan_id] &= ofproto_v1_3_parser.UINT32_MAX
637 cookie = (vlan_id << COOKIE_SHIFT_VLANID) + \
638 self.vlan_list[vlan_id]
639 cookie_list.append([cookie, vlan_id])
644 def _cookie_to_qosid(cookie):
645 return cookie & ofproto_v1_3_parser.UINT32_MAX
647 # REST command template
648 def rest_command(func):
649 def _rest_command(*args, **kwargs):
650 key, value = func(*args, **kwargs)
651 switch_id = dpid_lib.dpid_to_str(args[0].dp.id)
652 return {REST_SWITCHID: switch_id,
657 def get_status(self, req, vlan_id, waiters):
658 if self.version == ofproto_v1_0.OFP_VERSION:
659 raise ValueError('get_status operation is not supported')
661 msgs = self.ofctl.get_queue_stats(self.dp, waiters)
662 return REST_COMMAND_RESULT, msgs
665 def get_queue(self, rest, vlan_id):
666 if len(self.queue_list):
667 msg = {'result': 'success',
668 'details': self.queue_list}
670 msg = {'result': 'failure',
671 'details': 'Queue is not exists.'}
673 return REST_COMMAND_RESULT, msg
676 def set_queue(self, rest, vlan_id):
677 if self.ovs_bridge is None:
678 msg = {'result': 'failure',
679 'details': 'ovs_bridge is not exists'}
680 return REST_COMMAND_RESULT, msg
682 port_name = rest.get(REST_PORT_NAME, None)
683 vif_ports = self.ovs_bridge.get_port_name_list()
685 if port_name is not None:
686 if port_name not in vif_ports:
687 raise ValueError('%s port is not exists' % port_name)
688 vif_ports = [port_name]
691 queue_type = rest.get(REST_QUEUE_TYPE, 'linux-htb')
692 parent_max_rate = rest.get(REST_QUEUE_MAX_RATE, None)
693 queues = rest.get(REST_QUEUES, [])
697 max_rate = queue.get(REST_QUEUE_MAX_RATE, None)
698 min_rate = queue.get(REST_QUEUE_MIN_RATE, None)
699 if max_rate is None and min_rate is None:
700 raise ValueError('Required to specify max_rate or min_rate')
702 if max_rate is not None:
703 config['max-rate'] = max_rate
704 if min_rate is not None:
705 config['min-rate'] = min_rate
707 queue_config.append(config)
708 queue_list[queue_id] = {'config': config}
711 for port_name in vif_ports:
713 self.ovs_bridge.set_qos(port_name, type=queue_type,
714 max_rate=parent_max_rate,
716 except Exception as msg:
717 raise ValueError(msg)
718 self.queue_list[port_name] = queue_list
720 msg = {'result': 'success',
721 'details': queue_list}
723 return REST_COMMAND_RESULT, msg
725 def _delete_queue(self):
726 if self.ovs_bridge is None:
729 vif_ports = self.ovs_bridge.get_external_ports()
730 for port in vif_ports:
731 self.ovs_bridge.del_qos(port.port_name)
735 def delete_queue(self, rest, vlan_id):
736 if self._delete_queue():
738 self.queue_list.clear()
742 return REST_COMMAND_RESULT, msg
745 def set_qos(self, rest, vlan_id, waiters):
747 cookie_list = self._get_cookie(vlan_id)
748 for cookie, vid in cookie_list:
749 msg = self._set_qos(cookie, rest, waiters, vid)
751 return REST_COMMAND_RESULT, msgs
753 def _set_qos(self, cookie, rest, waiters, vlan_id):
754 match_value = rest[REST_MATCH]
756 match_value[REST_DL_VLAN] = vlan_id
758 priority = int(rest.get(REST_PRIORITY, QOS_PRIORITY_MIN))
759 if (QOS_PRIORITY_MAX < priority):
760 raise ValueError('Invalid priority value. Set [%d-%d]'
761 % (QOS_PRIORITY_MIN, QOS_PRIORITY_MAX))
763 match = Match.to_openflow(match_value)
766 action = rest.get(REST_ACTION, None)
767 if action is not None:
768 if REST_ACTION_MARK in action:
769 actions.append({'type': 'SET_FIELD',
771 'value': int(action[REST_ACTION_MARK])})
772 if REST_ACTION_METER in action:
773 actions.append({'type': 'METER',
774 'meter_id': action[REST_ACTION_METER]})
775 if REST_ACTION_QUEUE in action:
776 actions.append({'type': 'SET_QUEUE',
777 'queue_id': action[REST_ACTION_QUEUE]})
779 actions.append({'type': 'SET_QUEUE',
782 actions.append({'type': 'GOTO_TABLE',
783 'table_id': QOS_TABLE_ID + 1})
784 flow = self._to_of_flow(cookie=cookie, priority=priority,
785 match=match, actions=actions)
787 cmd = self.dp.ofproto.OFPFC_ADD
789 self.ofctl.mod_flow_entry(self.dp, flow, cmd)
791 raise ValueError('Invalid rule parameter.')
793 qos_id = QoS._cookie_to_qosid(cookie)
794 msg = {'result': 'success',
795 'details': 'QoS added. : qos_id=%d' % qos_id}
797 if vlan_id != VLANID_NONE:
798 msg.setdefault(REST_VLANID, vlan_id)
802 def get_qos(self, rest, vlan_id, waiters):
804 msgs = self.ofctl.get_flow_stats(self.dp, waiters)
805 if str(self.dp.id) in msgs:
806 flow_stats = msgs[str(self.dp.id)]
807 for flow_stat in flow_stats:
808 if flow_stat['table_id'] != QOS_TABLE_ID:
810 priority = flow_stat[REST_PRIORITY]
811 if priority != DEFAULT_FLOW_PRIORITY:
812 vid = flow_stat[REST_MATCH].get(REST_DL_VLAN, VLANID_NONE)
813 if vlan_id == REST_ALL or vlan_id == vid:
814 rule = self._to_rest_rule(flow_stat)
815 rules.setdefault(vid, [])
816 rules[vid].append(rule)
819 for vid, rule in rules.items():
820 if vid == VLANID_NONE:
821 vid_data = {REST_QOS: rule}
823 vid_data = {REST_VLANID: vid, REST_QOS: rule}
824 get_data.append(vid_data)
826 return REST_COMMAND_RESULT, get_data
829 def delete_qos(self, rest, vlan_id, waiters):
831 if rest[REST_QOS_ID] == REST_ALL:
834 qos_id = int(rest[REST_QOS_ID])
836 raise ValueError('Invalid qos id.')
841 msgs = self.ofctl.get_flow_stats(self.dp, waiters)
842 if str(self.dp.id) in msgs:
843 flow_stats = msgs[str(self.dp.id)]
844 for flow_stat in flow_stats:
845 cookie = flow_stat[REST_COOKIE]
846 ruleid = QoS._cookie_to_qosid(cookie)
847 priority = flow_stat[REST_PRIORITY]
848 dl_vlan = flow_stat[REST_MATCH].get(REST_DL_VLAN, VLANID_NONE)
850 if priority != DEFAULT_FLOW_PRIORITY:
851 if ((qos_id == REST_ALL or qos_id == ruleid) and
852 (vlan_id == dl_vlan or vlan_id == REST_ALL)):
853 match = Match.to_mod_openflow(flow_stat[REST_MATCH])
854 delete_list.append([cookie, priority, match])
856 if dl_vlan not in vlan_list:
857 vlan_list.append(dl_vlan)
859 self._update_vlan_list(vlan_list)
861 if len(delete_list) == 0:
862 msg_details = 'QoS rule is not exist.'
863 if qos_id != REST_ALL:
864 msg_details += ' : QoS ID=%d' % qos_id
865 msg = {'result': 'failure',
866 'details': msg_details}
868 cmd = self.dp.ofproto.OFPFC_DELETE_STRICT
871 for cookie, priority, match in delete_list:
872 flow = self._to_of_flow(cookie=cookie, priority=priority,
873 match=match, actions=actions)
874 self.ofctl.mod_flow_entry(self.dp, flow, cmd)
876 vid = match.get(REST_DL_VLAN, VLANID_NONE)
877 rule_id = QoS._cookie_to_qosid(cookie)
878 delete_ids.setdefault(vid, '')
879 delete_ids[vid] += (('%d' if delete_ids[vid] == ''
880 else ',%d') % rule_id)
883 for vid, rule_ids in delete_ids.items():
884 del_msg = {'result': 'success',
885 'details': ' deleted. : QoS ID=%s' % rule_ids}
886 if vid != VLANID_NONE:
887 del_msg.setdefault(REST_VLANID, vid)
890 return REST_COMMAND_RESULT, msg
893 def set_meter(self, rest, vlan_id, waiters):
894 if self.version == ofproto_v1_0.OFP_VERSION:
895 raise ValueError('set_meter operation is not supported')
898 msg = self._set_meter(rest, waiters)
900 return REST_COMMAND_RESULT, msgs
902 def _set_meter(self, rest, waiters):
903 cmd = self.dp.ofproto.OFPMC_ADD
905 self.ofctl.mod_meter_entry(self.dp, rest, cmd)
907 raise ValueError('Invalid meter parameter.')
909 msg = {'result': 'success',
910 'details': 'Meter added. : Meter ID=%s' %
915 def get_meter(self, rest, vlan_id, waiters):
916 if (self.version == ofproto_v1_0.OFP_VERSION or
917 self.version == ofproto_v1_2.OFP_VERSION):
918 raise ValueError('get_meter operation is not supported')
920 msgs = self.ofctl.get_meter_stats(self.dp, waiters)
921 return REST_COMMAND_RESULT, msgs
924 def delete_meter(self, rest, vlan_id, waiters):
925 if (self.version == ofproto_v1_0.OFP_VERSION or
926 self.version == ofproto_v1_2.OFP_VERSION):
927 raise ValueError('delete_meter operation is not supported')
929 cmd = self.dp.ofproto.OFPMC_DELETE
931 self.ofctl.mod_meter_entry(self.dp, rest, cmd)
933 raise ValueError('Invalid meter parameter.')
935 msg = {'result': 'success',
936 'details': 'Meter deleted. : Meter ID=%s' %
938 return REST_COMMAND_RESULT, msg
940 def _to_of_flow(self, cookie, priority, match, actions):
941 flow = {'cookie': cookie,
942 'priority': priority,
950 def _to_rest_rule(self, flow):
951 ruleid = QoS._cookie_to_qosid(flow[REST_COOKIE])
952 rule = {REST_QOS_ID: ruleid}
953 rule.update({REST_PRIORITY: flow[REST_PRIORITY]})
954 rule.update(Match.to_rest(flow))
955 rule.update(Action.to_rest(flow))
961 _CONVERT = {REST_DL_TYPE:
962 {REST_DL_TYPE_ARP: ether.ETH_TYPE_ARP,
963 REST_DL_TYPE_IPV4: ether.ETH_TYPE_IP,
964 REST_DL_TYPE_IPV6: ether.ETH_TYPE_IPV6},
966 {REST_NW_PROTO_TCP: inet.IPPROTO_TCP,
967 REST_NW_PROTO_UDP: inet.IPPROTO_UDP,
968 REST_NW_PROTO_ICMP: inet.IPPROTO_ICMP,
969 REST_NW_PROTO_ICMPV6: inet.IPPROTO_ICMPV6}}
972 def to_openflow(rest):
974 def __inv_combi(msg):
975 raise ValueError('Invalid combination: [%s]' % msg)
977 def __inv_2and1(*args):
978 __inv_combi('%s=%s and %s' % (args[0], args[1], args[2]))
980 def __inv_2and2(*args):
981 __inv_combi('%s=%s and %s=%s' % (
982 args[0], args[1], args[2], args[3]))
984 def __inv_1and1(*args):
985 __inv_combi('%s and %s' % (args[0], args[1]))
987 def __inv_1and2(*args):
988 __inv_combi('%s and %s=%s' % (args[0], args[1], args[2]))
993 dl_type = rest.get(REST_DL_TYPE)
994 nw_proto = rest.get(REST_NW_PROTO)
995 if dl_type is not None:
996 if dl_type == REST_DL_TYPE_ARP:
997 if REST_SRC_IPV6 in rest:
999 REST_DL_TYPE, REST_DL_TYPE_ARP, REST_SRC_IPV6)
1000 if REST_DST_IPV6 in rest:
1002 REST_DL_TYPE, REST_DL_TYPE_ARP, REST_DST_IPV6)
1003 if REST_DSCP in rest:
1005 REST_DL_TYPE, REST_DL_TYPE_ARP, REST_DSCP)
1008 REST_DL_TYPE, REST_DL_TYPE_ARP, REST_NW_PROTO)
1009 elif dl_type == REST_DL_TYPE_IPV4:
1010 if REST_SRC_IPV6 in rest:
1012 REST_DL_TYPE, REST_DL_TYPE_IPV4, REST_SRC_IPV6)
1013 if REST_DST_IPV6 in rest:
1015 REST_DL_TYPE, REST_DL_TYPE_IPV4, REST_DST_IPV6)
1016 if nw_proto == REST_NW_PROTO_ICMPV6:
1018 REST_DL_TYPE, REST_DL_TYPE_IPV4,
1019 REST_NW_PROTO, REST_NW_PROTO_ICMPV6)
1020 elif dl_type == REST_DL_TYPE_IPV6:
1021 if REST_SRC_IP in rest:
1023 REST_DL_TYPE, REST_DL_TYPE_IPV6, REST_SRC_IP)
1024 if REST_DST_IP in rest:
1026 REST_DL_TYPE, REST_DL_TYPE_IPV6, REST_DST_IP)
1027 if nw_proto == REST_NW_PROTO_ICMP:
1029 REST_DL_TYPE, REST_DL_TYPE_IPV6,
1030 REST_NW_PROTO, REST_NW_PROTO_ICMP)
1032 raise ValueError('Unknown dl_type : %s' % dl_type)
1034 if REST_SRC_IP in rest:
1035 if REST_SRC_IPV6 in rest:
1036 __inv_1and1(REST_SRC_IP, REST_SRC_IPV6)
1037 if REST_DST_IPV6 in rest:
1038 __inv_1and1(REST_SRC_IP, REST_DST_IPV6)
1039 if nw_proto == REST_NW_PROTO_ICMPV6:
1041 REST_SRC_IP, REST_NW_PROTO, REST_NW_PROTO_ICMPV6)
1042 rest[REST_DL_TYPE] = REST_DL_TYPE_IPV4
1043 elif REST_DST_IP in rest:
1044 if REST_SRC_IPV6 in rest:
1045 __inv_1and1(REST_DST_IP, REST_SRC_IPV6)
1046 if REST_DST_IPV6 in rest:
1047 __inv_1and1(REST_DST_IP, REST_DST_IPV6)
1048 if nw_proto == REST_NW_PROTO_ICMPV6:
1050 REST_DST_IP, REST_NW_PROTO, REST_NW_PROTO_ICMPV6)
1051 rest[REST_DL_TYPE] = REST_DL_TYPE_IPV4
1052 elif REST_SRC_IPV6 in rest:
1053 if nw_proto == REST_NW_PROTO_ICMP:
1055 REST_SRC_IPV6, REST_NW_PROTO, REST_NW_PROTO_ICMP)
1056 rest[REST_DL_TYPE] = REST_DL_TYPE_IPV6
1057 elif REST_DST_IPV6 in rest:
1058 if nw_proto == REST_NW_PROTO_ICMP:
1060 REST_DST_IPV6, REST_NW_PROTO, REST_NW_PROTO_ICMP)
1061 rest[REST_DL_TYPE] = REST_DL_TYPE_IPV6
1062 elif REST_DSCP in rest:
1063 # Apply dl_type ipv4, if doesn't specify dl_type
1064 rest[REST_DL_TYPE] = REST_DL_TYPE_IPV4
1066 if nw_proto == REST_NW_PROTO_ICMP:
1067 rest[REST_DL_TYPE] = REST_DL_TYPE_IPV4
1068 elif nw_proto == REST_NW_PROTO_ICMPV6:
1069 rest[REST_DL_TYPE] = REST_DL_TYPE_IPV6
1070 elif nw_proto == REST_NW_PROTO_TCP or \
1071 nw_proto == REST_NW_PROTO_UDP:
1072 raise ValueError('no dl_type was specified')
1074 raise ValueError('Unknown nw_proto: %s' % nw_proto)
1076 for key, value in rest.items():
1077 if key in Match._CONVERT:
1078 if value in Match._CONVERT[key]:
1079 match.setdefault(key, Match._CONVERT[key][value])
1081 raise ValueError('Invalid rule parameter. : key=%s' % key)
1083 match.setdefault(key, value)
1088 def to_rest(openflow):
1089 of_match = openflow[REST_MATCH]
1091 mac_dontcare = mac.haddr_to_str(mac.DONTCARE)
1092 ip_dontcare = '0.0.0.0'
1093 ipv6_dontcare = '::'
1096 for key, value in of_match.items():
1097 if key == REST_SRC_MAC or key == REST_DST_MAC:
1098 if value == mac_dontcare:
1100 elif key == REST_SRC_IP or key == REST_DST_IP:
1101 if value == ip_dontcare:
1103 elif key == REST_SRC_IPV6 or key == REST_DST_IPV6:
1104 if value == ipv6_dontcare:
1109 if key in Match._CONVERT:
1110 conv = Match._CONVERT[key]
1111 conv = dict((value, key) for key, value in conv.items())
1112 match.setdefault(key, conv[value])
1114 match.setdefault(key, value)
1119 def to_mod_openflow(of_match):
1120 mac_dontcare = mac.haddr_to_str(mac.DONTCARE)
1121 ip_dontcare = '0.0.0.0'
1122 ipv6_dontcare = '::'
1125 for key, value in of_match.items():
1126 if key == REST_SRC_MAC or key == REST_DST_MAC:
1127 if value == mac_dontcare:
1129 elif key == REST_SRC_IP or key == REST_DST_IP:
1130 if value == ip_dontcare:
1132 elif key == REST_SRC_IPV6 or key == REST_DST_IPV6:
1133 if value == ipv6_dontcare:
1138 match.setdefault(key, value)
1143 class Action(object):
1147 if REST_ACTION in flow:
1149 for act in flow[REST_ACTION]:
1150 field_value = re.search(r'SET_FIELD: \{ip_dscp:(\d+)', act)
1152 actions.append({REST_ACTION_MARK: field_value.group(1)})
1153 meter_value = re.search(r'METER:(\d+)', act)
1155 actions.append({REST_ACTION_METER: meter_value.group(1)})
1156 queue_value = re.search(r'SET_QUEUE:(\d+)', act)
1158 actions.append({REST_ACTION_QUEUE: queue_value.group(1)})
1159 action = {REST_ACTION: actions}
1161 action = {REST_ACTION: 'Unknown action type.'}