1# Copyright (C) 2012 Nippon Telegraph and Telephone Corporation. 2# Copyright (C) 2012 Isaku Yamahata <yamahata at private email ne jp> 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 13# implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17""" 18Wrapper utility library of :py:mod:`ryu.lib.ovs.vsctl` 19""" 20 21import functools 22import logging 23 24from ryu import cfg 25import ryu.exception as ryu_exc 26import ryu.lib.dpid as dpid_lib 27import ryu.lib.ovs.vsctl as ovs_vsctl 28from ryu.lib.ovs.vsctl import valid_ovsdb_addr 29 30 31LOG = logging.getLogger(__name__) 32 33CONF = cfg.CONF 34CONF.register_opts([ 35 cfg.IntOpt('ovsdb-timeout', default=2, help='ovsdb timeout') 36]) 37 38 39class OVSBridgeNotFound(ryu_exc.RyuException): 40 message = 'no bridge for datapath_id %(datapath_id)s' 41 42 43class VifPort(object): 44 45 def __init__(self, port_name, ofport, vif_id, vif_mac, switch): 46 super(VifPort, self).__init__() 47 self.port_name = port_name 48 self.ofport = ofport 49 self.vif_id = vif_id 50 self.vif_mac = vif_mac 51 self.switch = switch 52 53 def __str__(self): 54 return ('iface-id=%s, ' 55 'vif_mac=%s, ' 56 'port_name=%s, ' 57 'ofport=%d, ' 58 'bridge_name=%s' % (self.vif_id, 59 self.vif_mac, 60 self.port_name, 61 self.ofport, 62 self.switch.br_name)) 63 64 65class TunnelPort(object): 66 67 def __init__(self, port_name, ofport, tunnel_type, local_ip, remote_ip): 68 super(TunnelPort, self).__init__() 69 self.port_name = port_name 70 self.ofport = ofport 71 self.tunnel_type = tunnel_type 72 self.local_ip = local_ip 73 self.remote_ip = remote_ip 74 75 def __eq__(self, other): 76 return (self.port_name == other.port_name and 77 self.ofport == other.ofport and 78 self.tunnel_type == other.tunnel_type and 79 self.local_ip == other.local_ip and 80 self.remote_ip == other.remote_ip) 81 82 def __str__(self): 83 return ('port_name=%s, ' 84 'ofport=%s, ' 85 'type=%s, ' 86 'local_ip=%s, ' 87 'remote_ip=%s' % (self.port_name, 88 self.ofport, 89 self.tunnel_type, 90 self.local_ip, 91 self.remote_ip)) 92 93 94class OVSBridge(object): 95 """ 96 Class to provide wrapper utilities of :py:mod:`ryu.lib.ovs.vsctl.VSCtl` 97 98 ``CONF`` is a instance of ``oslo_config.cfg.ConfigOpts``. 99 Mostly ``self.CONF`` is sufficient to instantiate this class from your Ryu 100 application. 101 102 ``datapath_id`` specifies Datapath ID of the target OVS instance. 103 104 ``ovsdb_addr`` specifies the address of the OVS instance. 105 Automatically validated when you call ``init()`` method. 106 Refer to :py:mod:`ryu.lib.ovs.vsctl.valid_ovsdb_addr` for the format of 107 this address. 108 109 if ``timeout`` is omitted, ``CONF.ovsdb_timeout`` will be used as the 110 default value. 111 112 Usage of ``timeout`` and ``exception`` is the same with ``timeout_sec`` 113 and ``exception`` of :py:mod:`ryu.lib.ovs.vsctl.VSCtl.run_command`. 114 """ 115 116 def __init__(self, CONF, datapath_id, ovsdb_addr, timeout=None, 117 exception=None): 118 super(OVSBridge, self).__init__() 119 self.datapath_id = datapath_id 120 self.ovsdb_addr = ovsdb_addr 121 self.vsctl = ovs_vsctl.VSCtl(ovsdb_addr) 122 self.timeout = timeout or CONF.ovsdb_timeout 123 self.exception = exception 124 125 self.br_name = None 126 127 def run_command(self, commands): 128 """ 129 Executes the given commands and sends OVSDB messages. 130 131 ``commands`` must be a list of 132 :py:mod:`ryu.lib.ovs.vsctl.VSCtlCommand`. 133 134 The given ``timeout`` and ``exception`` when instantiation will be used 135 to call :py:mod:`ryu.lib.ovs.vsctl.VSCtl.run_command`. 136 """ 137 self.vsctl.run_command(commands, self.timeout, self.exception) 138 139 def init(self): 140 """ 141 Validates the given ``ovsdb_addr`` and connects to OVS instance. 142 143 If failed to connect to OVS instance or the given ``datapath_id`` does 144 not match with the Datapath ID of the connected OVS instance, raises 145 :py:mod:`ryu.lib.ovs.bridge.OVSBridgeNotFound` exception. 146 """ 147 if not valid_ovsdb_addr(self.ovsdb_addr): 148 raise ValueError('Invalid OVSDB address: %s' % self.ovsdb_addr) 149 if self.br_name is None: 150 self.br_name = self._get_bridge_name() 151 152 def _get_bridge_name(self): 153 """ get Bridge name of a given 'datapath_id' """ 154 command = ovs_vsctl.VSCtlCommand( 155 'find', 156 ('Bridge', 157 'datapath_id=%s' % dpid_lib.dpid_to_str(self.datapath_id))) 158 self.run_command([command]) 159 if not isinstance(command.result, list) or len(command.result) != 1: 160 raise OVSBridgeNotFound( 161 datapath_id=dpid_lib.dpid_to_str(self.datapath_id)) 162 return command.result[0].name 163 164 def get_controller(self): 165 """ 166 Gets the configured OpenFlow controller address. 167 168 This method is corresponding to the following ovs-vsctl command:: 169 170 $ ovs-vsctl get-controller <bridge> 171 """ 172 command = ovs_vsctl.VSCtlCommand('get-controller', [self.br_name]) 173 self.run_command([command]) 174 return command.result[0] 175 176 def set_controller(self, controllers): 177 """ 178 Sets the OpenFlow controller address. 179 180 This method is corresponding to the following ovs-vsctl command:: 181 182 $ ovs-vsctl set-controller <bridge> <target>... 183 """ 184 command = ovs_vsctl.VSCtlCommand('set-controller', [self.br_name]) 185 command.args.extend(controllers) 186 self.run_command([command]) 187 188 def del_controller(self): 189 """ 190 Deletes the configured OpenFlow controller address. 191 192 This method is corresponding to the following ovs-vsctl command:: 193 194 $ ovs-vsctl del-controller <bridge> 195 """ 196 command = ovs_vsctl.VSCtlCommand('del-controller', [self.br_name]) 197 self.run_command([command]) 198 199 def list_db_attributes(self, table, record=None): 200 """ 201 Lists 'record' (or all records) in 'table'. 202 203 This method is corresponding to the following ovs-vsctl command:: 204 205 $ ovs-vsctl list TBL [REC] 206 """ 207 command = ovs_vsctl.VSCtlCommand('list', (table, record)) 208 self.run_command([command]) 209 if command.result: 210 return command.result 211 return [] 212 213 def find_db_attributes(self, table, *conditions): 214 """ 215 Lists records satisfying 'conditions' in 'table'. 216 217 This method is corresponding to the following ovs-vsctl command:: 218 219 $ ovs-vsctl find TBL CONDITION... 220 221 .. Note:: 222 223 Currently, only '=' condition is supported. 224 To support other condition is TODO. 225 """ 226 args = [table] 227 args.extend(conditions) 228 command = ovs_vsctl.VSCtlCommand('find', args) 229 self.run_command([command]) 230 if command.result: 231 return command.result 232 return [] 233 234 def get_db_attribute(self, table, record, column, key=None): 235 """ 236 Gets values of 'column' in 'record' in 'table'. 237 238 This method is corresponding to the following ovs-vsctl command:: 239 240 $ ovs-vsctl get TBL REC COL[:KEY] 241 """ 242 if key is not None: 243 column = '%s:%s' % (column, key) 244 command = ovs_vsctl.VSCtlCommand( 245 'get', (table, record, column)) 246 self.run_command([command]) 247 if command.result: 248 return command.result[0] 249 return None 250 251 def set_db_attribute(self, table, record, column, value, key=None): 252 """ 253 Sets 'value' into 'column' in 'record' in 'table'. 254 255 This method is corresponding to the following ovs-vsctl command:: 256 257 $ ovs-vsctl set TBL REC COL[:KEY]=VALUE 258 """ 259 if key is not None: 260 column = '%s:%s' % (column, key) 261 command = ovs_vsctl.VSCtlCommand( 262 'set', (table, record, '%s=%s' % (column, value))) 263 self.run_command([command]) 264 265 def add_db_attribute(self, table, record, column, value, key=None): 266 """ 267 Adds ('key'=)'value' into 'column' in 'record' in 'table'. 268 269 This method is corresponding to the following ovs-vsctl command:: 270 271 $ ovs-vsctl add TBL REC COL [KEY=]VALUE 272 """ 273 if key is not None: 274 value = '%s=%s' % (key, value) 275 command = ovs_vsctl.VSCtlCommand( 276 'add', (table, record, column, value)) 277 self.run_command([command]) 278 279 def remove_db_attribute(self, table, record, column, value, key=None): 280 """ 281 Removes ('key'=)'value' into 'column' in 'record' in 'table'. 282 283 This method is corresponding to the following ovs-vsctl command:: 284 285 $ ovs-vsctl remove TBL REC COL [KEY=]VALUE 286 """ 287 if key is not None: 288 value = '%s=%s' % (key, value) 289 command = ovs_vsctl.VSCtlCommand( 290 'remove', (table, record, column, value)) 291 self.run_command([command]) 292 293 def clear_db_attribute(self, table, record, column): 294 """ 295 Clears values from 'column' in 'record' in 'table'. 296 297 This method is corresponding to the following ovs-vsctl command:: 298 299 $ ovs-vsctl clear TBL REC COL 300 """ 301 command = ovs_vsctl.VSCtlCommand('clear', (table, record, column)) 302 self.run_command([command]) 303 304 def db_get_val(self, table, record, column): 305 """ 306 Gets values of 'column' in 'record' in 'table'. 307 308 This method is corresponding to the following ovs-vsctl command:: 309 310 $ ovs-vsctl get TBL REC COL 311 """ 312 command = ovs_vsctl.VSCtlCommand('get', (table, record, column)) 313 self.run_command([command]) 314 assert len(command.result) == 1 315 return command.result[0] 316 317 def db_get_map(self, table, record, column): 318 """ 319 Gets dict type value of 'column' in 'record' in 'table'. 320 321 This method is corresponding to the following ovs-vsctl command:: 322 323 $ ovs-vsctl get TBL REC COL 324 """ 325 val = self.db_get_val(table, record, column) 326 assert isinstance(val, dict) 327 return val 328 329 def get_datapath_id(self): 330 """ 331 Gets Datapath ID of OVS instance. 332 333 This method is corresponding to the following ovs-vsctl command:: 334 335 $ ovs-vsctl get Bridge <bridge> datapath_id 336 """ 337 return self.db_get_val('Bridge', self.br_name, 'datapath_id') 338 339 def delete_port(self, port_name): 340 """ 341 Deletes a port on the OVS instance. 342 343 This method is corresponding to the following ovs-vsctl command:: 344 345 $ ovs-vsctl --if-exists del-port <bridge> <port> 346 """ 347 command = ovs_vsctl.VSCtlCommand( 348 'del-port', (self.br_name, port_name), '--if-exists') 349 self.run_command([command]) 350 351 def get_ofport(self, port_name): 352 """ 353 Gets the OpenFlow port number. 354 355 This method is corresponding to the following ovs-vsctl command:: 356 357 $ ovs-vsctl get Interface <port> ofport 358 """ 359 ofport_list = self.db_get_val('Interface', port_name, 'ofport') 360 assert len(ofport_list) == 1 361 return int(ofport_list[0]) 362 363 def get_port_name_list(self): 364 """ 365 Gets a list of all ports on OVS instance. 366 367 This method is corresponding to the following ovs-vsctl command:: 368 369 $ ovs-vsctl list-ports <bridge> 370 """ 371 command = ovs_vsctl.VSCtlCommand('list-ports', (self.br_name, )) 372 self.run_command([command]) 373 return command.result 374 375 def add_bond(self, name, ifaces, bond_mode=None, lacp=None): 376 """ 377 Creates a bonded port. 378 379 :param name: Port name to be created 380 :param ifaces: List of interfaces containing at least 2 interfaces 381 :param bond_mode: Bonding mode (active-backup, balance-tcp 382 or balance-slb) 383 :param lacp: LACP mode (active, passive or off) 384 """ 385 assert len(ifaces) >= 2 386 387 options = '' 388 if bond_mode: 389 options += 'bond_mode=%(bond_mode)s' % locals() 390 if lacp: 391 options += 'lacp=%(lacp)s' % locals() 392 393 command_add = ovs_vsctl.VSCtlCommand( 394 'add-bond', (self.br_name, name, ifaces), options) 395 self.run_command([command_add]) 396 397 def add_tunnel_port(self, name, tunnel_type, remote_ip, 398 local_ip=None, key=None, ofport=None): 399 """ 400 Creates a tunnel port. 401 402 :param name: Port name to be created 403 :param tunnel_type: Type of tunnel (gre or vxlan) 404 :param remote_ip: Remote IP address of tunnel 405 :param local_ip: Local IP address of tunnel 406 :param key: Key of GRE or VNI of VxLAN 407 :param ofport: Requested OpenFlow port number 408 """ 409 options = 'remote_ip=%(remote_ip)s' % locals() 410 if key: 411 options += ',key=%(key)s' % locals() 412 if local_ip: 413 options += ',local_ip=%(local_ip)s' % locals() 414 415 args = ['Interface', name, 'type=%s' % tunnel_type, 416 'options:%s' % options] 417 if ofport: 418 args.append('ofport_request=%(ofport)s' % locals()) 419 420 command_add = ovs_vsctl.VSCtlCommand('add-port', (self.br_name, name)) 421 command_set = ovs_vsctl.VSCtlCommand('set', args) 422 self.run_command([command_add, command_set]) 423 424 def add_gre_port(self, name, remote_ip, 425 local_ip=None, key=None, ofport=None): 426 """ 427 Creates a GRE tunnel port. 428 429 See the description of ``add_tunnel_port()``. 430 """ 431 self.add_tunnel_port(name, 'gre', remote_ip, 432 local_ip=local_ip, key=key, ofport=ofport) 433 434 def add_vxlan_port(self, name, remote_ip, 435 local_ip=None, key=None, ofport=None): 436 """ 437 Creates a VxLAN tunnel port. 438 439 See the description of ``add_tunnel_port()``. 440 """ 441 self.add_tunnel_port(name, 'vxlan', remote_ip, 442 local_ip=local_ip, key=key, ofport=ofport) 443 444 def del_port(self, port_name): 445 """ 446 Deletes a port on OVS instance. 447 448 This method is corresponding to the following ovs-vsctl command:: 449 450 $ ovs-vsctl del-port <bridge> <port> 451 """ 452 command = ovs_vsctl.VSCtlCommand('del-port', (self.br_name, port_name)) 453 self.run_command([command]) 454 455 def _get_ports(self, get_port): 456 ports = [] 457 port_names = self.get_port_name_list() 458 for name in port_names: 459 if self.get_ofport(name) < 0: 460 continue 461 port = get_port(name) 462 if port: 463 ports.append(port) 464 465 return ports 466 467 def _vifport(self, name, external_ids): 468 ofport = self.get_ofport(name) 469 return VifPort(name, ofport, external_ids['iface-id'], 470 external_ids['attached-mac'], self) 471 472 def _get_vif_port(self, name): 473 external_ids = self.db_get_map('Interface', name, 'external_ids') 474 if 'iface-id' in external_ids and 'attached-mac' in external_ids: 475 return self._vifport(name, external_ids) 476 477 def get_vif_ports(self): 478 """ Returns a VIF object for each VIF port """ 479 return self._get_ports(self._get_vif_port) 480 481 def _get_external_port(self, name): 482 # exclude vif ports 483 external_ids = self.db_get_map('Interface', name, 'external_ids') 484 if external_ids: 485 return 486 487 # exclude tunnel ports 488 options = self.db_get_map('Interface', name, 'options') 489 if 'remote_ip' in options: 490 return 491 492 ofport = self.get_ofport(name) 493 return VifPort(name, ofport, None, None, self) 494 495 def get_external_ports(self): 496 return self._get_ports(self._get_external_port) 497 498 def get_tunnel_port(self, name, tunnel_type='gre'): 499 type_ = self.db_get_val('Interface', name, 'type') 500 if type_ != tunnel_type: 501 return 502 503 options = self.db_get_map('Interface', name, 'options') 504 if 'local_ip' in options and 'remote_ip' in options: 505 ofport = self.get_ofport(name) 506 return TunnelPort(name, ofport, tunnel_type, 507 options['local_ip'], options['remote_ip']) 508 509 def get_tunnel_ports(self, tunnel_type='gre'): 510 get_tunnel_port = functools.partial(self.get_tunnel_port, 511 tunnel_type=tunnel_type) 512 return self._get_ports(get_tunnel_port) 513 514 def get_quantum_ports(self, port_name): 515 LOG.debug('port_name %s', port_name) 516 command = ovs_vsctl.VSCtlCommand( 517 'list-ifaces-verbose', 518 [dpid_lib.dpid_to_str(self.datapath_id), port_name]) 519 self.run_command([command]) 520 if command.result: 521 return command.result[0] 522 return None 523 524 def set_qos(self, port_name, type='linux-htb', max_rate=None, queues=None): 525 """ 526 Sets a Qos rule and creates Queues on the given port. 527 """ 528 queues = queues if queues else [] 529 command_qos = ovs_vsctl.VSCtlCommand( 530 'set-qos', 531 [port_name, type, max_rate]) 532 command_queue = ovs_vsctl.VSCtlCommand( 533 'set-queue', 534 [port_name, queues]) 535 self.run_command([command_qos, command_queue]) 536 if command_qos.result and command_queue.result: 537 return command_qos.result + command_queue.result 538 return None 539 540 def del_qos(self, port_name): 541 """ 542 Deletes the Qos rule on the given port. 543 """ 544 command = ovs_vsctl.VSCtlCommand( 545 'del-qos', 546 [port_name]) 547 self.run_command([command]) 548