backing up
[vsorcdistro/.git] / ryu / build / lib.linux-armv7l-2.7 / ryu / app / rest_qos.py
1 # Copyright (C) 2014 Kiyonari Harigae <lakshmi at cloudysunny14 org>
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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
12 # implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16
17 import logging
18 import json
19 import re
20
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
45
46
47 # =============================
48 #          REST API
49 # =============================
50 #
51 #  Note: specify switch and vlan group, as follows.
52 #   {switch-id} : 'all' or switchID
53 #   {vlan-id}   : 'all' or vlanID
54 #
55 # about queue status
56 #
57 # get status of queue
58 # GET /qos/queue/status/{switch-id}
59 #
60 # about queues
61 # get a queue configurations
62 # GET /qos/queue/{switch-id}
63 #
64 # set a queue to the switches
65 # POST /qos/queue/{switch-id}
66 #
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>"},...]}
72 #
73 #   Note: This operation override
74 #         previous configurations.
75 #   Note: Queue configurations are available for
76 #         OpenvSwitch.
77 #   Note: port_name is optional argument.
78 #         If does not pass the port_name argument,
79 #         all ports are target for configuration.
80 #
81 # delete queue
82 # DELETE /qos/queue/{swtich-id}
83 #
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.
87 #
88 # about qos rules
89 #
90 # get rules of qos
91 # * for no vlan
92 # GET /qos/rules/{switch-id}
93 #
94 # * for specific vlan group
95 # GET /qos/rules/{switch-id}/{vlan-id}
96 #
97 # set a qos rules
98 #
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.
102 #
103 # * for no vlan
104 # POST /qos/{switch-id}
105 #
106 # * for specific vlan group
107 # POST /qos/{switch-id}/{vlan-id}
108 #
109 #  request body format:
110 #   {"priority": "<value>",
111 #    "match": {"<field1>": "<value1>", "<field2>": "<value2>",...},
112 #    "actions": {"<action1>": "<value1>", "<action2>": "<value2>",...}
113 #   }
114 #
115 #  Description
116 #    * priority field
117 #     <value>
118 #    "0 to 65533"
119 #
120 #   Note: When "priority" has not been set up,
121 #         "priority: 1" is set to "priority".
122 #
123 #    * match field
124 #     <field> : <value>
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>"
134 #    "tp_src"  : "<int>"
135 #    "tp_dst"  : "<int>"
136 #    "ip_dscp" : "<int>"
137 #
138 #    * actions field
139 #     <field> : <value>
140 #    "mark": <dscp-value>
141 #    sets the IPv4 ToS/DSCP field to tos.
142 #    "meter": <meter-id>
143 #    apply meter entry
144 #    "queue": <queue-id>
145 #    register queue specified by queue-id
146 #
147 #   Note: When "actions" has not been set up,
148 #         "queue: 0" is set to "actions".
149 #
150 # delete a qos rules
151 # * for no vlan
152 # DELETE /qos/rule/{switch-id}
153 #
154 # * for specific vlan group
155 # DELETE /qos/{switch-id}/{vlan-id}
156 #
157 #  request body format:
158 #   {"<field>":"<value>"}
159 #
160 #     <field>  : <value>
161 #    "qos_id" : "<int>" or "all"
162 #
163 # about meter entries
164 #
165 # set a meter entry
166 # POST /qos/meter/{switch-id}
167 #
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>,
173 #              "rate": <int>,
174 #              "prec_level": <int>},...]}
175 #
176 # delete a meter entry
177 # DELETE /qos/meter/{switch-id}
178 #
179 #  request body format:
180 #   {"<field>":"<value>"}
181 #
182 #     <field>  : <value>
183 #    "meter_id" : "<int>"
184 #
185
186
187 SWITCHID_PATTERN = dpid_lib.DPID_PATTERN + r'|all'
188 VLANID_PATTERN = r'[0-9]{1,4}|all'
189
190 QOS_TABLE_ID = 0
191
192 REST_ALL = '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'
202 REST_QOS = 'qos'
203 REST_QOS_ID = 'qos_id'
204 REST_COOKIE = 'cookie'
205
206 REST_MATCH = 'match'
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'
227
228 REST_ACTION = 'actions'
229 REST_ACTION_QUEUE = 'queue'
230 REST_ACTION_MARK = 'mark'
231 REST_ACTION_METER = 'meter'
232
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'
240
241 DEFAULT_FLOW_PRIORITY = 0
242 QOS_PRIORITY_MAX = ofproto_v1_3_parser.UINT16_MAX - 1
243 QOS_PRIORITY_MIN = 1
244
245 VLANID_NONE = 0
246 VLANID_MIN = 2
247 VLANID_MAX = 4094
248 COOKIE_SHIFT_VLANID = 32
249
250 BASE_URL = '/qos'
251 REQUIREMENTS = {'switchid': SWITCHID_PATTERN,
252                 'vlanid': VLANID_PATTERN}
253
254 LOG = logging.getLogger(__name__)
255
256
257 class RestQoSAPI(app_manager.RyuApp):
258
259     OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION,
260                     ofproto_v1_2.OFP_VERSION,
261                     ofproto_v1_3.OFP_VERSION]
262
263     _CONTEXTS = {
264         'dpset': dpset.DPSet,
265         'conf_switch': conf_switch.ConfSwitchSet,
266         'wsgi': WSGIApplication}
267
268     def __init__(self, *args, **kwargs):
269         super(RestQoSAPI, self).__init__(*args, **kwargs)
270
271         # logger configure
272         QoSController.set_logger(self.logger)
273         self.cs = kwargs['conf_switch']
274         self.dpset = kwargs['dpset']
275         wsgi = kwargs['wsgi']
276         self.waiters = {}
277         self.data = {}
278         self.data['dpset'] = self.dpset
279         self.data['waiters'] = self.waiters
280         wsgi.registory['QoSController'] = self.data
281         wsgi.register(QoSController, self.data)
282
283     def stats_reply_handler(self, ev):
284         msg = ev.msg
285         dp = msg.datapath
286
287         if dp.id not in self.waiters:
288             return
289         if msg.xid not in self.waiters[dp.id]:
290             return
291         lock, msgs = self.waiters[dp.id][msg.xid]
292         msgs.append(msg)
293
294         flags = 0
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
300
301         if msg.flags & flags:
302             return
303         del self.waiters[dp.id][msg.xid]
304         lock.set()
305
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)
310         else:
311             QoSController._LOGGER.debug("unknown event: %s", ev)
312
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)
317         else:
318             QoSController._LOGGER.debug("unknown event: %s", ev)
319
320     @set_ev_cls(dpset.EventDP, dpset.DPSET_EV_DISPATCHER)
321     def handler_datapath(self, ev):
322         if ev.enter:
323             QoSController.regist_ofs(ev.dp, self.CONF)
324         else:
325             QoSController.unregist_ofs(ev.dp)
326
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)
331
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)
336
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)
341
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)
346
347
348 class QoSOfsList(dict):
349
350     def __init__(self):
351         super(QoSOfsList, self).__init__()
352
353     def get_ofs(self, dp_id):
354         if len(self) == 0:
355             raise ValueError('qos sw is not connected.')
356
357         dps = {}
358         if dp_id == REST_ALL:
359             dps = self
360         else:
361             try:
362                 dpid = dpid_lib.str_to_dpid(dp_id)
363             except:
364                 raise ValueError('Invalid switchID.')
365
366             if dpid in self:
367                 dps = {dpid: self[dpid]}
368             else:
369                 msg = 'qos sw is not connected. : switchID=%s' % dp_id
370                 raise ValueError(msg)
371
372         return dps
373
374
375 class QoSController(ControllerBase):
376
377     _OFS_LIST = QoSOfsList()
378     _LOGGER = None
379
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']
384
385     @classmethod
386     def set_logger(cls, logger):
387         cls._LOGGER = 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)
393
394     @staticmethod
395     def regist_ofs(dp, CONF):
396         if dp.id in QoSController._OFS_LIST:
397             return
398
399         dpid_str = dpid_lib.dpid_to_str(dp.id)
400         try:
401             f_ofs = QoS(dp, CONF)
402             f_ofs.set_default_flow()
403         except OFPUnknownVersion as message:
404             QoSController._LOGGER.info('dpid=%s: %s',
405                                        dpid_str, message)
406             return
407
408         QoSController._OFS_LIST.setdefault(dp.id, f_ofs)
409         QoSController._LOGGER.info('dpid=%s: Join qos switch.',
410                                    dpid_str)
411
412     @staticmethod
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))
418
419     @staticmethod
420     def set_ovsdb_addr(dpid, value):
421         ofs = QoSController._OFS_LIST.get(dpid, None)
422         if ofs is not None:
423             ofs.set_ovsdb_addr(dpid, value)
424
425     @staticmethod
426     def delete_ovsdb_addr(dpid):
427         ofs = QoSController._OFS_LIST.get(dpid, None)
428         if ofs is not None:
429             ofs.set_ovsdb_addr(dpid, None)
430
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,
435                                    'get_queue', None)
436
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,
441                                    'set_queue', None)
442
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)
448
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)
454
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)
460
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)
466
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)
472
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)
478
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)
484
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)
490
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)
496
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)
502
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)
508
509     def _access_switch(self, req, switchid, vlan_id, func, waiters):
510         try:
511             rest = req.json if req.body else {}
512         except ValueError:
513             QoSController._LOGGER.debug('invalid syntax %s', req.body)
514             return Response(status=400)
515
516         try:
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))
521
522         msgs = []
523         for f_ofs in dps.values():
524             function = getattr(f_ofs, func)
525             try:
526                 if waiters is not None:
527                     msg = function(rest, vid, waiters)
528                 else:
529                     msg = function(rest, vid)
530             except ValueError as message:
531                 return Response(status=400, body=str(message))
532             msgs.append(msg)
533
534         body = json.dumps(msgs)
535         return Response(content_type='application/json', body=body)
536
537     @staticmethod
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,
544                                                                 VLANID_MAX)
545                 raise ValueError(msg)
546         return vlan_id
547
548
549 class QoS(object):
550
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}
554
555     def __init__(self, dp, CONF):
556         super(QoS, self).__init__()
557         self.vlan_list = {}
558         self.vlan_list[VLANID_NONE] = 0  # for VLAN=None
559         self.dp = dp
560         self.version = dp.ofproto.OFP_VERSION
561         # Dictionary of port name to Queue config.
562         # e.g.)
563         # self.queue_list = {
564         #     "s1-eth1": {
565         #         "0": {
566         #             "config": {
567         #                 "max-rate": "600000"
568         #             }
569         #         },
570         #         "1": {
571         #             "config": {
572         #                 "min-rate": "900000"
573         #             }
574         #         }
575         #     }
576         # }
577         self.queue_list = {}
578         self.CONF = CONF
579         self.ovsdb_addr = None
580         self.ovs_bridge = None
581
582         if self.version not in self._OFCTL:
583             raise OFPUnknownVersion(version=self.version)
584
585         self.ofctl = self._OFCTL[self.version]
586
587     def set_default_flow(self):
588         if self.version == ofproto_v1_0.OFP_VERSION:
589             return
590
591         cookie = 0
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,
596                                 priority=priority,
597                                 match={},
598                                 actions=actions)
599
600         cmd = self.dp.ofproto.OFPFC_ADD
601         self.ofctl.mod_flow_entry(self.dp, flow, cmd)
602
603     def set_ovsdb_addr(self, dpid, ovsdb_addr):
604         old_address = self.ovsdb_addr
605         if old_address == ovsdb_addr:
606             return
607         elif ovsdb_addr is None:
608             # Determine deleting OVSDB address was requested.
609             if self.ovs_bridge:
610                 self.ovs_bridge = None
611             return
612
613         ovs_bridge = bridge.OVSBridge(self.CONF, dpid, ovsdb_addr)
614         try:
615             ovs_bridge.init()
616         except:
617             raise ValueError('ovsdb addr is not available.')
618         self.ovsdb_addr = ovsdb_addr
619         self.ovs_bridge = ovs_bridge
620
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]
625
626     def _get_cookie(self, vlan_id):
627         if vlan_id == REST_ALL:
628             vlan_ids = self.vlan_list.keys()
629         else:
630             vlan_ids = [vlan_id]
631
632         cookie_list = []
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])
640
641         return cookie_list
642
643     @staticmethod
644     def _cookie_to_qosid(cookie):
645         return cookie & ofproto_v1_3_parser.UINT32_MAX
646
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,
653                     key: value}
654         return _rest_command
655
656     @rest_command
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')
660
661         msgs = self.ofctl.get_queue_stats(self.dp, waiters)
662         return REST_COMMAND_RESULT, msgs
663
664     @rest_command
665     def get_queue(self, rest, vlan_id):
666         if len(self.queue_list):
667             msg = {'result': 'success',
668                    'details': self.queue_list}
669         else:
670             msg = {'result': 'failure',
671                    'details': 'Queue is not exists.'}
672
673         return REST_COMMAND_RESULT, msg
674
675     @rest_command
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
681
682         port_name = rest.get(REST_PORT_NAME, None)
683         vif_ports = self.ovs_bridge.get_port_name_list()
684
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]
689
690         queue_list = {}
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, [])
694         queue_id = 0
695         queue_config = []
696         for queue in 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')
701             config = {}
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
706             if len(config):
707                 queue_config.append(config)
708             queue_list[queue_id] = {'config': config}
709             queue_id += 1
710
711         for port_name in vif_ports:
712             try:
713                 self.ovs_bridge.set_qos(port_name, type=queue_type,
714                                         max_rate=parent_max_rate,
715                                         queues=queue_config)
716             except Exception as msg:
717                 raise ValueError(msg)
718             self.queue_list[port_name] = queue_list
719
720         msg = {'result': 'success',
721                'details': queue_list}
722
723         return REST_COMMAND_RESULT, msg
724
725     def _delete_queue(self):
726         if self.ovs_bridge is None:
727             return False
728
729         vif_ports = self.ovs_bridge.get_external_ports()
730         for port in vif_ports:
731             self.ovs_bridge.del_qos(port.port_name)
732         return True
733
734     @rest_command
735     def delete_queue(self, rest, vlan_id):
736         if self._delete_queue():
737             msg = 'success'
738             self.queue_list.clear()
739         else:
740             msg = 'failure'
741
742         return REST_COMMAND_RESULT, msg
743
744     @rest_command
745     def set_qos(self, rest, vlan_id, waiters):
746         msgs = []
747         cookie_list = self._get_cookie(vlan_id)
748         for cookie, vid in cookie_list:
749             msg = self._set_qos(cookie, rest, waiters, vid)
750             msgs.append(msg)
751         return REST_COMMAND_RESULT, msgs
752
753     def _set_qos(self, cookie, rest, waiters, vlan_id):
754         match_value = rest[REST_MATCH]
755         if vlan_id:
756             match_value[REST_DL_VLAN] = vlan_id
757
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))
762
763         match = Match.to_openflow(match_value)
764
765         actions = []
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',
770                                 'field': REST_DSCP,
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]})
778         else:
779             actions.append({'type': 'SET_QUEUE',
780                             'queue_id': 0})
781
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)
786
787         cmd = self.dp.ofproto.OFPFC_ADD
788         try:
789             self.ofctl.mod_flow_entry(self.dp, flow, cmd)
790         except:
791             raise ValueError('Invalid rule parameter.')
792
793         qos_id = QoS._cookie_to_qosid(cookie)
794         msg = {'result': 'success',
795                'details': 'QoS added. : qos_id=%d' % qos_id}
796
797         if vlan_id != VLANID_NONE:
798             msg.setdefault(REST_VLANID, vlan_id)
799         return msg
800
801     @rest_command
802     def get_qos(self, rest, vlan_id, waiters):
803         rules = {}
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:
809                     continue
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)
817
818         get_data = []
819         for vid, rule in rules.items():
820             if vid == VLANID_NONE:
821                 vid_data = {REST_QOS: rule}
822             else:
823                 vid_data = {REST_VLANID: vid, REST_QOS: rule}
824             get_data.append(vid_data)
825
826         return REST_COMMAND_RESULT, get_data
827
828     @rest_command
829     def delete_qos(self, rest, vlan_id, waiters):
830         try:
831             if rest[REST_QOS_ID] == REST_ALL:
832                 qos_id = REST_ALL
833             else:
834                 qos_id = int(rest[REST_QOS_ID])
835         except:
836             raise ValueError('Invalid qos id.')
837
838         vlan_list = []
839         delete_list = []
840
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)
849
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])
855                     else:
856                         if dl_vlan not in vlan_list:
857                             vlan_list.append(dl_vlan)
858
859         self._update_vlan_list(vlan_list)
860
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}
867         else:
868             cmd = self.dp.ofproto.OFPFC_DELETE_STRICT
869             actions = []
870             delete_ids = {}
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)
875
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)
881
882             msg = []
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)
888                 msg.append(del_msg)
889
890         return REST_COMMAND_RESULT, msg
891
892     @rest_command
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')
896
897         msgs = []
898         msg = self._set_meter(rest, waiters)
899         msgs.append(msg)
900         return REST_COMMAND_RESULT, msgs
901
902     def _set_meter(self, rest, waiters):
903         cmd = self.dp.ofproto.OFPMC_ADD
904         try:
905             self.ofctl.mod_meter_entry(self.dp, rest, cmd)
906         except:
907             raise ValueError('Invalid meter parameter.')
908
909         msg = {'result': 'success',
910                'details': 'Meter added. : Meter ID=%s' %
911                rest[REST_METER_ID]}
912         return msg
913
914     @rest_command
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')
919
920         msgs = self.ofctl.get_meter_stats(self.dp, waiters)
921         return REST_COMMAND_RESULT, msgs
922
923     @rest_command
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')
928
929         cmd = self.dp.ofproto.OFPMC_DELETE
930         try:
931             self.ofctl.mod_meter_entry(self.dp, rest, cmd)
932         except:
933             raise ValueError('Invalid meter parameter.')
934
935         msg = {'result': 'success',
936                'details': 'Meter deleted. : Meter ID=%s' %
937                rest[REST_METER_ID]}
938         return REST_COMMAND_RESULT, msg
939
940     def _to_of_flow(self, cookie, priority, match, actions):
941         flow = {'cookie': cookie,
942                 'priority': priority,
943                 'flags': 0,
944                 'idle_timeout': 0,
945                 'hard_timeout': 0,
946                 'match': match,
947                 'actions': actions}
948         return flow
949
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))
956         return rule
957
958
959 class Match(object):
960
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},
965                 REST_NW_PROTO:
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}}
970
971     @staticmethod
972     def to_openflow(rest):
973
974         def __inv_combi(msg):
975             raise ValueError('Invalid combination: [%s]' % msg)
976
977         def __inv_2and1(*args):
978             __inv_combi('%s=%s and %s' % (args[0], args[1], args[2]))
979
980         def __inv_2and2(*args):
981             __inv_combi('%s=%s and %s=%s' % (
982                 args[0], args[1], args[2], args[3]))
983
984         def __inv_1and1(*args):
985             __inv_combi('%s and %s' % (args[0], args[1]))
986
987         def __inv_1and2(*args):
988             __inv_combi('%s and %s=%s' % (args[0], args[1], args[2]))
989
990         match = {}
991
992         # error check
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:
998                     __inv_2and1(
999                         REST_DL_TYPE, REST_DL_TYPE_ARP, REST_SRC_IPV6)
1000                 if REST_DST_IPV6 in rest:
1001                     __inv_2and1(
1002                         REST_DL_TYPE, REST_DL_TYPE_ARP, REST_DST_IPV6)
1003                 if REST_DSCP in rest:
1004                     __inv_2and1(
1005                         REST_DL_TYPE, REST_DL_TYPE_ARP, REST_DSCP)
1006                 if nw_proto:
1007                     __inv_2and1(
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:
1011                     __inv_2and1(
1012                         REST_DL_TYPE, REST_DL_TYPE_IPV4, REST_SRC_IPV6)
1013                 if REST_DST_IPV6 in rest:
1014                     __inv_2and1(
1015                         REST_DL_TYPE, REST_DL_TYPE_IPV4, REST_DST_IPV6)
1016                 if nw_proto == REST_NW_PROTO_ICMPV6:
1017                     __inv_2and2(
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:
1022                     __inv_2and1(
1023                         REST_DL_TYPE, REST_DL_TYPE_IPV6, REST_SRC_IP)
1024                 if REST_DST_IP in rest:
1025                     __inv_2and1(
1026                         REST_DL_TYPE, REST_DL_TYPE_IPV6, REST_DST_IP)
1027                 if nw_proto == REST_NW_PROTO_ICMP:
1028                     __inv_2and2(
1029                         REST_DL_TYPE, REST_DL_TYPE_IPV6,
1030                         REST_NW_PROTO, REST_NW_PROTO_ICMP)
1031             else:
1032                 raise ValueError('Unknown dl_type : %s' % dl_type)
1033         else:
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:
1040                     __inv_1and2(
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:
1049                     __inv_1and2(
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:
1054                     __inv_1and2(
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:
1059                     __inv_1and2(
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
1065             else:
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')
1073                 else:
1074                     raise ValueError('Unknown nw_proto: %s' % nw_proto)
1075
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])
1080                 else:
1081                     raise ValueError('Invalid rule parameter. : key=%s' % key)
1082             else:
1083                 match.setdefault(key, value)
1084
1085         return match
1086
1087     @staticmethod
1088     def to_rest(openflow):
1089         of_match = openflow[REST_MATCH]
1090
1091         mac_dontcare = mac.haddr_to_str(mac.DONTCARE)
1092         ip_dontcare = '0.0.0.0'
1093         ipv6_dontcare = '::'
1094
1095         match = {}
1096         for key, value in of_match.items():
1097             if key == REST_SRC_MAC or key == REST_DST_MAC:
1098                 if value == mac_dontcare:
1099                     continue
1100             elif key == REST_SRC_IP or key == REST_DST_IP:
1101                 if value == ip_dontcare:
1102                     continue
1103             elif key == REST_SRC_IPV6 or key == REST_DST_IPV6:
1104                 if value == ipv6_dontcare:
1105                     continue
1106             elif value == 0:
1107                 continue
1108
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])
1113             else:
1114                 match.setdefault(key, value)
1115
1116         return match
1117
1118     @staticmethod
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 = '::'
1123
1124         match = {}
1125         for key, value in of_match.items():
1126             if key == REST_SRC_MAC or key == REST_DST_MAC:
1127                 if value == mac_dontcare:
1128                     continue
1129             elif key == REST_SRC_IP or key == REST_DST_IP:
1130                 if value == ip_dontcare:
1131                     continue
1132             elif key == REST_SRC_IPV6 or key == REST_DST_IPV6:
1133                 if value == ipv6_dontcare:
1134                     continue
1135             elif value == 0:
1136                 continue
1137
1138             match.setdefault(key, value)
1139
1140         return match
1141
1142
1143 class Action(object):
1144
1145     @staticmethod
1146     def to_rest(flow):
1147         if REST_ACTION in flow:
1148             actions = []
1149             for act in flow[REST_ACTION]:
1150                 field_value = re.search(r'SET_FIELD: \{ip_dscp:(\d+)', act)
1151                 if field_value:
1152                     actions.append({REST_ACTION_MARK: field_value.group(1)})
1153                 meter_value = re.search(r'METER:(\d+)', act)
1154                 if meter_value:
1155                     actions.append({REST_ACTION_METER: meter_value.group(1)})
1156                 queue_value = re.search(r'SET_QUEUE:(\d+)', act)
1157                 if queue_value:
1158                     actions.append({REST_ACTION_QUEUE: queue_value.group(1)})
1159             action = {REST_ACTION: actions}
1160         else:
1161             action = {REST_ACTION: 'Unknown action type.'}
1162
1163         return action