├── README.md ├── controller ├── __init__.py ├── mpsdn_controller.py ├── network_topology.py └── switch.py ├── demo ├── README.md ├── configuration │ ├── clean.sh │ ├── configure_controller_parameters.sh │ ├── configure_multipath.sh │ ├── configure_singlepath.sh │ └── start_controller.sh ├── css │ ├── bootstrap-theme.css │ ├── bootstrap-theme.css.map │ ├── bootstrap-theme.min.css │ ├── bootstrap-theme.min.css.map │ ├── bootstrap.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ └── bootstrap.min.css.map ├── demo.py ├── demo_topology.py ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── index.html └── js │ ├── animation.js │ ├── bootstrap.min.js │ ├── jquery.min.js │ └── velocity.min.js ├── network_create.sh ├── start_controller.sh └── topologies.py /README.md: -------------------------------------------------------------------------------- 1 | # Multipath SDN Controller 2 | 3 | This SDN Controller runs on top of a multipath network and sets up optimal multipath forwarding flow tables to maximize throughput. 4 | It uses Ryu and can be tested on an emulated network such as Mininet. 5 | 6 | ## Dependencies 7 | 8 | The controller requires a modified version of OpenvSwitch in order to run. 9 | It can be found in the following repositories, togheter with information about its modifications: 10 | https://github.com/dariobanfi/ovs-multipath 11 | 12 | ## Architecture 13 | The controller therefore has three logic components: 14 | - Topology Discovery Component 15 | This component is used to discover the SDN switches connected to the controller and have knowledge of the paths between them. This can be automatically be done on L2 Topologies through the Link Layer Discovery Protocol (LLDP) but can be more complex over network-layer routing and require a manual con guration (done through REST APIs). 16 | - Multipath Routing Component 17 | It uses the network knowledge to compute multiple paths and push the resulting computation as ow rules to the SDN switches. The rules can be a simple forward or a multipath forward, which splits the ow packets over two or more routes. The controller might additionally set up packet reordering rules at a switch that 18 | - Network Measurement Component 19 | This component is used to do real-time measurements of the network. The con- troller keeps an estimate of the latency and bandwidth of the multiple paths that connect the SDN switches of the multipath topology. This data is used by the multipath routing component to compute forwarding tables which maximize the throughput between the nodes. 20 | 21 | ## Demo 22 | Check out a visualization of the network benefits over here: 23 | [https://www.youtube.com/watch?v=hkgf7l9Lshw&feature=youtu.be](https://www.youtube.com/watch?v=hkgf7l9Lshw&feature=youtu.be) 24 | 25 | To run the demo make sure to install the dependencies with pip (like `bottle`, `ryu`, etc) and to provide a video file to be streamed 26 | -------------------------------------------------------------------------------- /controller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dariobanfi/multipath-sdn-controller/130cb6562525bb10351753eb5b7a7101f5e47ff1/controller/__init__.py -------------------------------------------------------------------------------- /controller/mpsdn_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from ryu.base import app_manager 4 | from ryu.controller import ofp_event 5 | from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER 6 | from ryu.controller.handler import set_ev_cls 7 | from ryu.ofproto import ofproto_v1_3 8 | from ryu.lib import hub 9 | from ryu.topology import event 10 | from network_topology import NetworkTopology 11 | from ryu.topology.switches import LLDPPacket 12 | from ryu.lib.packet import (packet, ethernet, arp, icmp, icmpv6, ipv4, ipv6) 13 | from ryu.app.wsgi import ControllerBase, WSGIApplication, route 14 | from webob import Response 15 | import json 16 | import time 17 | import traceback 18 | 19 | ''' 20 | MPSDN Network Controller for a L2 network 21 | ''' 22 | 23 | __author__ = 'Dario Banfi' 24 | __license__ = 'Apache 2.0' 25 | __version__ = '1.0' 26 | __email__ = 'dario.banfi@tum.de' 27 | 28 | 29 | API_INSTANCE_NAME = 'mp_controller_api_app' 30 | 31 | 32 | class MultipathController(app_manager.RyuApp): 33 | OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] 34 | _CONTEXTS = {'wsgi': WSGIApplication} 35 | 36 | def __init__(self, *args, **kwargs): 37 | super(MultipathController, self).__init__(*args, **kwargs) 38 | 39 | # Random ethertype to evaluate latency 40 | self.PROBE_ETHERTYPE = 0x07C7 41 | 42 | # Set to true once there is enoough informatio 43 | # to start multipath computation 44 | self.network_is_measured = False 45 | 46 | # Maximum delay imbalance to use a reordering buffer 47 | # Example 25ms,50ms = |(25/75)-0.5| = 0.16 48 | self.MDI_REORDERING_THRESHOLD = 0.2 49 | 50 | # Maximum delay imbalance threshold for not using multipath 51 | self.MDI_DROP_THRESHOLD = 0.25 52 | 53 | # If this value is changed in the configuration, the path setup 54 | # algorithm is halted if a path has more hops to the destination 55 | # than this limit. 56 | # If left -1, MDI_DROP will be used instead! 57 | # It is suited for L2 Networks where link delays will be 58 | # very similar 59 | self.MAX_HOP_DIFFERENCE = -1 60 | 61 | # Minimum available capacity a path needs to be used in B/s 62 | self.MIN_MULTIPATH_CAPACITY = 100 63 | 64 | # Maximum paths allowed for a multipath flow 65 | self.MAX_PATHS_PER_MULTIPATH_FLOW = 2 66 | 67 | # Recalculates bucket only on addition or failures in the topology 68 | self.UPDATE_FORWARDING_ON_TOPOLOGY_CHANGE_ONLY = False 69 | 70 | # Recomputes forwarding continuously reardless of congestion/failures 71 | self.UPDATE_FORWARDING_CONTINOUSLY = False 72 | 73 | # High priority 74 | self.PRIORITY_PROBE_PACKETS = 65000 75 | 76 | # Monitoring frequency for port stats. 77 | self.MONITORING_PORT_STATS_FREQUENCY = 5 78 | 79 | self.MONITORING_PORT_STATS = False 80 | 81 | # Used for REST APIs 82 | wsgi = kwargs['wsgi'] 83 | wsgi.register(MultipathRestController, {API_INSTANCE_NAME: self}) 84 | 85 | # Holds the topology data and structure 86 | self.topo_shape = NetworkTopology(self) 87 | 88 | ########################################## 89 | # UTILITY FUNCTIONS # 90 | ########################################## 91 | 92 | def run(self): 93 | ''' 94 | Called to start the monitoring/computation in the controller 95 | It can be called with a GET to the API: 96 | /multipath/start_path_computation 97 | ''' 98 | 99 | # Network monitor module 100 | self.monitoringhub = hub.spawn(self.network_monitor) 101 | 102 | # Multipath computation module 103 | hub.spawn_after(5, self.multipath_computation) 104 | 105 | def add_flow(self, datapath, priority, match, actions, buffer_id=None): 106 | ''' Adds a flow to a datapath ''' 107 | ofproto = datapath.ofproto 108 | parser = datapath.ofproto_parser 109 | 110 | inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, 111 | actions)] 112 | if buffer_id: 113 | mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id, 114 | priority=priority, match=match, 115 | instructions=inst) 116 | else: 117 | mod = parser.OFPFlowMod(datapath=datapath, priority=priority, 118 | match=match, instructions=inst) 119 | datapath.send_msg(mod) 120 | 121 | def _send_packet(self, datapath, port, pkt): 122 | ''' Instructs a datapath to output a packet to one of his ports ''' 123 | ofproto = datapath.ofproto 124 | parser = datapath.ofproto_parser 125 | pkt.serialize() 126 | self.logger.debug('packet-out %s' % (pkt,)) 127 | data = pkt.data 128 | actions = [parser.OFPActionOutput(port=port)] 129 | out = parser.OFPPacketOut(datapath=datapath, 130 | buffer_id=ofproto.OFP_NO_BUFFER, 131 | in_port=ofproto.OFPP_CONTROLLER, 132 | actions=actions, 133 | data=data) 134 | datapath.send_msg(out) 135 | 136 | def set_edge_ports(self): 137 | # Setting the edge port [port connected to host networks] 138 | # of a switch to be the highest in number 139 | # (In mininet it corresponds to the host port when they are 140 | # added at the end of the topology creation) 141 | for dp_id, switch in self.topo_shape.dpid_to_switch.iteritems(): 142 | switch.edge_port = len(switch.ports.keys()) 143 | 144 | def multipath_computation(self): 145 | while True: 146 | if not self.topo_shape.is_empty() and self.network_is_measured: 147 | computation_start = time.time() 148 | self.logger.info('Starting multipath computation sub-routine') 149 | self.topo_shape.multipath_computation() 150 | self.logger.info( 151 | 'Multipath computation finished in %f seconds', 152 | time.time() - computation_start 153 | ) 154 | 155 | self.MONITORING_PORT_STATS = True 156 | if self.UPDATE_FORWARDING_CONTINOUSLY: 157 | hub.sleep(10) 158 | else: 159 | break 160 | 161 | def add_default_flows(self, datapath): 162 | ''' 163 | Adds default unknown flows to the controller 164 | ''' 165 | 166 | ofproto = datapath.ofproto 167 | parser = datapath.ofproto_parser 168 | match = parser.OFPMatch() 169 | actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER)] 170 | self.add_flow(datapath, 1, match, actions) 171 | 172 | # Installing the flow rules to send latency probe packets 173 | match = parser.OFPMatch(eth_type=self.PROBE_ETHERTYPE) 174 | actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER)] 175 | self.add_flow(datapath, self.PRIORITY_PROBE_PACKETS, match, actions) 176 | 177 | def add_default_for_all(self): 178 | for dpid, s in self.topo_shape.dpid_to_switch: 179 | self.add_default_flows(s.dp) 180 | 181 | def stop_monitoring(self): 182 | self.keep_monitoring = False 183 | 184 | 185 | 186 | ########################################## 187 | # NETWORK DISCOVERY HANDLERS # 188 | ########################################## 189 | 190 | @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) 191 | def switch_features_handler(self, ev): 192 | ''' 193 | Switch features handler callback 194 | ''' 195 | self.logger.debug('EventOFPSwitchFeatures') 196 | self.add_default_flows(ev.msg.datapath) 197 | 198 | 199 | @set_ev_cls(event.EventSwitchEnter, MAIN_DISPATCHER) 200 | def switch_enter_handler(self, event): 201 | self.logger.info('EventSwitchEnter') 202 | self.topo_shape.add_switch(event.switch) 203 | 204 | @set_ev_cls(event.EventSwitchLeave, MAIN_DISPATCHER) 205 | def switch_leave_handler(self, event): 206 | self.logger.debug('EventSwitchLeave') 207 | 208 | # Redundant for Mininet emulated network since 209 | # you cannot remove switches/link once the topology 210 | # has started 211 | 212 | # self.topo_shape.remove_switch(event.switch) 213 | 214 | @set_ev_cls(event.EventLinkAdd, MAIN_DISPATCHER) 215 | def link_add_handler(self, event): 216 | self.logger.info('EventLinkAdd') 217 | self.topo_shape.add_link(event) 218 | 219 | @set_ev_cls(event.EventLinkDelete, MAIN_DISPATCHER) 220 | def link_delete_handler(self, event): 221 | self.logger.debug('EventLinkDelete %s' % event) 222 | # Redundant for Mininet emulated network since 223 | # you cannot remove switches/link once the topology 224 | # has started 225 | 226 | # self.topo_shape.remove_link(event) 227 | 228 | @set_ev_cls(event.EventPortAdd, MAIN_DISPATCHER) 229 | def port_add_handler(self, event): 230 | self.logger.info('EventPortAdd') 231 | self.topo_shape.add_port(event.port) 232 | 233 | @set_ev_cls(event.EventPortDelete, MAIN_DISPATCHER) 234 | def port_delete_handler(self, event): 235 | self.logger.debug('EventPortDelete') 236 | 237 | # Redundant for Mininet emulated network since 238 | # you cannot remove switches/link once the topology 239 | # has started 240 | 241 | # self.topo_shape.remove_port(event.port) 242 | 243 | ########################################## 244 | # NETWORK MONITORING # 245 | ########################################## 246 | 247 | def network_monitor(self): 248 | ''' Monitors network RTT and Congestion ''' 249 | 250 | self.logger.info('Starting monitoring sub-routine') 251 | # First, we get an estimation of the link benchmark_network_capacity 252 | # in a state where the network will be idle. 253 | self.benchmark_network_capacity() 254 | 255 | self.keep_monitoring = True 256 | # Then we start the periodic measurement of RTT times and port 257 | # utilization 258 | 259 | counter = 0 260 | while self.keep_monitoring: 261 | if not self.topo_shape.is_empty(): 262 | self.logger.debug('Requesting port stats to ' 263 | 'measure utilization') 264 | self.logger.info('\n------------------') 265 | for dpid, s in self.topo_shape.dpid_to_switch.iteritems(): 266 | 267 | s.port_stats_request_time = time.time() 268 | 269 | # Requesting portstats to calculate controller 270 | # to switch delay and congeston 271 | # if self.MONITORING_PORT_STATS: 272 | self._request_port_stats(s) 273 | 274 | # Calculating peering switches RTT (once every 10 portstats 275 | # so ~10 secs) 276 | if counter % 30 == 0: 277 | self.send_latency_probe_packet(s) 278 | 279 | counter += 1 280 | 281 | hub.sleep(self.MONITORING_PORT_STATS_FREQUENCY) 282 | 283 | self.logger.info('Stopping monitor') 284 | 285 | def benchmark_network_capacity(self): 286 | ''' 287 | This mechanism is left for future work. 288 | It can send a high rate UDP from hosts to destination to and measure 289 | on the receiving switch the drop rate of the packets, to infer the link 290 | capacity 291 | Currently the max capacity is set through REST APIs 292 | ''' 293 | 294 | pass 295 | 296 | def send_latency_probe_packet(self, switch): 297 | ''' 298 | Injects latency probe packets in the network 299 | ''' 300 | self.logger.info('Injecting latency probe packets') 301 | 302 | for peer_switch, peer_port in switch.peer_to_local_port.iteritems(): 303 | 304 | self.logger.debug('Sending probe packet from %s to %s through ' 305 | ' port %s', 306 | switch, peer_switch, peer_port 307 | ) 308 | 309 | actions = [switch.dp.ofproto_parser.OFPActionOutput(peer_port)] 310 | 311 | pkt = packet.Packet() 312 | pkt.add_protocol(ethernet.ethernet(ethertype=self.PROBE_ETHERTYPE, 313 | dst=0x000000000001, 314 | src=0x000000000000) 315 | ) 316 | 317 | pkt.serialize() 318 | payload = '%d;%d;%f' % ( 319 | switch.dp.id, peer_switch.dp.id, time.time()) 320 | data = pkt.data + payload 321 | 322 | out = switch.dp.ofproto_parser.OFPPacketOut( 323 | datapath=switch.dp, 324 | buffer_id=switch.dp.ofproto.OFP_NO_BUFFER, 325 | data=data, 326 | in_port=switch.dp.ofproto.OFPP_CONTROLLER, 327 | actions=actions 328 | ) 329 | 330 | switch.dp.send_msg(out) 331 | 332 | def probe_packet_handler(self, pkt): 333 | ''' 334 | Handles a latency probe packets and computes the 335 | delay between two switches 336 | ''' 337 | try: 338 | receive_time = time.time() 339 | # Ignoring 14 bytes of ethernet header 340 | data = pkt.data[14:].split(';') 341 | send_dpid = int(data[0]) 342 | recv_dpid = int(data[1]) 343 | inc_time = float(data[2]) 344 | sample_delay = receive_time - inc_time 345 | self.topo_shape.dpid_to_switch[send_dpid].calculate_delay_to_peer( 346 | self.topo_shape.dpid_to_switch[recv_dpid], sample_delay) 347 | self.network_is_measured = True 348 | except: 349 | traceback.print_exc() 350 | self.logger.error('Unable to parse incoming probe packet') 351 | 352 | @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) 353 | def _port_stats_reply_handler(self, ev): 354 | ''' 355 | Handles a PORT STATS response from a switch 356 | ''' 357 | 358 | ports = ev.msg.body 359 | port_stats_reply_time = time.time() 360 | 361 | # Calculate switch-controller RTT 362 | switch = self.topo_shape.dpid_to_switch[ev.msg.datapath.id] 363 | switch.calculate_delay_to_controller(port_stats_reply_time) 364 | 365 | sorted_port_table = sorted(ports, key=lambda l: l.port_no) 366 | for stat in sorted_port_table: 367 | if stat.port_no not in switch.ports: 368 | continue 369 | port = switch.ports[stat.port_no] 370 | utilization = stat.tx_bytes + stat.rx_bytes 371 | 372 | if port.last_request_time: 373 | timedelta = port_stats_reply_time - port.last_request_time 374 | datadelta = utilization - port.last_utilization_value 375 | 376 | utilization_bps = datadelta / timedelta 377 | port.capacity = port.max_capacity - utilization_bps 378 | 379 | self.logger.info( 380 | 's[%s] p[%d] utilization %2.f max_capacity %s', 381 | switch.dp.id, stat.port_no, 382 | utilization_bps, 383 | port.max_capacity 384 | ) 385 | port.last_request_time = port_stats_reply_time 386 | port.last_utilization_value = utilization 387 | 388 | @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) 389 | def _flow_stats_reply_handler(self, ev): 390 | ''' 391 | Handles a FLOW STATS reply 392 | ''' 393 | 394 | self.logger.debug( 395 | 'Receive flow stats response from: %016x at t: %f', 396 | ev.msg.datapath.id, 397 | time.time() 398 | ) 399 | 400 | body = ev.msg.body 401 | 402 | self.logger.debug('datapath ' 403 | 'in-port eth-dst ' 404 | 'out-port packets bytes') 405 | 406 | self.logger.debug('---------------- ' 407 | '-------- ----------------- ' 408 | '-------- -------- --------') 409 | 410 | self.logger.debug(ev.msg.body) 411 | for stat in sorted( 412 | [flow for flow in body if flow.priority == 1], 413 | key=lambda flow: (flow.match['in_port'], flow.match['eth_dst']) 414 | ): 415 | self.logger.info( 416 | '%016x %8x %17s %8x %8d %8d', 417 | ev.msg.datapath.id, 418 | stat.match['in_port'], stat.match['eth_dst'], 419 | stat.instructions[0].actions[0].port, 420 | stat.packet_count, stat.byte_count 421 | ) 422 | 423 | def _request_flow_stats(self, switch): 424 | ''' 425 | Requests flow stats for a switch 426 | ''' 427 | self.logger.debug( 428 | 'Request flow stats for: %016x at t: %f', 429 | switch.dp.id, time.time() 430 | ) 431 | parser = switch.dp.ofproto_parser 432 | req = parser.OFPFlowStatsRequest(switch.dp) 433 | switch.dp.send_msg(req) 434 | 435 | def _request_port_stats(self, switch): 436 | ''' 437 | Request port statistic to a switch 438 | ''' 439 | self.logger.debug( 440 | 'Request port stats for dp %s at t: %f', 441 | switch.dp.id, time.time() 442 | ) 443 | ofproto = switch.dp.ofproto 444 | parser = switch.dp.ofproto_parser 445 | req = parser.OFPPortStatsRequest(switch.dp, 0, ofproto.OFPP_ANY) 446 | switch.dp.send_msg(req) 447 | 448 | ########################################## 449 | # PACKET IN HANDLER # 450 | ########################################## 451 | 452 | @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) 453 | def _packet_in_handler(self, ev): 454 | msg = ev.msg 455 | pkt = packet.Packet(msg.data) 456 | 457 | pkt_ethernet = pkt.get_protocols(ethernet.ethernet)[0] 458 | datapath = msg.datapath 459 | port = msg.match['in_port'] 460 | 461 | # Checking if it's the probe packet for RTT estimation 462 | if pkt_ethernet.ethertype == self.PROBE_ETHERTYPE: 463 | self.probe_packet_handler(pkt) 464 | return 465 | 466 | # Ignoring LLDP Packets 467 | try: 468 | LLDPPacket.lldp_parse(msg.data) 469 | self.logger.debug('Received LLDP Packet') 470 | return 471 | except: 472 | pass 473 | 474 | self.logger.debug('EventOFPPacketIn %s' % pkt) 475 | 476 | # Handling ARP 477 | pkt_arp = pkt.get_protocol(arp.arp) 478 | if pkt_arp: 479 | self._handle_arp(msg, datapath, port, pkt, pkt_arp) 480 | return 481 | 482 | pkt_ipv4 = pkt.get_protocol(ipv4.ipv4) 483 | pkt_icmp = pkt.get_protocol(icmp.icmp) 484 | 485 | # Handling ICMPv4 486 | if pkt_icmp: 487 | self._handle_icmpv4( 488 | msg, datapath, port, pkt_ethernet, pkt_ipv4, pkt_icmp) 489 | return 490 | 491 | # Handing IPv4 492 | if pkt_ipv4: 493 | self._handle_ipv4(datapath, port, pkt_ethernet, pkt_ipv4) 494 | return 495 | 496 | pkt_ipv6 = pkt.get_protocol(ipv6.ipv6) 497 | pkt_icmp = pkt.get_protocol(icmpv6.icmpv6) 498 | 499 | # Handling ICMPv6 500 | if pkt_icmp: 501 | self._handle_icmpv6( 502 | datapath, port, pkt_ethernet, pkt_ipv6, pkt_icmp) 503 | return 504 | 505 | # Handing IPv6 506 | if pkt_ipv4: 507 | self._handle_ipv6(datapath, port, pkt_ethernet, pkt_ipv4) 508 | return 509 | 510 | # Unhandled 511 | self.logger.debug('Unknown packet %s' % str(pkt)) 512 | 513 | def _handle_arp(self, msg, datapath, in_port_no, pkt, pkt_arp): 514 | 515 | self.logger.debug('ARP Packet %s' % pkt_arp) 516 | 517 | def _handle_icmpv4(self, msg, datapath, in_port_no, pkt_ethernet, pkt_ipv4, 518 | pkt_icmp): 519 | 520 | self.logger.debug('Handling ICMP packet %s', pkt_icmp) 521 | 522 | def _handle_ipv4(self, datapath, port, pkt_ethernet, pkt_ipv4): 523 | self.logger.debug( 524 | 'Handling ip packet from port %d - %s' % (port, pkt_ipv4) 525 | ) 526 | 527 | def _handle_ipv6(self, datapath, port, pkt_ethernet, pkt_ipv6): 528 | self.logger.debug('Handling ipv6 packet') 529 | 530 | def _handle_icmpv6(self, datapath, port, pkt_ethernet, pkt_ipv6, pkt_icmp): 531 | self.logger.debug('Handling ICMPv6 Packet') 532 | 533 | 534 | class MultipathRestController(ControllerBase): 535 | 536 | def __init__(self, req, link, data, **config): 537 | super(MultipathRestController, self).__init__( 538 | req, link, data, **config) 539 | self.mp_instance = data[API_INSTANCE_NAME] 540 | 541 | @route('multipath', 542 | '/multipath/set_port_weight/{dp_id}/{port_no}/{weight}', 543 | methods=['GET']) 544 | def set_port_weight(self, req, **kwargs): 545 | multipath_controller = self.mp_instance 546 | 547 | try: 548 | dp_id = int(kwargs['dp_id']) 549 | port_no = int(kwargs['port_no']) 550 | weight = int(kwargs['weight']) 551 | multipath_controller.topo_shape.dpid_to_switch[ 552 | dp_id].ports[port_no].set_max_capacity(weight) 553 | except: 554 | traceback.print_exc() 555 | return Response(content_type='text/html', body='Failure!\n') 556 | 557 | return Response(content_type='text/html', body='Success !\n') 558 | 559 | @route('multipath', 560 | '/multipath/set_edge_port/{dp_id}/{port_no}', 561 | methods=['GET']) 562 | def set_edge_port(self, req, **kwargs): 563 | multipath_controller = self.mp_instance 564 | 565 | try: 566 | dp_id = int(kwargs['dp_id']) 567 | port_no = int(kwargs['port_no']) 568 | multipath_controller.topo_shape.dpid_to_switch[ 569 | dp_id].edge_port = port_no 570 | except: 571 | traceback.print_exc() 572 | return Response(content_type='text/html', body='Failure!\n') 573 | 574 | return Response(content_type='text/html', body='Success !\n') 575 | 576 | @route('multipath', '/multipath/set_ip_network/{dp_id}/{ip}/{netmask}', 577 | methods=['GET']) 578 | def set_ip_network(self, req, **kwargs): 579 | multipath_controller = self.mp_instance 580 | try: 581 | dp_id = int(kwargs['dp_id']) 582 | ip_net = kwargs['ip'] 583 | netmask = kwargs['netmask'] 584 | multipath_controller.topo_shape.dpid_to_switch[ 585 | dp_id].ip_network = ip_net 586 | multipath_controller.topo_shape.dpid_to_switch[ 587 | dp_id].ip_netmask = netmask 588 | except: 589 | traceback.print_exc() 590 | return Response(content_type='text/html', body='Failure!\n') 591 | 592 | return Response(content_type='text/html', body='Success !\n') 593 | 594 | @route('multipath', '/multipath/start_path_computation', methods=['GET']) 595 | def start_path_computation(self, req, **kwargs): 596 | multipath_controller = self.mp_instance 597 | 598 | multipath_controller.run() 599 | 600 | return Response(content_type='text/html', 601 | body='Path computation started!\n') 602 | 603 | @route('multipath', '/multipath/recompute_multipath', methods=['GET']) 604 | def start_path_recomputation(self, req, **kwargs): 605 | multipath_controller = self.mp_instance 606 | 607 | hub.spawn_after( 608 | 1, 609 | multipath_controller.multipath_computation 610 | ) 611 | 612 | 613 | return Response(content_type='text/html', 614 | body='Path recomputatation started!\n') 615 | 616 | @route('multipath', '/multipath/configuration', methods=['POST']) 617 | def configure_controller_parameters(self, req, **kwargs): 618 | multipath_controller = self.mp_instance 619 | 620 | config = json.loads(req.body) 621 | 622 | if 'max_hop_difference' in config.keys(): 623 | multipath_controller.MAX_HOP_DIFFERENCE = config['max_hop_difference'] 624 | if 'mdi_reordering' in config.keys(): 625 | multipath_controller.MDI_REORDERING_THRESHOLD = float( 626 | config['mdi_reordering'] 627 | ) 628 | if 'mdi_drop' in config.keys(): 629 | multipath_controller.MDI_DROP_THRESHOLD = float( 630 | config['mdi_drop'] 631 | ) 632 | if 'max_paths' in config.keys(): 633 | multipath_controller.MAX_PATHS_PER_MULTIPATH_FLOW = int( 634 | config['max_paths'] 635 | ) 636 | if 'min_multipath_capacity' in config.keys(): 637 | multipath_controller.MIN_MULTIPATH_CAPACITY = int( 638 | config['min_multipath_capacity'] 639 | ) 640 | 641 | if 'monitoring_frequency_seconds' in config.keys(): 642 | multipath_controller.MONITORING_PORT_STATS_FREQUENCY = int( 643 | config['monitoring_frequency_seconds'] 644 | ) 645 | 646 | return Response(content_type='text/html', 647 | body='Configuration accepted\n') 648 | 649 | @route('multipath', 650 | '/multipath/change_bucket_weight/{dp_id}/{group_id}/{rules}', 651 | methods=['GET']) 652 | def change_bucket_weight(self, req, **kwargs): 653 | ''' 654 | Changes bucket weight for a datapath's GROUP 655 | Rules are passed in this format port,weight; : 656 | Example : '1,1;2,1;3,2;4,2' 657 | ''' 658 | multipath_controller = self.mp_instance 659 | 660 | try: 661 | dp_id = kwargs['dp_id'] 662 | group_id = kwargs['group_id'] 663 | arg_rules = kwargs['rules'] 664 | rules = {} 665 | for i in arg_rules.split(';'): 666 | rules[int(i.split(',')[0])] = int(i.split(',')[1]) 667 | 668 | multipath_controller.topo_shape.modify_group( 669 | multipath_controller.topo_shape.dpid_to_switch[ 670 | int(dp_id)].dp, int(group_id), rules) 671 | except: 672 | traceback.print_exc() 673 | 674 | return Response(content_type='text/html', body='Done!\n') 675 | -------------------------------------------------------------------------------- /controller/network_topology.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from itertools import tee, islice, chain, izip 4 | from switch import Port, Switch 5 | import logging 6 | import time 7 | import random 8 | import itertools 9 | 10 | __author__ = 'Dario Banfi' 11 | __license__ = 'Apache 2.0' 12 | __version__ = '1.0' 13 | __email__ = 'dario.banfi@tum.de' 14 | 15 | ''' 16 | Representation of the network topology knowledge mantained 17 | by the Controller and algorithms necessary to compute and 18 | set up multipath forwarding flow rules 19 | ''' 20 | 21 | logger = logging.getLogger(__name__) 22 | 23 | 24 | class NetworkTopology(): 25 | 26 | def __init__(self, controller, *args, **kwargs): 27 | 28 | self.controller = controller 29 | 30 | # Datapath id - Switch dict 31 | self.dpid_to_switch = {} 32 | 33 | # Dictionary containing the output of the controller 34 | # path computation algorithm 35 | self.mp_config = {} 36 | 37 | # Path finding algorithm used inside the max-flow to find 38 | # forwarding paths 39 | self.pathfindinding_algo = Dijkstra( 40 | self.dpid_to_switch, 41 | controller.MIN_MULTIPATH_CAPACITY, 42 | controller.UPDATE_FORWARDING_ON_TOPOLOGY_CHANGE_ONLY, 43 | ) 44 | 45 | # Dictionary containg multipath group id for a specific tuple 46 | # node, src, dst, in_port 47 | self.multipath_group_ids = {} 48 | 49 | # Group id numbers already in use 50 | self.group_ids = [] 51 | 52 | # Default priority for output rules and splitting groups 53 | self.PRIORITY_DEFAULT = 32768 54 | 55 | # A bit higher priority for the reordering one so that 56 | self.PRIORITY_REORDERING = 32800 57 | 58 | # Maximum paths allowed for a multipath flow 59 | controller.MAX_PATHS_PER_MULTIPATH_FLOW = 2 60 | 61 | # Group number for reordering 62 | self.OPENFLOW_GROUP_REORDERING = 4 63 | 64 | def is_empty(self): 65 | return len(self.dpid_to_switch) == 0 66 | 67 | ########################################## 68 | # TOPOLOGY CREATION # 69 | ########################################## 70 | 71 | def add_switch(self, switch): 72 | ''' 73 | Adds a switch to the topology 74 | ''' 75 | if switch.dp.id not in self.dpid_to_switch: 76 | s = Switch(switch.dp) 77 | self.dpid_to_switch[switch.dp.id] = s 78 | for port in switch.ports: 79 | self.add_port(port) 80 | self.pathfindinding_algo.topology_last_update = time.time() 81 | 82 | def remove_switch(self, switch): 83 | ''' 84 | Removes a switch to the topology 85 | ''' 86 | if switch and switch.dp.id in self.dpid_to_switch: 87 | del self.dpid_to_switch[switch.dp.id] 88 | self.pathfindinding_algo.topology_last_update = time.time() 89 | 90 | def add_port(self, port): 91 | ''' 92 | Adds a port to the topology 93 | ''' 94 | port = Port(port) 95 | switch = self.dpid_to_switch[port.dpid] 96 | switch.ports[port.port_no] = port 97 | self.pathfindinding_algo.topology_last_update = time.time() 98 | 99 | def remove_port(self, port): 100 | ''' 101 | Removes a switch to the topology 102 | ''' 103 | port = Port(port) 104 | try: 105 | switch = self.dpid_to_switch[port.dpid] 106 | del switch.ports[port.port_no] 107 | self.pathfindinding_algo.topology_last_update = time.time() 108 | except KeyError: 109 | return 110 | 111 | def _update_port_link(self, dpid, port): 112 | ''' 113 | Updates a port 114 | ''' 115 | switch = self.dpid_to_switch[dpid] 116 | p = switch.ports.get(port.port_no, None) 117 | if p: 118 | p.peer_switch_dpid = port.peer_switch_dpid 119 | p.peer_port_no = port.peer_port_no 120 | p.is_edge = False 121 | else: 122 | switch.ports[port.port_no] = port 123 | 124 | peer_switch = self.dpid_to_switch[port.peer_switch_dpid] 125 | switch.peer_to_local_port[peer_switch] = port.port_no 126 | 127 | def add_link(self, event): 128 | ''' 129 | Adds a link from port to to port 130 | ''' 131 | src_port = Port( 132 | port=event.link.src, peer=event.link.dst, is_edge=False) 133 | dst_port = Port( 134 | port=event.link.dst, peer=event.link.src, is_edge=False) 135 | self._update_port_link(src_port.dpid, src_port) 136 | self._update_port_link(dst_port.dpid, dst_port) 137 | self.pathfindinding_algo.topology_last_update = time.time() 138 | 139 | def __remove_link(self, port): 140 | if port.dpid in self.dpid_to_switch: 141 | switch = self.dpid_to_switch[port.dpid] 142 | p = switch.ports[port.port_no] 143 | p.peer_switch_dpid = None 144 | p.peer_port_no = None 145 | 146 | def remove_link(self, event): 147 | ''' 148 | Removes a link from a port 149 | ''' 150 | try: 151 | switch_1 = self.dpid_to_switch[event.link.src.dpid] 152 | switch_2 = self.dpid_to_switch[event.link6.dst.dpid] 153 | del switch_1.peer_to_local_port[switch_2] 154 | del switch_2.peer_to_local_port[switch_1] 155 | 156 | except KeyError: 157 | return 158 | 159 | self.__remove_link(event.link.src) 160 | self.__remove_link(event.link.dst) 161 | self.pathfindinding_algo.topology_last_update = time.time() 162 | 163 | ########################################## 164 | # PATH SETUP # 165 | ########################################## 166 | 167 | def multipath_computation(self): 168 | edges = [] 169 | self.mp_config = {} 170 | 171 | for dpid, switch in self.dpid_to_switch.iteritems(): 172 | # Updating the capacity_maxflow variable which will be 173 | # modified by the algorithm with the realtime monitored capacity 174 | for port_no, port in switch.ports.iteritems(): 175 | port.capacity_maxflow = port.capacity 176 | # Adding the edge switches to a list 177 | if switch.edge_port: 178 | edges.append(switch) 179 | 180 | # Calculate forwarding paths between all edges couples 181 | logger.info('%s', self.dpid_to_switch) 182 | for edge_couple in itertools.permutations(edges, 2): 183 | self.calculate_multipath(edge_couple[0], edge_couple[1]) 184 | self.create_flow_rules(edge_couple[0], edge_couple[1]) 185 | logger.info('-' * 20) 186 | 187 | def restore_capacities(self): 188 | ''' 189 | Restores max_flow capacities modified by the algorithm 190 | ''' 191 | for dpid, switch in self.dpid_to_switch.iteritems(): 192 | for port_no, port in switch.ports.iteritems(): 193 | port.restore_capacity() 194 | 195 | def calculate_multipath(self, src, dst): 196 | 197 | paths = [] 198 | previous_path = None 199 | 200 | while True: 201 | shortest_path = self.pathfindinding_algo.find_route(src, dst) 202 | if shortest_path and previous_path is None: 203 | previous_path = shortest_path 204 | 205 | # Limiting the number of paths in a multipath flow 206 | path_limit_reached = len(paths) + 1 > self.controller.MAX_PATHS_PER_MULTIPATH_FLOW 207 | 208 | path_delays = [] 209 | 210 | if shortest_path is None: 211 | logger.info('Path computation Algorithm terminates ' 212 | '- NO MORE PATHS') 213 | break 214 | else: 215 | path_delays.append(shortest_path.latency()) 216 | path_delays.append(previous_path.latency()) 217 | 218 | # Stopping algorithm on MAX_HOP_DIFFERENCE 219 | if self.controller.MAX_HOP_DIFFERENCE != -1 and (shortest_path.length() - previous_path.length()) > self.controller.MAX_HOP_DIFFERENCE: 220 | logger.info('Path computation Algorithm terminates ' 221 | ' - MAX HOP REACHED') 222 | break 223 | 224 | # Stopping algorithm on MDI_DROP_THRESHOLD 225 | if (self.mdi(path_delays) > self.controller.MDI_DROP_THRESHOLD): 226 | logger.info('Path computation Algorithm ' 227 | 'terminates - MDI_DROP_THRESHOLD REACHED') 228 | break 229 | if path_limit_reached is None: 230 | logger.info('Path computation Algorithm terminates ' 231 | '- PATH LIMIT REACHED') 232 | break 233 | 234 | paths.append(shortest_path) 235 | 236 | capacity = shortest_path.capacity() 237 | latency = shortest_path.latency() 238 | logger.info('Computed path %s capacity %f latency %f' % 239 | (shortest_path, capacity, latency)) 240 | self.save_path(src, dst, shortest_path, capacity, latency) 241 | shortest_path.decrease_capacity(capacity) 242 | 243 | self.restore_capacities() 244 | 245 | def save_path(self, src, dst, path, capacity, latency): 246 | for previous_node, node, next_node in path.iter_previous_and_next(): 247 | if node == src: 248 | input_intf = node.edge_port 249 | else: 250 | input_intf = node.peer_to_local_port[previous_node] 251 | 252 | if node == dst: 253 | output_intf = node.edge_port 254 | else: 255 | output_intf = node.peer_to_local_port[next_node] 256 | 257 | if (dst, src, node, input_intf, output_intf) in self.mp_config: 258 | self.mp_config[ 259 | dst, src, node, input_intf, output_intf 260 | ] += (capacity, latency) 261 | else: 262 | self.mp_config[dst, src, node, input_intf, output_intf] = ( 263 | capacity, latency) 264 | 265 | def generate_openflow_gid(self): 266 | ''' 267 | Returns a random OpenFlow group id 268 | ''' 269 | n = random.randint(0, 2**32) 270 | while n in self.group_ids: 271 | n = random.randint(0, 2**32) # lol 272 | return n 273 | 274 | def mdi(self, rules): 275 | ''' 276 | The delay imbalance metric checks all the path delays and 277 | computes the maximum inbalance between them: 278 | Eg : [50,50,50] 3 paths with the same delay, so the delay 279 | imbalance will be 0 280 | Eg : [50,60,300] 3 paths and different delays, the value will be 0.35 281 | 282 | The value ranges from [0 to 0.5] where 0 is no imbalance 283 | ''' 284 | max_delay_imbalance = 0 285 | for x, y in itertools.combinations(rules, 2): 286 | try: 287 | max_delay_imbalance = max( 288 | max_delay_imbalance, abs((x/(x+y))-0.5) 289 | ) 290 | except ZeroDivisionError: 291 | max_delay_imbalance = 0 292 | 293 | logger.info( 294 | 'Max delay imbalance for %s is %f', 295 | rules, 296 | max_delay_imbalance 297 | ) 298 | 299 | return max_delay_imbalance 300 | 301 | def create_flow_rules(self, src, dst): 302 | ''' 303 | Creates flow rules from a src to a dst switch 304 | ''' 305 | 306 | # Do the magic 307 | mp_dict = {} 308 | for k, v in self.mp_config.iteritems(): 309 | reduce( 310 | lambda a, b: a.setdefault(b, {}), k[:-1], mp_dict)[k[-1]] = v 311 | 312 | if not mp_dict: 313 | return 314 | 315 | for node in mp_dict[dst][src]: 316 | 317 | # The switch has multiple paths converging 318 | max_delay_imbalance = -1 319 | in_latencies = [] 320 | in_ports = 0 321 | 322 | for in_port in mp_dict[dst][src][node]: 323 | in_ports += 1 324 | out_rules = {} 325 | out_total_capacity = 0 326 | out_total_latency = 0 327 | 328 | for out_port in mp_dict[dst][src][node][in_port]: 329 | capacity = mp_dict[dst][src][node][in_port][out_port][0] 330 | latency = mp_dict[dst][src][node][in_port][out_port][1] 331 | out_rules[out_port] = (capacity, latency) 332 | out_total_capacity += capacity 333 | out_total_latency += latency 334 | in_latencies.append(latency) 335 | 336 | in_latencies.append(latency) 337 | 338 | group_id = None 339 | group_new = False 340 | 341 | # The switch is splitting traffic in multipath 342 | if len(out_rules) > 1: 343 | only_delays = [x[1][1] for x in out_rules.items()] 344 | max_delay_imbalance = self.mdi( 345 | only_delays 346 | ) 347 | 348 | if (node, src, dst, in_port) not in self.multipath_group_ids: 349 | group_new = True 350 | self.multipath_group_ids[ 351 | node, src, dst, in_port 352 | ] = self.generate_openflow_gid() 353 | 354 | group_id = self.multipath_group_ids[ 355 | node, src, dst, in_port 356 | ] 357 | 358 | # Adding flow out_rules 359 | ofp = node.dp.ofproto 360 | ofp_parser = node.dp.ofproto_parser 361 | match_ip = ofp_parser.OFPMatch( 362 | eth_type=0x0800, 363 | ipv4_src=(src.ip_network, src.ip_netmask), 364 | ipv4_dst=(dst.ip_network, dst.ip_netmask), 365 | in_port=in_port 366 | ) 367 | match_arp = ofp_parser.OFPMatch( 368 | eth_type=0x0806, 369 | arp_spa=src.ip_network, 370 | arp_tpa=dst.ip_network, 371 | in_port=in_port 372 | ) 373 | 374 | # Sending SELECT Rules 375 | if group_id: 376 | buckets = [] 377 | for port, (capacity, latency) in out_rules.iteritems(): 378 | bucket_weight = self.compute_bucket_weight( 379 | out_total_capacity, 380 | capacity, 381 | out_total_latency, 382 | latency, 383 | max_delay_imbalance 384 | ) 385 | bucket_action = [ 386 | ofp_parser.OFPActionOutput(port, 2000) 387 | ] 388 | if(bucket_weight > 0): 389 | buckets.append( 390 | ofp_parser.OFPBucket( 391 | weight=bucket_weight, 392 | actions=bucket_action 393 | ) 394 | ) 395 | # If GROUP Was new, we send a GROUP_ADD 396 | if group_new: 397 | logger.info( 398 | 'GROUP_ADD for %s from %s to %s port ' 399 | '%d GROUP_ID %d out_rules %s', 400 | node, src, dst, in_port, group_id, buckets 401 | ) 402 | logger.info('GROUP_ADD_BUCKETS %s', buckets) 403 | 404 | req = ofp_parser.OFPGroupMod( 405 | node.dp, ofp.OFPGC_ADD, ofp.OFPGT_SELECT, group_id, 406 | buckets 407 | ) 408 | node.dp.send_msg(req) 409 | 410 | # If the GROUP already existed, we send a GROUP_MOD to 411 | # eventually adjust the buckets with current link 412 | # utilization 413 | else: 414 | req = ofp_parser.OFPGroupMod( 415 | node.dp, ofp.OFPGC_MODIFY, ofp.OFPGT_SELECT, 416 | group_id, buckets) 417 | node.dp.send_msg(req) 418 | logger.info('GROUP_MOD for %s from %s to %s port %d ' 419 | 'GROUP_ID %d out_rules %s', 420 | node, src, dst, in_port, group_id, buckets 421 | ) 422 | logger.info('GROUP_MOD_BUCKETS %s', buckets) 423 | 424 | actions = [ofp_parser.OFPActionGroup(group_id)] 425 | self.controller.add_flow(node.dp, self.PRIORITY_DEFAULT, 426 | match_ip, actions, buffer_id=None) 427 | self.controller.add_flow(node.dp, 1, match_arp, 428 | actions, buffer_id=None) 429 | 430 | # Sending OUTPUT Rules 431 | else: 432 | logger.info('Match for %s from %s to %s port ' 433 | '%d out_rules %s', 434 | node, src, dst, in_port, out_rules) 435 | port, rate = out_rules.popitem() 436 | actions = [ofp_parser.OFPActionOutput(port)] 437 | self.controller.add_flow(node.dp, self.PRIORITY_DEFAULT, 438 | match_ip, actions, buffer_id=None) 439 | self.controller.add_flow( 440 | node.dp, self.PRIORITY_DEFAULT, 441 | match_arp, actions, buffer_id=None 442 | ) 443 | 444 | # Creating the reordering group if there are multiple paths 445 | # converging on the node and their delay imbalance is above the 446 | # threeshold 447 | if in_ports > 1 and self.mdi(in_latencies) > self.controller.MDI_REORDERING_THRESHOLD: 448 | self.create_reordering_group( 449 | node, src, dst, mp_dict[dst][src][node] 450 | ) 451 | 452 | def create_reordering_group(self, node, src, dst, in_to_out_ports): 453 | 454 | ofp = node.dp.ofproto 455 | ofp_parser = node.dp.ofproto_parser 456 | group_id = self.generate_openflow_gid() 457 | out_port_no = in_to_out_ports.values()[0].keys()[0] 458 | buckets = [] 459 | bucket_action = [ofp_parser.OFPActionOutput(out_port_no, 2000)] 460 | buckets.append(ofp_parser.OFPBucket(actions=bucket_action)) 461 | req = ofp_parser.OFPGroupMod( 462 | node.dp, ofp.OFPGC_ADD, self.OPENFLOW_GROUP_REORDERING, 463 | group_id, buckets 464 | ) 465 | node.dp.send_msg(req) 466 | 467 | logger.info( 468 | 'REORDERING at %s to port %d GROUP_ID %d', 469 | node, out_port_no, group_id 470 | ) 471 | 472 | for in_port in in_to_out_ports.keys(): 473 | match = ofp_parser.OFPMatch( 474 | eth_type=0x0800, in_port=in_port, ip_proto=6, 475 | ipv4_src=(src.ip_network, 476 | src.ip_netmask), 477 | ipv4_dst=(dst.ip_network, dst.ip_netmask) 478 | ) 479 | actions = [ofp_parser.OFPActionGroup(group_id)] 480 | self.controller.add_flow(node.dp, self.PRIORITY_REORDERING, 481 | match, actions, buffer_id=None) 482 | 483 | def compute_bucket_weight(self, total_capacity, capacity, total_latency, 484 | latency, max_delay_imbalance): 485 | ''' 486 | This function computes the bucket weight, AKA the number of 487 | packets which are sent over each iteration of the weighted round robin 488 | Scheduler. 489 | I saw experimentally that having bursts the size of 50-100 packets 490 | is the best compromise 491 | between throughput/reordering with paths that differ a lot in latency. 492 | If the latency is very close to be the same, I use smaller bursts 493 | (~10 packets) to fully utilize 494 | the combined path capacity 495 | Latency infulences the bucket size but not as much as the capacity, 496 | the reordering buffer will take care of it 497 | ''' 498 | 499 | c_ratio = float(capacity) / float(total_capacity) 500 | l_ratio = latency / total_latency 501 | 502 | if max_delay_imbalance < 0.15: 503 | c_var = 4 504 | l_var = 0 # Latency is not impactful in such low levels 505 | elif max_delay_imbalance < 0.25: 506 | c_var = 10 507 | l_var = 8 508 | else: 509 | c_var = 150 510 | l_var = 50 511 | 512 | # More capacity more packets, more delay,less packets 513 | # outval = c_var*c_ratio + (l_var-l_var*l_ratio) 514 | 515 | # Using only bandwidth for bucket weight because there needs to be some 516 | # better formula otherwise with very low latencies the differences can 517 | # be huge 518 | outval = c_var*c_ratio 519 | 520 | logger.info('total_c: %f c_ratio: %f c: %f total_l: %f l_ratio: %f l:' 521 | ' %f Bucket weight -> %d', 522 | total_capacity, c_ratio, capacity, total_latency, 523 | l_ratio, latency, outval 524 | ) 525 | 526 | return int(outval) 527 | 528 | def modify_group(self, datapath, group_id, rules): 529 | 530 | logger.info('Modify group for %s gid %s rules %s', 531 | datapath, group_id, rules) 532 | 533 | ofp = datapath.ofproto 534 | ofp_parser = datapath.ofproto_parser 535 | buckets = [] 536 | 537 | for port, rate in rules.iteritems(): 538 | bucket_action = [ofp_parser.OFPActionOutput(port, 2000)] 539 | buckets.append(ofp_parser.OFPBucket( 540 | weight=rate, actions=bucket_action) 541 | ) 542 | 543 | req = ofp_parser.OFPGroupMod( 544 | datapath, ofp.OFPGC_MODIFY, ofp.OFPGT_SELECT, 545 | group_id, buckets 546 | ) 547 | 548 | datapath.send_msg(req) 549 | 550 | def remove_flow_rules(self, datapath, table_id, match, instructions): 551 | '''Create OFP flow mod message to remove flows from table''' 552 | ofproto = datapath.ofproto 553 | flow_mod = datapath.ofproto_parser.OFPFlowMod( 554 | datapath, 0, 0, table_id, ofproto.OFPFC_DELETE, 0, 0, 1, 555 | ofproto.OFPCML_NO_BUFFER, ofproto.OFPP_ANY, 556 | ofproto.OFPG_ANY, 0, match, instructions 557 | ) 558 | 559 | return flow_mod 560 | 561 | def delete_all_flows(self, switch): 562 | 563 | for dpid, node in self.dpid_to_switch.iteritems(): 564 | ofp_parser = switch.dp.ofproto_parser 565 | 566 | empty_match = ofp_parser.OFPMatch() 567 | instructions = [] 568 | flow_mod = self.remove_flow_rules( 569 | switch.dp, 0, 570 | empty_match, instructions 571 | ) 572 | switch.dp.send_msg(flow_mod) 573 | 574 | 575 | class Path: 576 | 577 | def __init__(self, node): 578 | self.nodes = [node] 579 | 580 | def insert(self, index, node): 581 | self.nodes.insert(index, node) 582 | 583 | def length(self): 584 | return len(self.nodes) 585 | 586 | def capacity(self): 587 | capacity = float('inf') 588 | for previous_node, node, next_node in self.iter_previous_and_next(): 589 | if next_node: 590 | next_port = node.peer_to_local_port[next_node] 591 | capacity = min( 592 | capacity, node.ports[next_port].capacity_maxflow) 593 | return capacity 594 | 595 | def latency(self): 596 | latency = 0 597 | for previous_node, node, next_node in self.iter_previous_and_next(): 598 | if next_node: 599 | next_port = node.peer_to_local_port[next_node] 600 | latency += node.ports[next_port].latency 601 | return latency 602 | 603 | def decrease_capacity(self, capacity): 604 | for previous_node, node, next_node in self.iter_previous_and_next(): 605 | if next_node: 606 | next_port = node.peer_to_local_port[next_node] 607 | node.ports[next_port].capacity_maxflow -= capacity 608 | 609 | def get_nodes_without(self, node): 610 | retval = list(self.nodes) 611 | retval.remove(node) 612 | return retval 613 | 614 | def iter_previous_and_next(self): 615 | prevs, items, nexts = tee(self.nodes, 3) 616 | prevs = chain([None], prevs) 617 | nexts = chain(islice(nexts, 1, None), [None]) 618 | return izip(prevs, items, nexts) 619 | 620 | def __str__(self): 621 | s = '' 622 | for node in self.nodes: 623 | s = '%s%s ' % (s, node) 624 | return s 625 | 626 | 627 | class Algorithm(object): 628 | 629 | ''' Algorithm base class ''' 630 | 631 | def __init__(self, dpid_to_switch, mintravcap, topochangeupdate): 632 | self.dpid_to_switch = dpid_to_switch 633 | self.topology_last_update = time.time() 634 | self.min_trasverse_capacity = mintravcap 635 | self.update_forwarding_only_on_topology_change = topochangeupdate 636 | 637 | 638 | def find_route(self, src, dst): 639 | ''' 640 | Sub-classes implement this method to calculate 641 | to calculate the paths 642 | ''' 643 | logger.error('Algorithm not implemented') 644 | return None 645 | 646 | 647 | class Dijkstra(Algorithm): 648 | 649 | class Heap(object): 650 | 651 | ''' 652 | Minimal heap, stores tuple (switch, distance) 653 | ''' 654 | 655 | def __init__(self): 656 | self.heap = [] 657 | self.switch_to_position = {} 658 | 659 | def insert(self, switch, dist): 660 | self.heap.append((switch, dist)) 661 | self.switch_to_position[switch] = len(self.heap) - 1 662 | self._shift_to_root(len(self.heap) - 1) 663 | 664 | def _shift_to_root(self, position): 665 | while position > 0 and \ 666 | self.heap[position][1] < self.heap[(position - 1) / 2][1]: 667 | self._exchange(position, (position - 1) / 2) 668 | position = (position - 1) / 2 669 | 670 | def pop(self): 671 | length = len(self.heap) 672 | if length == 0: 673 | return None 674 | 675 | ans = self.heap[0] 676 | 677 | self.heap[0] = self.heap[length - 1] 678 | self.switch_to_position[self.heap[0][0]] = 0 679 | self.heap.pop() 680 | del self.switch_to_position[ans[0]] 681 | 682 | self._shift_to_leaf(0) 683 | 684 | return ans 685 | 686 | def _shift_to_leaf(self, position): 687 | length = len(self.heap) 688 | while position * 2 + 1 < length: 689 | if position * 2 + 2 < length: 690 | if self.heap[position * 2 + 1][1] < self.heap[position * 2 + 2][1]: 691 | if self.heap[position][1] > self.heap[position * 2 + 1][1]: 692 | self._exchange(position, position * 2 + 1) 693 | position = position * 2 + 1 694 | else: 695 | break 696 | else: 697 | if self.heap[position][1] > self.heap[position * 2 + 2][1]: 698 | self._exchange(position, position * 2 + 2) 699 | position = position * 2 + 2 700 | else: 701 | break 702 | else: 703 | if self.heap[position][1] > self.heap[position * 2 + 1][1]: 704 | self._exchange(position, position * 2 + 1) 705 | position = position * 2 + 1 706 | else: 707 | break 708 | 709 | def _exchange(self, x, y): 710 | # x and y are positions in self.heap 711 | self.heap[x], self.heap[y] = self.heap[y], self.heap[x] 712 | self.switch_to_position[self.heap[x][0]] = x 713 | self.switch_to_position[self.heap[y][0]] = y 714 | 715 | def update(self, switch, distance): 716 | position = self.switch_to_position[switch] 717 | self.heap[position] = (switch, distance) 718 | self._shift_to_leaf(position) 719 | self._shift_to_root(position) 720 | 721 | def __repr__(self): 722 | return str(self.heap) 723 | 724 | def __init__(self, *args, **kwargs): 725 | super(Dijkstra, self).__init__(*args, **kwargs) 726 | self.paths = {} 727 | self.route_last_update = time.time() 728 | 729 | def find_route(self, source, destination): 730 | logger.info('Searching route from %s to %s' % (source, destination)) 731 | if self.update_forwarding_only_on_topology_change: 732 | if self.route_last_update < self.topology_last_update: 733 | self.paths = {} 734 | self.route_last_update = time.time() 735 | else: 736 | self.paths = {} 737 | self.route_last_update = time.time() 738 | 739 | logger.debug('self.topology_last_update %s' % 740 | self.topology_last_update) 741 | 742 | if (source, destination) in self.paths: 743 | logger.info('Path pre-computed') 744 | return self.paths[source, destination] 745 | 746 | pq = Dijkstra.Heap() 747 | distance = {} 748 | previous = {} 749 | 750 | for dpid, switch in self.dpid_to_switch.iteritems(): 751 | if switch != source: 752 | distance[switch] = float('inf') 753 | else: 754 | distance[switch] = 0 755 | 756 | previous[switch] = None 757 | logger.debug('Inserting %s in the queue' % switch) 758 | pq.insert(switch, distance[switch]) 759 | 760 | while True: 761 | x = pq.pop() 762 | logger.info('Popping %s : %f' % x) 763 | if x is None: 764 | break 765 | 766 | logger.info('%s', pq) 767 | switch, dist = x 768 | 769 | if not switch.has_peer_capacity(): 770 | logger.info('No peer capacity') 771 | break 772 | 773 | if switch == destination: 774 | calculated_path = Path(destination) 775 | while previous[switch]: 776 | calculated_path.insert(0, previous[switch]) 777 | switch = previous[switch] 778 | self.paths[source, destination] = calculated_path 779 | if calculated_path.length() == 1: 780 | return None 781 | else: 782 | return calculated_path 783 | 784 | for port_no, port in switch.ports.iteritems(): 785 | logger.debug('Analyzing %s %d %s - peer_switch_dpid: %s' % 786 | (switch, port_no, port, port.peer_switch_dpid)) 787 | 788 | peer_switch = self.dpid_to_switch.get(port.peer_switch_dpid, 789 | None) 790 | 791 | if peer_switch is None or port.capacity_maxflow <= self.min_trasverse_capacity: 792 | logger.info('Port %s cannot be traversed' % port) 793 | continue 794 | logger.info('dist %s port latency %s distance %s' % 795 | (dist, port.latency, distance[peer_switch])) 796 | 797 | if dist + port.latency < distance[peer_switch]: 798 | distance[peer_switch] = dist + port.latency 799 | pq.update(peer_switch, dist + port.latency) 800 | previous[peer_switch] = switch 801 | 802 | return None 803 | -------------------------------------------------------------------------------- /controller/switch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from ryu.topology import switches 4 | from ryu.topology.switches import Port as Port_type 5 | from ryu.ofproto.ofproto_v1_0_parser import OFPPhyPort 6 | 7 | import netaddr 8 | import logging 9 | 10 | __author__ = 'Dario Banfi' 11 | __license__ = 'Apache 2.0' 12 | __version__ = '1.0' 13 | __email__ = 'dario.banfi@tum.de' 14 | 15 | ''' 16 | Representation of a Switch and its ports in the MPSDN network 17 | ''' 18 | 19 | logger = logging.getLogger(__name__) 20 | 21 | 22 | class Port(switches.Port): 23 | 24 | def __init__(self, port, peer=None, dp=None, is_edge=True): 25 | 26 | # Dpid of peering switch 27 | self.peer_switch_dpid = None 28 | 29 | # Port_no of peer 30 | self.peer_port_no = None 31 | 32 | # Delay cost 33 | self.latency = 0.001 34 | 35 | # Max capacity cost Bytes/s 36 | # Setting here a default upper limit of 200Mb/s 37 | self.max_capacity = 25000000 38 | 39 | # Real capacity 40 | self.capacity = self.max_capacity 41 | 42 | # Used for the algorithm 43 | self.capacity_maxflow = self.capacity 44 | 45 | # Last time port stats were requested 46 | self.last_request_time = None 47 | 48 | # Last utilization value computed for the port 49 | self.last_utilization_value = None 50 | 51 | # Edge ports are ports which are connected to the host networks 52 | # Every port is initialized as edge_port by default 53 | # and set to False once a addLink event is seen 54 | self.is_edge = is_edge 55 | 56 | if isinstance(port, Port_type): 57 | # init switches.Port variables 58 | self.dpid = port.dpid 59 | self._ofproto = port._ofproto 60 | self._config = port._config 61 | self._state = port._state 62 | 63 | self.port_no = port.port_no 64 | self.hw_addr = netaddr.EUI(port.hw_addr) 65 | self.name = port.name 66 | 67 | # below are our new variables 68 | if peer: 69 | self.peer_switch_dpid = peer.dpid 70 | self.peer_port_no = peer.port_no 71 | elif isinstance(port, OFPPhyPort): 72 | self.dpid = dp.id 73 | self._ofproto = dp.ofproto 74 | self._config = port.config 75 | self._state = port.state 76 | 77 | self.port_no = port.port_no 78 | self.hw_addr = netaddr.EUI(port.hw_addr) 79 | self.name = port.name 80 | else: 81 | logger.error('%s %s %s not match', 82 | type(port), 83 | switches.Port, 84 | Port_type 85 | ) 86 | raise AttributeError 87 | 88 | def restore_capacity(self): 89 | ''' 90 | Resets the capacity which had been decreased by the 91 | max-flow algorithm 92 | ''' 93 | self.capacity_maxflow = self.capacity 94 | 95 | def set_max_capacity(self, capacity): 96 | self.max_capacity = capacity 97 | self.capacity = capacity 98 | self.capacity_maxflow = capacity 99 | 100 | def __repr__(self): 101 | return 'Port' % ( 102 | self.port_no, self.capacity_maxflow, self.latency 103 | ) 104 | 105 | def __str__(self): 106 | return 'Port' % ( 107 | self.port_no, self.capacity_maxflow, self.latency 108 | ) 109 | 110 | 111 | class Switch(switches.Switch): 112 | 113 | def __init__(self, dp): 114 | 115 | # Init with Datapath object 116 | super(Switch, self).__init__(dp) 117 | 118 | self.ip_network = None 119 | 120 | self.ip_netmask = None 121 | 122 | self.edge_port = None 123 | 124 | self.peer_to_local_port = {} 125 | 126 | self.ports = {} 127 | 128 | self.delay_to_controller = 0 129 | 130 | self.port_stats_request_time = 0 131 | 132 | def calculate_delay_to_controller(self, timedelta): 133 | ''' 134 | Smoothes the sample delay to controller with previous 135 | measurements 136 | ''' 137 | if self.port_stats_request_time != 0: 138 | 139 | sample_delay = (timedelta - self.port_stats_request_time) / 2 140 | 141 | # Smoothed RTT, like TCP 142 | if self.delay_to_controller == 0: 143 | self.delay_to_controller = sample_delay 144 | else: 145 | self.delay_to_controller = 0.875 * \ 146 | self.delay_to_controller + 0.125 * sample_delay 147 | 148 | self.port_stats_request_time = 0 149 | else: 150 | logger.error( 151 | 'Trying to calculate switch-controller delay ' 152 | 'without initial time value' 153 | ) 154 | 155 | def calculate_delay_to_peer(self, peer_switch, delay): 156 | ''' 157 | Smoothes the delay of two peering switches 158 | ''' 159 | 160 | peer_port_no = self.peer_to_local_port[peer_switch] 161 | delay = delay - self.delay_to_controller - \ 162 | peer_switch.delay_to_controller 163 | # When delays are very low calculation can be imprecise 164 | if(delay < 0): 165 | delay = 0 166 | if self.ports[peer_port_no].latency != float('inf'): 167 | # Smoothed RTT 168 | self.ports[peer_port_no].latency = 0.875 * \ 169 | self.ports[peer_port_no].latency + 0.125 * delay 170 | else: 171 | self.ports[peer_port_no].latency = delay 172 | 173 | logger.info('Sample delay from dp %d to %d is %f' % 174 | (self.dp.id, peer_switch.dp.id, delay)) 175 | 176 | def has_peer_capacity(self): 177 | ''' Returns true if any of the connected ports to this 178 | switch sill have capacity_maxflow>0 179 | ''' 180 | retval = False 181 | for port_no, port in self.ports.iteritems(): 182 | if not port.is_edge and port.capacity_maxflow > 0: 183 | retval = True 184 | break 185 | return retval 186 | 187 | def __repr__(self): 188 | msg = 'Switch li > a:hover, 318 | .dropdown-menu > li > a:focus { 319 | background-color: #e8e8e8; 320 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 321 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 322 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 323 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 324 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 325 | background-repeat: repeat-x; 326 | } 327 | .dropdown-menu > .active > a, 328 | .dropdown-menu > .active > a:hover, 329 | .dropdown-menu > .active > a:focus { 330 | background-color: #2e6da4; 331 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 332 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 333 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 334 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 335 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 336 | background-repeat: repeat-x; 337 | } 338 | .navbar-default { 339 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); 340 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); 341 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); 342 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); 343 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 344 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 345 | background-repeat: repeat-x; 346 | border-radius: 4px; 347 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 348 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 349 | } 350 | .navbar-default .navbar-nav > .open > a, 351 | .navbar-default .navbar-nav > .active > a { 352 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 353 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 354 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); 355 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); 356 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); 357 | background-repeat: repeat-x; 358 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 359 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 360 | } 361 | .navbar-brand, 362 | .navbar-nav > li > a { 363 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25); 364 | } 365 | .navbar-inverse { 366 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); 367 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); 368 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); 369 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); 370 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 371 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 372 | background-repeat: repeat-x; 373 | border-radius: 4px; 374 | } 375 | .navbar-inverse .navbar-nav > .open > a, 376 | .navbar-inverse .navbar-nav > .active > a { 377 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); 378 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); 379 | background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); 380 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); 381 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); 382 | background-repeat: repeat-x; 383 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 384 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 385 | } 386 | .navbar-inverse .navbar-brand, 387 | .navbar-inverse .navbar-nav > li > a { 388 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); 389 | } 390 | .navbar-static-top, 391 | .navbar-fixed-top, 392 | .navbar-fixed-bottom { 393 | border-radius: 0; 394 | } 395 | @media (max-width: 767px) { 396 | .navbar .navbar-nav .open .dropdown-menu > .active > a, 397 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, 398 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { 399 | color: #fff; 400 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 401 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 402 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 403 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 405 | background-repeat: repeat-x; 406 | } 407 | } 408 | .alert { 409 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2); 410 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 411 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 412 | } 413 | .alert-success { 414 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 415 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 416 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); 417 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 418 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 419 | background-repeat: repeat-x; 420 | border-color: #b2dba1; 421 | } 422 | .alert-info { 423 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 424 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 425 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); 426 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 427 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 428 | background-repeat: repeat-x; 429 | border-color: #9acfea; 430 | } 431 | .alert-warning { 432 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 433 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 434 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); 435 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 436 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 437 | background-repeat: repeat-x; 438 | border-color: #f5e79e; 439 | } 440 | .alert-danger { 441 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 442 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 443 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); 444 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 445 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 446 | background-repeat: repeat-x; 447 | border-color: #dca7a7; 448 | } 449 | .progress { 450 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 451 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 452 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); 453 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 454 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 455 | background-repeat: repeat-x; 456 | } 457 | .progress-bar { 458 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); 459 | background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); 460 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); 461 | background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); 462 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); 463 | background-repeat: repeat-x; 464 | } 465 | .progress-bar-success { 466 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 467 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); 468 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); 469 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 470 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 471 | background-repeat: repeat-x; 472 | } 473 | .progress-bar-info { 474 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 475 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 476 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); 477 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 478 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 479 | background-repeat: repeat-x; 480 | } 481 | .progress-bar-warning { 482 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 483 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 484 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); 485 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 486 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 487 | background-repeat: repeat-x; 488 | } 489 | .progress-bar-danger { 490 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 491 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); 492 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); 493 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 494 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 495 | background-repeat: repeat-x; 496 | } 497 | .progress-bar-striped { 498 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 499 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 500 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 501 | } 502 | .list-group { 503 | border-radius: 4px; 504 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 505 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 506 | } 507 | .list-group-item.active, 508 | .list-group-item.active:hover, 509 | .list-group-item.active:focus { 510 | text-shadow: 0 -1px 0 #286090; 511 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); 512 | background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); 513 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); 514 | background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); 515 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); 516 | background-repeat: repeat-x; 517 | border-color: #2b669a; 518 | } 519 | .list-group-item.active .badge, 520 | .list-group-item.active:hover .badge, 521 | .list-group-item.active:focus .badge { 522 | text-shadow: none; 523 | } 524 | .panel { 525 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 526 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 527 | } 528 | .panel-default > .panel-heading { 529 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 530 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 531 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 532 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 533 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 534 | background-repeat: repeat-x; 535 | } 536 | .panel-primary > .panel-heading { 537 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 538 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 539 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 540 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 541 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 542 | background-repeat: repeat-x; 543 | } 544 | .panel-success > .panel-heading { 545 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 546 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 547 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); 548 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 549 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 550 | background-repeat: repeat-x; 551 | } 552 | .panel-info > .panel-heading { 553 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 554 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 555 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); 556 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 557 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 558 | background-repeat: repeat-x; 559 | } 560 | .panel-warning > .panel-heading { 561 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 562 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 563 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); 564 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 565 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 566 | background-repeat: repeat-x; 567 | } 568 | .panel-danger > .panel-heading { 569 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 570 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 571 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); 572 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 573 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 574 | background-repeat: repeat-x; 575 | } 576 | .well { 577 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 578 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 579 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); 580 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 581 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 582 | background-repeat: repeat-x; 583 | border-color: #dcdcdc; 584 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 585 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 586 | } 587 | /*# sourceMappingURL=bootstrap-theme.css.map */ 588 | -------------------------------------------------------------------------------- /demo/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.6 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} 6 | /*# sourceMappingURL=bootstrap-theme.min.css.map */ -------------------------------------------------------------------------------- /demo/css/bootstrap-theme.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":";;;;AAmBA,YAAA,aAAA,UAAA,aAAA,aAAA,aAME,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBDvCR,mBAAA,mBAAA,oBAAA,oBAAA,iBAAA,iBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBCsCA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBDlCR,qBAAA,sBAAA,sBAAA,uBAAA,mBAAA,oBAAA,sBAAA,uBAAA,sBAAA,uBAAA,sBAAA,uBAAA,+BAAA,gCAAA,6BAAA,gCAAA,gCAAA,gCCiCA,mBAAA,KACQ,WAAA,KDlDV,mBAAA,oBAAA,iBAAA,oBAAA,oBAAA,oBAuBI,YAAA,KAyCF,YAAA,YAEE,iBAAA,KAKJ,aErEI,YAAA,EAAA,IAAA,EAAA,KACA,iBAAA,iDACA,iBAAA,4CAAA,iBAAA,qEAEA,iBAAA,+CCnBF,OAAA,+GH4CA,OAAA,0DACA,kBAAA,SAuC2C,aAAA,QAA2B,aAAA,KArCtE,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAgBN,aEtEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAiBN,aEvEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAkBN,UExEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,gBAAA,gBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,iBAAA,iBAEE,iBAAA,QACA,aAAA,QAMA,mBAAA,0BAAA,yBAAA,0BAAA,yBAAA,yBAAA,oBAAA,2BAAA,0BAAA,2BAAA,0BAAA,0BAAA,6BAAA,oCAAA,mCAAA,oCAAA,mCAAA,mCAME,iBAAA,QACA,iBAAA,KAmBN,aEzEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAoBN,YE1EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,kBAAA,kBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,mBAAA,mBAEE,iBAAA,QACA,aAAA,QAMA,qBAAA,4BAAA,2BAAA,4BAAA,2BAAA,2BAAA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,+BAAA,sCAAA,qCAAA,sCAAA,qCAAA,qCAME,iBAAA,QACA,iBAAA,KA2BN,eAAA,WClCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBD2CV,0BAAA,0BE3FI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GF0FF,kBAAA,SAEF,yBAAA,+BAAA,+BEhGI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GFgGF,kBAAA,SASF,gBE7GI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SH+HA,cAAA,ICjEA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBD6DV,sCAAA,oCE7GI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD0EV,cAAA,iBAEE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEhII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SHkJA,cAAA,IAHF,sCAAA,oCEhII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDgFV,8BAAA,iCAYI,YAAA,EAAA,KAAA,EAAA,gBAKJ,qBAAA,kBAAA,mBAGE,cAAA,EAqBF,yBAfI,mDAAA,yDAAA,yDAGE,MAAA,KE7JF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UFqKJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC3HA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBDsIV,eEtLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAKF,YEvLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAMF,eExLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAOF,cEzLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAeF,UEjMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuMJ,cE3MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFwMJ,sBE5MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyMJ,mBE7MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0MJ,sBE9MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2MJ,qBE/MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,sBElLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKFyLJ,YACE,cAAA,IC9KA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDgLV,wBAAA,8BAAA,8BAGE,YAAA,EAAA,KAAA,EAAA,QEnOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiOF,aAAA,QALF,+BAAA,qCAAA,qCAQI,YAAA,KAUJ,OCnME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBD4MV,8BE5PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyPJ,8BE7PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0PJ,8BE9PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2PJ,2BE/PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4PJ,8BEhQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6PJ,6BEjQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoQJ,MExQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsQF,aAAA,QC3NA,mBAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA"} -------------------------------------------------------------------------------- /demo/demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from __future__ import division 4 | from mininet.net import Mininet 5 | from mininet.node import RemoteController, OVSKernelSwitch 6 | from demo_topology import DemoTopo 7 | from time import sleep 8 | from mininet.link import TCLink 9 | 10 | import subprocess 11 | import traceback 12 | import signal 13 | from bottle import request, Bottle, abort 14 | from gevent.pywsgi import WSGIServer 15 | from geventwebsocket import WebSocketError 16 | from geventwebsocket.handler import WebSocketHandler 17 | 18 | 19 | __author__ = 'Dario Banfi' 20 | __license__ = 'Apache 2.0' 21 | __version__ = '1.0' 22 | __email__ = 'dario.banfi@tum.de' 23 | 24 | 25 | MPSDN_PROJECT_FOLDER = '/home/ambi/mpsdn' 26 | VIDEO_FILE = '/home/ambi/vid.m4v' 27 | 28 | 29 | class Demo: 30 | 31 | def __init__(self): 32 | self.wsock = None 33 | 34 | def set_wsock(self, ws): 35 | self.wsock = ws 36 | 37 | def network_creation(self): 38 | ''' 39 | Initializes the mininet topology, configures singlepath and pings 40 | src and dst to test reachability 41 | ''' 42 | 43 | print 'Starting controller' 44 | self.controllerprocess = subprocess.Popen( 45 | "./configuration/start_controller.sh", 46 | stdout=subprocess.PIPE 47 | ) 48 | sleep(1) 49 | print 'Creating network' 50 | self.net = Mininet(topo=DemoTopo(), link=TCLink, 51 | switch=OVSKernelSwitch, controller=RemoteController) 52 | self.net.start() 53 | sleep(1) 54 | subprocess.call('./configuration/configure_singlepath.sh', shell=True) 55 | 56 | print 'Testing host reachability' 57 | self.src = self.net.getNodeByName('src') 58 | self.dst = self.net.getNodeByName('dst') 59 | self.h1 = self.net.getNodeByName('h1') 60 | self.h2 = self.net.getNodeByName('h2') 61 | self.net.ping([self.src, self.dst]) 62 | 63 | def start_streaming(self): 64 | ''' 65 | Starts streaming the video file between src and dst 66 | ''' 67 | 68 | self.rtp_server = self.src.sendCmd( 69 | "vlc-wrapper -Idummy -vvv %s --repeat --mtu 1500 --sout " 70 | "'#rtp{dst=10.0.0.2,port=5050,mux=ts,ttl=64}'" % VIDEO_FILE, 71 | shell=True 72 | ) 73 | sleep(1) 74 | self.rtp_client = self.dst.sendCmd( 75 | 'vlc-wrapper --network-caching=0 rtp://@:5050') 76 | 77 | def start_controller(self): 78 | ''' 79 | Starts the controller monitoring/path setup 80 | ''' 81 | 82 | subprocess.call( 83 | './configuration/configure_controller_parameters.sh', 84 | shell=True 85 | ) 86 | sleep(5) 87 | 88 | def congest_subpath(self): 89 | ''' 90 | Congesting path until the end of the demo runtime 91 | ''' 92 | 93 | self.iperf_server = self.h2.popen( 94 | "iperf -u -s" 95 | ) 96 | sleep(1) 97 | self.iperf_client = self.h1.sendCmd( 98 | "iperf -u -c 10.0.0.4 -t 120 -b 20M" 99 | ) 100 | 101 | def readapt(self): 102 | ''' 103 | Re-running controller computation to adapt 104 | ''' 105 | subprocess.call( 106 | 'curl http://localhost:8080/multipath/recompute_multipath', 107 | shell=True 108 | ) 109 | 110 | 111 | def start(self): 112 | try: 113 | self.network_creation() 114 | sleep(13) 115 | self.wsock.send('step1') 116 | print 'Step 1 - SinglePath Streaming' 117 | self.start_streaming() 118 | sleep(30) 119 | self.wsock.send('step2') 120 | print 'Step 2 - Starting Multipath controller' 121 | self.start_controller() 122 | sleep(30) 123 | self.wsock.send('step3') 124 | print 'Step 3 - Congesting Subpath!' 125 | self.congest_subpath() 126 | sleep(20) 127 | self.wsock.send('step4') 128 | sleep(5) 129 | print 'Step 4 - Readapting' 130 | self.readapt() 131 | sleep(20) 132 | 133 | # Stop 134 | self.wsock.send('stop') 135 | sleep(1) 136 | self.cleanup() 137 | self.net.stop() 138 | 139 | except: 140 | print 'Caught exception! Cleaning up...' 141 | traceback.print_exc() 142 | self.cleanup() 143 | subprocess.call('mn -c', shell=True) 144 | subprocess.call('pkill -f python', shell=True) 145 | 146 | def stop(self): 147 | pass 148 | 149 | def cleanup(self): 150 | print 'Demo Cleanup' 151 | if hasattr(self, 'iperf_server'): 152 | self.iperf_server.send_signal(signal.SIGINT) 153 | subprocess.call('pkill -f iperf', shell=True) 154 | subprocess.call('pkill -f ryu', shell=True) 155 | if hasattr(self, 'rtp_client'): 156 | try: 157 | self.rtp_client.send_signal(signal.SIGKILL) 158 | except: 159 | pass 160 | print 'Killing VLC' 161 | subprocess.call('pkill -f vlc', shell=True) 162 | 163 | 164 | app = Bottle() 165 | demo = Demo() 166 | 167 | 168 | @app.route('/websocket') 169 | def handle_websocket(): 170 | wsock = request.environ.get('wsgi.websocket') 171 | if not wsock: 172 | abort(400, 'Expected WebSocket request.') 173 | 174 | while True: 175 | try: 176 | message = wsock.receive() 177 | if message == 'start': 178 | print 'Received Start Message' 179 | wsock.send("start") 180 | demo.set_wsock(wsock) 181 | print 'Starting Demo' 182 | demo.start() 183 | elif message == 'stop': 184 | demo.stop() 185 | except WebSocketError: 186 | break 187 | 188 | 189 | server = WSGIServer(("0.0.0.0", 9090), app, 190 | handler_class=WebSocketHandler) 191 | server.serve_forever() 192 | -------------------------------------------------------------------------------- /demo/demo_topology.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from mininet.topo import Topo 4 | 5 | ''' 6 | Useful topologies used for testing / simulations 7 | ''' 8 | 9 | __author__ = 'Dario Banfi' 10 | __license__ = 'Apache 2.0' 11 | __version__ = '1.0' 12 | __email__ = 'dario.banfi@tum.de' 13 | 14 | 15 | class DemoTopo(Topo): 16 | 17 | def __init__(self, **opts): 18 | Topo.__init__(self, **opts) 19 | 20 | src = self.addHost('src', ip='10.0.0.1') 21 | dst = self.addHost('dst', ip='10.0.0.2') 22 | h1 = self.addHost('h1', ip='10.0.0.3') 23 | h2 = self.addHost('h2', ip='10.0.0.4') 24 | 25 | self.switch = {} 26 | for s in range(1, 7): 27 | self.switch[s-1] = self.addSwitch( 28 | 's%s' % (s), dpid='000000000000000%s' % s, 29 | protocols='OpenFlow13' 30 | ) 31 | 32 | self.addLink( 33 | self.switch[0], self.switch[1], port1=1, port2=1) 34 | self.addLink( 35 | self.switch[0], self.switch[3], port1=2, port2=1) 36 | 37 | self.addLink(self.switch[1], self.switch[2], 38 | port1=2, port2=1, delay='25ms', bw=7 39 | ) 40 | self.addLink(self.switch[2], self.switch[5], port1=2, port2=1) 41 | 42 | self.addLink(self.switch[3], self.switch[4], 43 | port1=2, port2=1, delay='25ms', bw=7 44 | ) 45 | self.addLink(self.switch[4], self.switch[5], port1=2, port2=2) 46 | 47 | self.addLink(self.switch[0], src) 48 | self.addLink(self.switch[5], dst) 49 | self.addLink(self.switch[3], h1) 50 | self.addLink(self.switch[4], h2) 51 | 52 | topos = {'demo': (lambda: DemoTopo())} 53 | -------------------------------------------------------------------------------- /demo/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dariobanfi/multipath-sdn-controller/130cb6562525bb10351753eb5b7a7101f5e47ff1/demo/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /demo/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dariobanfi/multipath-sdn-controller/130cb6562525bb10351753eb5b7a7101f5e47ff1/demo/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /demo/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dariobanfi/multipath-sdn-controller/130cb6562525bb10351753eb5b7a7101f5e47ff1/demo/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /demo/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dariobanfi/multipath-sdn-controller/130cb6562525bb10351753eb5b7a7101f5e47ff1/demo/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Multipath SDN Demo 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 |
21 |

Endpoint-Transparent Multipath in Software Defined Networks

22 |

Demo

23 | 26 |
27 |
28 |

29 |
30 |
31 |

32 |

Not ready, refresh

33 |
34 |
35 |
36 |
37 |
38 | 40 | 43 | 46 | 50 | 54 | 58 | 61 | 62 | 67 | 70 | 71 | 72 | 78 | 81 | 82 | 83 | 88 | 91 | 92 | 93 | 99 | 102 | 103 | Source 104 | Destination 105 | H1 106 | H2 107 | 108 | 109 | 110 | 111 | 120 | 129 | 138 | 139 | 140 | 141 | Controller 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 268 | 269 | 270 | 271 | 7 Mb/s 272 | 7 Mb/s 273 | 274 | 275 | 276 |
277 |
278 |
279 |
280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | -------------------------------------------------------------------------------- /demo/js/animation.js: -------------------------------------------------------------------------------- 1 | var arrowsup = ["#aup1","#aup2","#aup3","#aup4","#aup5","#aup6"]; 2 | var arrowsdown = ["#adown1","#adown2","#adown3","#adown4","#adown5","#adown6"]; 3 | var switches = ["#s1","#s2","#s3","#s4","#s5","#s6"]; 4 | var links = ["#link12","#link23","#link36","#link14","#link45","#link56"]; 5 | var hosts = ["#h1img", "#h2img", "#srcimg", "#dstimg"]; 6 | var directlinks = ["#h1link", "#h2link", "#srclink", "#dstlink"]; 7 | var labels = [ "#h1label", "#h2label", "#srclabel", "#dstlabel"]; 8 | var speeds = ["#speedup", "#speeddown"]; 9 | var controller = ["#controllerlabel","#controllerimg", ]; 10 | var objects = [arrowsup, arrowsdown,switches, links, hosts, controller, directlinks,labels, speeds]; 11 | 12 | var congestion_subpath = ["#h1link", "#h2link", "#link45"]; 13 | 14 | 15 | var ws = new WebSocket("ws://127.0.0.1:9090/websocket"); 16 | var mt = $("#mt"); 17 | var st = $("#st"); 18 | 19 | ws.onopen = function() { 20 | mt.text("Connected"); 21 | st.text("Ready to start"); 22 | }; 23 | ws.onmessage = function (evt) { 24 | if (evt.data === "start"){ 25 | startDemo(); 26 | } 27 | else if (evt.data === "step1"){ 28 | startStreaming(); 29 | } 30 | else if (evt.data === "step2"){ 31 | startController(); 32 | } 33 | else if (evt.data === "step3"){ 34 | startCongestion(); 35 | } 36 | else if (evt.data === "step4"){ 37 | readapt(); 38 | } 39 | else if (evt.data === "stop"){ 40 | end(); 41 | } 42 | }; 43 | 44 | function startMessage(){ 45 | ws.send('start'); 46 | } 47 | 48 | 49 | 50 | function hideAll(){ 51 | $.each(objects, function(i, value ){ 52 | $.each(value, function(j, subvalue){ 53 | $(subvalue).hide(); 54 | }); 55 | }); 56 | } 57 | 58 | function startDemo(){ 59 | var counter = 3; 60 | var interval = setInterval(function() { 61 | st.text('Starting in ' + counter); 62 | st.velocity('fadeIn'); 63 | counter--; 64 | if (counter == -1){ 65 | clearInterval(interval); 66 | setupTopology(interval); 67 | } 68 | }, 1000); 69 | } 70 | 71 | 72 | function setupTopology(){ 73 | mt.text('Step 0 - Creating emulated topology'); 74 | st.text(''); 75 | 76 | setTimeout(function(){ 77 | st.velocity('fadeIn'); 78 | st.text('Adding Switches'); 79 | $.each(switches, function(i, val){ 80 | var d1 = Math.floor(Math.random()*(2000-500+1)+500); 81 | $(val).velocity('fadeIn', {duration: d1}); 82 | }) ; 83 | }, 2000); 84 | 85 | setTimeout(function(){ 86 | st.velocity('fadeIn'); 87 | st.text('Adding Links'); 88 | $.each(links, function(i, val){ 89 | var d1 = Math.floor(Math.random()*(2000-500+1)+500); 90 | $(val).velocity('fadeIn', {duration: d1}); 91 | }) ; 92 | $.each(speeds, function(i, val){ 93 | var d1 = Math.floor(Math.random()*(2000-500+1)+500); 94 | $(val).velocity('fadeIn', {duration: d1}); 95 | }) ; 96 | }, 4000); 97 | 98 | setTimeout(function(){ 99 | st.velocity('fadeIn'); 100 | st.text('Adding Hosts'); 101 | $.each(hosts, function(i, val){ 102 | var d1 = Math.floor(Math.random()*(2000-500+1)+500); 103 | $(val).velocity('fadeIn', {duration: d1}); 104 | }) ; 105 | $.each(labels, function(i, val){ 106 | var d1 = Math.floor(Math.random()*(2000-500+1)+500); 107 | $(val).velocity('fadeIn', {duration: d1}); 108 | }) ; 109 | $.each(directlinks, function(i, val){ 110 | var d1 = Math.floor(Math.random()*(2000-500+1)+500); 111 | $(val).velocity('fadeIn', {duration: d1}); 112 | }) ; 113 | }, 6000); 114 | 115 | setTimeout(function(){ 116 | st.text('Configuring Single-Path Forwarding'); 117 | }, 8000); 118 | 119 | setTimeout(function(){ 120 | $("#link14").velocity( 121 | {fill: "#D1D0CE"}, 122 | {duration: 500} 123 | ); 124 | $("#link45").velocity( 125 | {fill: "#D1D0CE"}, 126 | {duration: 500} 127 | ); 128 | $("#link56").velocity( 129 | {fill: "#D1D0CE"}, 130 | {duration: 500} 131 | ); 132 | $("#h1link").velocity( 133 | {fill: "#D1D0CE"}, 134 | {duration: 500} 135 | ); 136 | $("#h2link").velocity( 137 | {fill: "#D1D0CE"}, 138 | {duration: 500} 139 | ); 140 | }, 8000); 141 | setTimeout(function(){ 142 | $("#srclink").velocity( 143 | {fill: "#2FD566"}, 144 | {duration: 500} 145 | ); 146 | }, 8000); 147 | setTimeout(function(){ 148 | $("#link12").velocity( 149 | {fill: "#2FD566"}, 150 | {duration: 500} 151 | ); 152 | 153 | }, 8500); 154 | setTimeout(function(){ 155 | $("#link23").velocity( 156 | {fill: "#2FD566"}, 157 | {duration: 500} 158 | ); 159 | 160 | }, 9000); 161 | setTimeout(function(){ 162 | $("#link36").velocity( 163 | {fill: "#2FD566"}, 164 | {duration: 500} 165 | ); 166 | 167 | }, 9500); 168 | setTimeout(function(){ 169 | $("#dstlink").velocity( 170 | {fill: "#2FD566"}, 171 | {duration: 500} 172 | ); 173 | 174 | }, 10000); 175 | } 176 | 177 | function startStreaming(){ 178 | mt.text('Step 1 - Single-Path Streaming'); 179 | mt.velocity('fadeIn'); 180 | st.html('Streaming Video (~10 Mb/s bitrate) over 7 Mb/s link'); 181 | st.velocity('fadeIn'); 182 | $("#srclink").velocity({fill: "#000080"}, { delay: 0, loop: true }); 183 | $("#link12").velocity({fill: "#000080"}, { delay: 50,loop: true }); 184 | $("#link23").velocity({fill: "#000080"}, {delay: 100, loop: true }); 185 | $("#link36").velocity({fill: "#000080"}, {delay: 150, loop: true }); 186 | $("#dstlink").velocity({fill: "#000080"}, {delay: 200, loop: true }); 187 | } 188 | 189 | function startController(){ 190 | mt.text('Step 2 - Multipath Streaming'); 191 | mt.velocity('fadeIn'); 192 | st.text('Starting Controller'); 193 | st.velocity('fadeIn'); 194 | $.each(controller, function(key, value){ 195 | $(value).velocity('fadeIn', {duration: 1000}); 196 | }); 197 | setTimeout(function(){ 198 | st.html('Using LLDP to learn topology'); 199 | st.velocity('fadeIn'); 200 | $.each(arrowsup, function(i, val){ 201 | var d1 = Math.floor(Math.random()*(1000-500+1)+500 ); 202 | $(val).velocity('fadeIn', {duration: d1, loop: 3}); 203 | }) ; 204 | }, 3000); 205 | setTimeout(function(){ 206 | st.html('Measuring Latency'); 207 | st.velocity('fadeIn'); 208 | $.each(arrowsdown, function(i, val){ 209 | var d1 = Math.floor(Math.random()*(1000-500+1)+500 ); 210 | $(val).velocity('fadeIn', {duration: d1, loop: 3}); 211 | }) ; 212 | }, 7000); 213 | setTimeout(function(){ 214 | st.html('Computing Multipath Forwarding'); 215 | st.velocity('fadeIn'); 216 | }, 10000); 217 | setTimeout(function(){ 218 | st.html('Sending rules to the switches'); 219 | st.velocity('fadeIn'); 220 | $.each(arrowsdown, function(i, val){ 221 | var d1 = Math.floor(Math.random()*(1000-500+1)+500 ); 222 | $(val).velocity('fadeIn', {duration: d1, loop: 2}); 223 | }) ; 224 | $("#srclink").velocity( 225 | {fill: "#2FD566"}, 226 | {duration: 500} 227 | ); 228 | $("#link14").velocity( 229 | {fill: "#2FD566"}, 230 | {duration: 500} 231 | ); 232 | $("#link45").velocity( 233 | {fill: "#2FD566"}, 234 | {duration: 500} 235 | ); 236 | $("#link56").velocity( 237 | {fill: "#2FD566"}, 238 | {duration: 500} 239 | ); 240 | $("#h1link").velocity( 241 | {fill: "#2FD566"}, 242 | {duration: 500} 243 | ); 244 | $("#h2link").velocity( 245 | {fill: "#2FD566"}, 246 | {duration: 500} 247 | ); 248 | }, 13000); 249 | setTimeout(function(){ 250 | st.html('Multipath Streaming over an aggregated ~14 Mb/s Channel'); 251 | $("#srclink").velocity({fill: "#000080"}, { delay: 0, loop: true }); 252 | $("#link14").velocity({fill: "#000080"}, { delay: 50,loop: true }); 253 | $("#link45").velocity({fill: "#000080"}, {delay: 100, loop: true }); 254 | $("#link56").velocity({fill: "#000080"}, {delay: 150, loop: true }); 255 | }, 15000); 256 | } 257 | 258 | function startCongestion(){ 259 | mt.text('Step 3 - Sub-Path Congestion'); 260 | mt.velocity('fadeIn'); 261 | st.html(''); 262 | setTimeout(function(){ 263 | st.html('Streaming 20 Mb/s UDP Flow from H1 to H2'); 264 | st.velocity('fadeIn'); 265 | $.each(congestion_subpath, function(key,val){ 266 | $(val).velocity({fill: "#FF0000"},{duration: 200}); 267 | }); 268 | }, 2000); 269 | } 270 | 271 | function readapt(){ 272 | mt.text('Step 4 - Readapting Forwarding Tables'); 273 | mt.velocity('fadeIn'); 274 | st.html(''); 275 | setTimeout(function(){ 276 | st.html('Measuring port usage'); 277 | st.velocity('fadeIn'); 278 | $.each(arrowsdown, function(i, val){ 279 | var d1 = Math.floor(Math.random()*(1000-200+1)+200 ); 280 | $(val).velocity('fadeIn', {duration: d1, loop: 2}); 281 | }) ; 282 | $.each(arrowsup, function(i, val){ 283 | var d1 = Math.floor(Math.random()*(1000-200+1)+200 ); 284 | $(val).velocity('fadeIn', {duration: d1, loop: 2}); 285 | }) ; 286 | }, 2000); 287 | setTimeout(function(){ 288 | st.html('Recomputing Multipath Forwarding'); 289 | st.velocity('fadeIn'); 290 | }, 6000); 291 | setTimeout(function(){ 292 | st.html('Sending rules to the switches'); 293 | st.velocity('fadeIn'); 294 | $.each(arrowsdown, function(i, val){ 295 | var d1 = Math.floor(Math.random()*(1000-200+1)+200 ); 296 | $(val).velocity('fadeIn', {duration: d1, loop: 2}); 297 | }) ; 298 | }, 9000); 299 | setTimeout(function(){ 300 | st.html('Singlepath Streaming'); 301 | st.velocity('fadeIn'); 302 | $("#link14").velocity("stop"); 303 | $("#link56").velocity("stop"); 304 | $("#link14").velocity( 305 | {fill: "#D1D0CE"}, 306 | {duration: 500} 307 | ); 308 | $("#link56").velocity( 309 | {fill: "#D1D0CE"}, 310 | {duration: 500} 311 | ); 312 | }, 12000); 313 | } 314 | 315 | function end(){ 316 | mt.text('Stop'); 317 | mt.velocity('fadeIn'); 318 | st.html('End of Demo'); 319 | st.velocity('fadeIn'); 320 | hideAll(); 321 | } 322 | 323 | 324 | hideAll(); -------------------------------------------------------------------------------- /demo/js/velocity.min.js: -------------------------------------------------------------------------------- 1 | /*! VelocityJS.org (1.2.3). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License */ 2 | /*! VelocityJS.org jQuery Shim (1.0.1). (C) 2014 The jQuery Foundation. MIT @license: en.wikipedia.org/wiki/MIT_License. */ 3 | !function(a){function b(a){var b=a.length,d=c.type(a);return"function"===d||c.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===d||0===b||"number"==typeof b&&b>0&&b-1 in a}if(!a.jQuery){var c=function(a,b){return new c.fn.init(a,b)};c.isWindow=function(a){return null!=a&&a==a.window},c.type=function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?e[g.call(a)]||"object":typeof a},c.isArray=Array.isArray||function(a){return"array"===c.type(a)},c.isPlainObject=function(a){var b;if(!a||"object"!==c.type(a)||a.nodeType||c.isWindow(a))return!1;try{if(a.constructor&&!f.call(a,"constructor")&&!f.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(d){return!1}for(b in a);return void 0===b||f.call(a,b)},c.each=function(a,c,d){var e,f=0,g=a.length,h=b(a);if(d){if(h)for(;g>f&&(e=c.apply(a[f],d),e!==!1);f++);else for(f in a)if(e=c.apply(a[f],d),e===!1)break}else if(h)for(;g>f&&(e=c.call(a[f],f,a[f]),e!==!1);f++);else for(f in a)if(e=c.call(a[f],f,a[f]),e===!1)break;return a},c.data=function(a,b,e){if(void 0===e){var f=a[c.expando],g=f&&d[f];if(void 0===b)return g;if(g&&b in g)return g[b]}else if(void 0!==b){var f=a[c.expando]||(a[c.expando]=++c.uuid);return d[f]=d[f]||{},d[f][b]=e,e}},c.removeData=function(a,b){var e=a[c.expando],f=e&&d[e];f&&c.each(b,function(a,b){delete f[b]})},c.extend=function(){var a,b,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;for("boolean"==typeof h&&(k=h,h=arguments[i]||{},i++),"object"!=typeof h&&"function"!==c.type(h)&&(h={}),i===j&&(h=this,i--);j>i;i++)if(null!=(f=arguments[i]))for(e in f)a=h[e],d=f[e],h!==d&&(k&&d&&(c.isPlainObject(d)||(b=c.isArray(d)))?(b?(b=!1,g=a&&c.isArray(a)?a:[]):g=a&&c.isPlainObject(a)?a:{},h[e]=c.extend(k,g,d)):void 0!==d&&(h[e]=d));return h},c.queue=function(a,d,e){function f(a,c){var d=c||[];return null!=a&&(b(Object(a))?!function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;)a[e++]=b[d++];if(c!==c)for(;void 0!==b[d];)a[e++]=b[d++];return a.length=e,a}(d,"string"==typeof a?[a]:a):[].push.call(d,a)),d}if(a){d=(d||"fx")+"queue";var g=c.data(a,d);return e?(!g||c.isArray(e)?g=c.data(a,d,f(e)):g.push(e),g):g||[]}},c.dequeue=function(a,b){c.each(a.nodeType?[a]:a,function(a,d){b=b||"fx";var e=c.queue(d,b),f=e.shift();"inprogress"===f&&(f=e.shift()),f&&("fx"===b&&e.unshift("inprogress"),f.call(d,function(){c.dequeue(d,b)}))})},c.fn=c.prototype={init:function(a){if(a.nodeType)return this[0]=a,this;throw new Error("Not a DOM node.")},offset:function(){var b=this[0].getBoundingClientRect?this[0].getBoundingClientRect():{top:0,left:0};return{top:b.top+(a.pageYOffset||document.scrollTop||0)-(document.clientTop||0),left:b.left+(a.pageXOffset||document.scrollLeft||0)-(document.clientLeft||0)}},position:function(){function a(){for(var a=this.offsetParent||document;a&&"html"===!a.nodeType.toLowerCase&&"static"===a.style.position;)a=a.offsetParent;return a||document}var b=this[0],a=a.apply(b),d=this.offset(),e=/^(?:body|html)$/i.test(a.nodeName)?{top:0,left:0}:c(a).offset();return d.top-=parseFloat(b.style.marginTop)||0,d.left-=parseFloat(b.style.marginLeft)||0,a.style&&(e.top+=parseFloat(a.style.borderTopWidth)||0,e.left+=parseFloat(a.style.borderLeftWidth)||0),{top:d.top-e.top,left:d.left-e.left}}};var d={};c.expando="velocity"+(new Date).getTime(),c.uuid=0;for(var e={},f=e.hasOwnProperty,g=e.toString,h="Boolean Number String Function Array Date RegExp Object Error".split(" "),i=0;ie;++e){var f=j(c,a,d);if(0===f)return c;var g=i(c,a,d)-b;c-=g/f}return c}function l(){for(var b=0;t>b;++b)x[b]=i(b*u,a,d)}function m(b,c,e){var f,g,h=0;do g=c+(e-c)/2,f=i(g,a,d)-b,f>0?e=g:c=g;while(Math.abs(f)>r&&++h=q?k(b,h):0==i?h:m(b,c,c+u)}function o(){y=!0,(a!=c||d!=e)&&l()}var p=4,q=.001,r=1e-7,s=10,t=11,u=1/(t-1),v="Float32Array"in b;if(4!==arguments.length)return!1;for(var w=0;4>w;++w)if("number"!=typeof arguments[w]||isNaN(arguments[w])||!isFinite(arguments[w]))return!1;a=Math.min(a,1),d=Math.min(d,1),a=Math.max(a,0),d=Math.max(d,0);var x=v?new Float32Array(t):new Array(t),y=!1,z=function(b){return y||o(),a===c&&d===e?b:0===b?0:1===b?1:i(n(b),c,e)};z.getControlPoints=function(){return[{x:a,y:c},{x:d,y:e}]};var A="generateBezier("+[a,c,d,e]+")";return z.toString=function(){return A},z}function j(a,b){var c=a;return p.isString(a)?t.Easings[a]||(c=!1):c=p.isArray(a)&&1===a.length?h.apply(null,a):p.isArray(a)&&2===a.length?u.apply(null,a.concat([b])):p.isArray(a)&&4===a.length?i.apply(null,a):!1,c===!1&&(c=t.Easings[t.defaults.easing]?t.defaults.easing:s),c}function k(a){if(a){var b=(new Date).getTime(),c=t.State.calls.length;c>1e4&&(t.State.calls=e(t.State.calls));for(var f=0;c>f;f++)if(t.State.calls[f]){var h=t.State.calls[f],i=h[0],j=h[2],n=h[3],o=!!n,q=null;n||(n=t.State.calls[f][3]=b-16);for(var r=Math.min((b-n)/j.duration,1),s=0,u=i.length;u>s;s++){var w=i[s],y=w.element;if(g(y)){var z=!1;if(j.display!==d&&null!==j.display&&"none"!==j.display){if("flex"===j.display){var A=["-webkit-box","-moz-box","-ms-flexbox","-webkit-flex"];m.each(A,function(a,b){v.setPropertyValue(y,"display",b)})}v.setPropertyValue(y,"display",j.display)}j.visibility!==d&&"hidden"!==j.visibility&&v.setPropertyValue(y,"visibility",j.visibility);for(var B in w)if("element"!==B){var C,D=w[B],E=p.isString(D.easing)?t.Easings[D.easing]:D.easing;if(1===r)C=D.endValue;else{var F=D.endValue-D.startValue;if(C=D.startValue+F*E(r,j,F),!o&&C===D.currentValue)continue}if(D.currentValue=C,"tween"===B)q=C;else{if(v.Hooks.registered[B]){var G=v.Hooks.getRoot(B),H=g(y).rootPropertyValueCache[G];H&&(D.rootPropertyValue=H)}var I=v.setPropertyValue(y,B,D.currentValue+(0===parseFloat(C)?"":D.unitType),D.rootPropertyValue,D.scrollData);v.Hooks.registered[B]&&(g(y).rootPropertyValueCache[G]=v.Normalizations.registered[G]?v.Normalizations.registered[G]("extract",null,I[1]):I[1]),"transform"===I[0]&&(z=!0)}}j.mobileHA&&g(y).transformCache.translate3d===d&&(g(y).transformCache.translate3d="(0px, 0px, 0px)",z=!0),z&&v.flushTransformCache(y)}}j.display!==d&&"none"!==j.display&&(t.State.calls[f][2].display=!1),j.visibility!==d&&"hidden"!==j.visibility&&(t.State.calls[f][2].visibility=!1),j.progress&&j.progress.call(h[1],h[1],r,Math.max(0,n+j.duration-b),n,q),1===r&&l(f)}}t.State.isTicking&&x(k)}function l(a,b){if(!t.State.calls[a])return!1;for(var c=t.State.calls[a][0],e=t.State.calls[a][1],f=t.State.calls[a][2],h=t.State.calls[a][4],i=!1,j=0,k=c.length;k>j;j++){var l=c[j].element;if(b||f.loop||("none"===f.display&&v.setPropertyValue(l,"display",f.display),"hidden"===f.visibility&&v.setPropertyValue(l,"visibility",f.visibility)),f.loop!==!0&&(m.queue(l)[1]===d||!/\.velocityQueueEntryFlag/i.test(m.queue(l)[1]))&&g(l)){g(l).isAnimating=!1,g(l).rootPropertyValueCache={};var n=!1;m.each(v.Lists.transforms3D,function(a,b){var c=/^scale/.test(b)?1:0,e=g(l).transformCache[b];g(l).transformCache[b]!==d&&new RegExp("^\\("+c+"[^.]").test(e)&&(n=!0,delete g(l).transformCache[b])}),f.mobileHA&&(n=!0,delete g(l).transformCache.translate3d),n&&v.flushTransformCache(l),v.Values.removeClass(l,"velocity-animating")}if(!b&&f.complete&&!f.loop&&j===k-1)try{f.complete.call(e,e)}catch(o){setTimeout(function(){throw o},1)}h&&f.loop!==!0&&h(e),g(l)&&f.loop===!0&&!b&&(m.each(g(l).tweensContainer,function(a,b){/^rotate/.test(a)&&360===parseFloat(b.endValue)&&(b.endValue=0,b.startValue=360),/^backgroundPosition/.test(a)&&100===parseFloat(b.endValue)&&"%"===b.unitType&&(b.endValue=0,b.startValue=100)}),t(l,"reverse",{loop:!0,delay:f.delay})),f.queue!==!1&&m.dequeue(l,f.queue)}t.State.calls[a]=!1;for(var p=0,q=t.State.calls.length;q>p;p++)if(t.State.calls[p]!==!1){i=!0;break}i===!1&&(t.State.isTicking=!1,delete t.State.calls,t.State.calls=[])}var m,n=function(){if(c.documentMode)return c.documentMode;for(var a=7;a>4;a--){var b=c.createElement("div");if(b.innerHTML="",b.getElementsByTagName("span").length)return b=null,a}return d}(),o=function(){var a=0;return b.webkitRequestAnimationFrame||b.mozRequestAnimationFrame||function(b){var c,d=(new Date).getTime();return c=Math.max(0,16-(d-a)),a=d+c,setTimeout(function(){b(d+c)},c)}}(),p={isString:function(a){return"string"==typeof a},isArray:Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)},isFunction:function(a){return"[object Function]"===Object.prototype.toString.call(a)},isNode:function(a){return a&&a.nodeType},isNodeList:function(a){return"object"==typeof a&&/^\[object (HTMLCollection|NodeList|Object)\]$/.test(Object.prototype.toString.call(a))&&a.length!==d&&(0===a.length||"object"==typeof a[0]&&a[0].nodeType>0)},isWrapped:function(a){return a&&(a.jquery||b.Zepto&&b.Zepto.zepto.isZ(a))},isSVG:function(a){return b.SVGElement&&a instanceof b.SVGElement},isEmptyObject:function(a){for(var b in a)return!1;return!0}},q=!1;if(a.fn&&a.fn.jquery?(m=a,q=!0):m=b.Velocity.Utilities,8>=n&&!q)throw new Error("Velocity: IE8 and below require jQuery to be loaded before Velocity.");if(7>=n)return void(jQuery.fn.velocity=jQuery.fn.animate);var r=400,s="swing",t={State:{isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),isAndroid:/Android/i.test(navigator.userAgent),isGingerbread:/Android 2\.3\.[3-7]/i.test(navigator.userAgent),isChrome:b.chrome,isFirefox:/Firefox/i.test(navigator.userAgent),prefixElement:c.createElement("div"),prefixMatches:{},scrollAnchor:null,scrollPropertyLeft:null,scrollPropertyTop:null,isTicking:!1,calls:[]},CSS:{},Utilities:m,Redirects:{},Easings:{},Promise:b.Promise,defaults:{queue:"",duration:r,easing:s,begin:d,complete:d,progress:d,display:d,visibility:d,loop:!1,delay:!1,mobileHA:!0,_cacheValues:!0},init:function(a){m.data(a,"velocity",{isSVG:p.isSVG(a),isAnimating:!1,computedStyle:null,tweensContainer:null,rootPropertyValueCache:{},transformCache:{}})},hook:null,mock:!1,version:{major:1,minor:2,patch:2},debug:!1};b.pageYOffset!==d?(t.State.scrollAnchor=b,t.State.scrollPropertyLeft="pageXOffset",t.State.scrollPropertyTop="pageYOffset"):(t.State.scrollAnchor=c.documentElement||c.body.parentNode||c.body,t.State.scrollPropertyLeft="scrollLeft",t.State.scrollPropertyTop="scrollTop");var u=function(){function a(a){return-a.tension*a.x-a.friction*a.v}function b(b,c,d){var e={x:b.x+d.dx*c,v:b.v+d.dv*c,tension:b.tension,friction:b.friction};return{dx:e.v,dv:a(e)}}function c(c,d){var e={dx:c.v,dv:a(c)},f=b(c,.5*d,e),g=b(c,.5*d,f),h=b(c,d,g),i=1/6*(e.dx+2*(f.dx+g.dx)+h.dx),j=1/6*(e.dv+2*(f.dv+g.dv)+h.dv);return c.x=c.x+i*d,c.v=c.v+j*d,c}return function d(a,b,e){var f,g,h,i={x:-1,v:0,tension:null,friction:null},j=[0],k=0,l=1e-4,m=.016;for(a=parseFloat(a)||500,b=parseFloat(b)||20,e=e||null,i.tension=a,i.friction=b,f=null!==e,f?(k=d(a,b),g=k/e*m):g=m;;)if(h=c(h||i,g),j.push(1+h.x),k+=16,!(Math.abs(h.x)>l&&Math.abs(h.v)>l))break;return f?function(a){return j[a*(j.length-1)|0]}:k}}();t.Easings={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},spring:function(a){return 1-Math.cos(4.5*a*Math.PI)*Math.exp(6*-a)}},m.each([["ease",[.25,.1,.25,1]],["ease-in",[.42,0,1,1]],["ease-out",[0,0,.58,1]],["ease-in-out",[.42,0,.58,1]],["easeInSine",[.47,0,.745,.715]],["easeOutSine",[.39,.575,.565,1]],["easeInOutSine",[.445,.05,.55,.95]],["easeInQuad",[.55,.085,.68,.53]],["easeOutQuad",[.25,.46,.45,.94]],["easeInOutQuad",[.455,.03,.515,.955]],["easeInCubic",[.55,.055,.675,.19]],["easeOutCubic",[.215,.61,.355,1]],["easeInOutCubic",[.645,.045,.355,1]],["easeInQuart",[.895,.03,.685,.22]],["easeOutQuart",[.165,.84,.44,1]],["easeInOutQuart",[.77,0,.175,1]],["easeInQuint",[.755,.05,.855,.06]],["easeOutQuint",[.23,1,.32,1]],["easeInOutQuint",[.86,0,.07,1]],["easeInExpo",[.95,.05,.795,.035]],["easeOutExpo",[.19,1,.22,1]],["easeInOutExpo",[1,0,0,1]],["easeInCirc",[.6,.04,.98,.335]],["easeOutCirc",[.075,.82,.165,1]],["easeInOutCirc",[.785,.135,.15,.86]]],function(a,b){t.Easings[b[0]]=i.apply(null,b[1])});var v=t.CSS={RegEx:{isHex:/^#([A-f\d]{3}){1,2}$/i,valueUnwrap:/^[A-z]+\((.*)\)$/i,wrappedValueAlreadyExtracted:/[0-9.]+ [0-9.]+ [0-9.]+( [0-9.]+)?/,valueSplit:/([A-z]+\(.+\))|(([A-z0-9#-.]+?)(?=\s|$))/gi},Lists:{colors:["fill","stroke","stopColor","color","backgroundColor","borderColor","borderTopColor","borderRightColor","borderBottomColor","borderLeftColor","outlineColor"],transformsBase:["translateX","translateY","scale","scaleX","scaleY","skewX","skewY","rotateZ"],transforms3D:["transformPerspective","translateZ","scaleZ","rotateX","rotateY"]},Hooks:{templates:{textShadow:["Color X Y Blur","black 0px 0px 0px"],boxShadow:["Color X Y Blur Spread","black 0px 0px 0px 0px"],clip:["Top Right Bottom Left","0px 0px 0px 0px"],backgroundPosition:["X Y","0% 0%"],transformOrigin:["X Y Z","50% 50% 0px"],perspectiveOrigin:["X Y","50% 50%"]},registered:{},register:function(){for(var a=0;a=n)switch(a){case"name":return"filter";case"extract":var d=c.toString().match(/alpha\(opacity=(.*)\)/i);return c=d?d[1]/100:1;case"inject":return b.style.zoom=1,parseFloat(c)>=1?"":"alpha(opacity="+parseInt(100*parseFloat(c),10)+")"}else switch(a){case"name":return"opacity";case"extract":return c;case"inject":return c}}},register:function(){9>=n||t.State.isGingerbread||(v.Lists.transformsBase=v.Lists.transformsBase.concat(v.Lists.transforms3D));for(var a=0;ae&&(e=1),f=!/(\d)$/i.test(e);break;case"skew":f=!/(deg|\d)$/i.test(e);break;case"rotate":f=!/(deg|\d)$/i.test(e)}return f||(g(c).transformCache[b]="("+e+")"),g(c).transformCache[b]}}}();for(var a=0;a=n||3!==f.split(" ").length||(f+=" 1"),f;case"inject":return 8>=n?4===e.split(" ").length&&(e=e.split(/\s+/).slice(0,3).join(" ")):3===e.split(" ").length&&(e+=" 1"),(8>=n?"rgb":"rgba")+"("+e.replace(/\s+/g,",").replace(/\.(\d)+(?=,)/g,"")+")"}}}()}},Names:{camelCase:function(a){return a.replace(/-(\w)/g,function(a,b){return b.toUpperCase()})},SVGAttribute:function(a){var b="width|height|x|y|cx|cy|r|rx|ry|x1|x2|y1|y2";return(n||t.State.isAndroid&&!t.State.isChrome)&&(b+="|transform"),new RegExp("^("+b+")$","i").test(a)},prefixCheck:function(a){if(t.State.prefixMatches[a])return[t.State.prefixMatches[a],!0];for(var b=["","Webkit","Moz","ms","O"],c=0,d=b.length;d>c;c++){var e;if(e=0===c?a:b[c]+a.replace(/^\w/,function(a){return a.toUpperCase()}),p.isString(t.State.prefixElement.style[e]))return t.State.prefixMatches[a]=e,[e,!0]}return[a,!1]}},Values:{hexToRgb:function(a){var b,c=/^#?([a-f\d])([a-f\d])([a-f\d])$/i,d=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;return a=a.replace(c,function(a,b,c,d){return b+b+c+c+d+d}),b=d.exec(a),b?[parseInt(b[1],16),parseInt(b[2],16),parseInt(b[3],16)]:[0,0,0]},isCSSNullValue:function(a){return 0==a||/^(none|auto|transparent|(rgba\(0, ?0, ?0, ?0\)))$/i.test(a)},getUnitType:function(a){return/^(rotate|skew)/i.test(a)?"deg":/(^(scale|scaleX|scaleY|scaleZ|alpha|flexGrow|flexHeight|zIndex|fontWeight)$)|((opacity|red|green|blue|alpha)$)/i.test(a)?"":"px"},getDisplayType:function(a){var b=a&&a.tagName.toString().toLowerCase();return/^(b|big|i|small|tt|abbr|acronym|cite|code|dfn|em|kbd|strong|samp|var|a|bdo|br|img|map|object|q|script|span|sub|sup|button|input|label|select|textarea)$/i.test(b)?"inline":/^(li)$/i.test(b)?"list-item":/^(tr)$/i.test(b)?"table-row":/^(table)$/i.test(b)?"table":/^(tbody)$/i.test(b)?"table-row-group":"block"},addClass:function(a,b){a.classList?a.classList.add(b):a.className+=(a.className.length?" ":"")+b},removeClass:function(a,b){a.classList?a.classList.remove(b):a.className=a.className.toString().replace(new RegExp("(^|\\s)"+b.split(" ").join("|")+"(\\s|$)","gi")," ")}},getPropertyValue:function(a,c,e,f){function h(a,c){function e(){j&&v.setPropertyValue(a,"display","none")}var i=0;if(8>=n)i=m.css(a,c);else{var j=!1;if(/^(width|height)$/.test(c)&&0===v.getPropertyValue(a,"display")&&(j=!0,v.setPropertyValue(a,"display",v.Values.getDisplayType(a))),!f){if("height"===c&&"border-box"!==v.getPropertyValue(a,"boxSizing").toString().toLowerCase()){var k=a.offsetHeight-(parseFloat(v.getPropertyValue(a,"borderTopWidth"))||0)-(parseFloat(v.getPropertyValue(a,"borderBottomWidth"))||0)-(parseFloat(v.getPropertyValue(a,"paddingTop"))||0)-(parseFloat(v.getPropertyValue(a,"paddingBottom"))||0);return e(),k}if("width"===c&&"border-box"!==v.getPropertyValue(a,"boxSizing").toString().toLowerCase()){var l=a.offsetWidth-(parseFloat(v.getPropertyValue(a,"borderLeftWidth"))||0)-(parseFloat(v.getPropertyValue(a,"borderRightWidth"))||0)-(parseFloat(v.getPropertyValue(a,"paddingLeft"))||0)-(parseFloat(v.getPropertyValue(a,"paddingRight"))||0);return e(),l}}var o;o=g(a)===d?b.getComputedStyle(a,null):g(a).computedStyle?g(a).computedStyle:g(a).computedStyle=b.getComputedStyle(a,null),"borderColor"===c&&(c="borderTopColor"),i=9===n&&"filter"===c?o.getPropertyValue(c):o[c],(""===i||null===i)&&(i=a.style[c]),e()}if("auto"===i&&/^(top|right|bottom|left)$/i.test(c)){var p=h(a,"position");("fixed"===p||"absolute"===p&&/top|left/i.test(c))&&(i=m(a).position()[c]+"px")}return i}var i;if(v.Hooks.registered[c]){var j=c,k=v.Hooks.getRoot(j);e===d&&(e=v.getPropertyValue(a,v.Names.prefixCheck(k)[0])),v.Normalizations.registered[k]&&(e=v.Normalizations.registered[k]("extract",a,e)),i=v.Hooks.extractValue(j,e)}else if(v.Normalizations.registered[c]){var l,o;l=v.Normalizations.registered[c]("name",a),"transform"!==l&&(o=h(a,v.Names.prefixCheck(l)[0]),v.Values.isCSSNullValue(o)&&v.Hooks.templates[c]&&(o=v.Hooks.templates[c][1])),i=v.Normalizations.registered[c]("extract",a,o)}if(!/^[\d-]/.test(i))if(g(a)&&g(a).isSVG&&v.Names.SVGAttribute(c))if(/^(height|width)$/i.test(c))try{i=a.getBBox()[c]}catch(p){i=0}else i=a.getAttribute(c);else i=h(a,v.Names.prefixCheck(c)[0]);return v.Values.isCSSNullValue(i)&&(i=0),t.debug>=2&&console.log("Get "+c+": "+i),i},setPropertyValue:function(a,c,d,e,f){var h=c;if("scroll"===c)f.container?f.container["scroll"+f.direction]=d:"Left"===f.direction?b.scrollTo(d,f.alternateValue):b.scrollTo(f.alternateValue,d);else if(v.Normalizations.registered[c]&&"transform"===v.Normalizations.registered[c]("name",a))v.Normalizations.registered[c]("inject",a,d),h="transform",d=g(a).transformCache[c];else{if(v.Hooks.registered[c]){var i=c,j=v.Hooks.getRoot(c);e=e||v.getPropertyValue(a,j),d=v.Hooks.injectValue(i,d,e),c=j}if(v.Normalizations.registered[c]&&(d=v.Normalizations.registered[c]("inject",a,d),c=v.Normalizations.registered[c]("name",a)),h=v.Names.prefixCheck(c)[0],8>=n)try{a.style[h]=d}catch(k){t.debug&&console.log("Browser does not support ["+d+"] for ["+h+"]")}else g(a)&&g(a).isSVG&&v.Names.SVGAttribute(c)?a.setAttribute(c,d):a.style[h]=d;t.debug>=2&&console.log("Set "+c+" ("+h+"): "+d)}return[h,d]},flushTransformCache:function(a){function b(b){return parseFloat(v.getPropertyValue(a,b))}var c="";if((n||t.State.isAndroid&&!t.State.isChrome)&&g(a).isSVG){var d={translate:[b("translateX"),b("translateY")],skewX:[b("skewX")],skewY:[b("skewY")],scale:1!==b("scale")?[b("scale"),b("scale")]:[b("scaleX"),b("scaleY")],rotate:[b("rotateZ"),0,0]};m.each(g(a).transformCache,function(a){/^translate/i.test(a)?a="translate":/^scale/i.test(a)?a="scale":/^rotate/i.test(a)&&(a="rotate"),d[a]&&(c+=a+"("+d[a].join(" ")+") ",delete d[a])})}else{var e,f;m.each(g(a).transformCache,function(b){return e=g(a).transformCache[b],"transformPerspective"===b?(f=e,!0):(9===n&&"rotateZ"===b&&(b="rotate"),void(c+=b+e+" "))}),f&&(c="perspective"+f+" "+c)}v.setPropertyValue(a,"transform",c)}};v.Hooks.register(),v.Normalizations.register(),t.hook=function(a,b,c){var e=d;return a=f(a),m.each(a,function(a,f){if(g(f)===d&&t.init(f),c===d)e===d&&(e=t.CSS.getPropertyValue(f,b));else{var h=t.CSS.setPropertyValue(f,b,c);"transform"===h[0]&&t.CSS.flushTransformCache(f),e=h}}),e};var w=function(){function a(){return h?B.promise||null:i}function e(){function a(){function a(a,b){var c=d,e=d,g=d;return p.isArray(a)?(c=a[0],!p.isArray(a[1])&&/^[\d-]/.test(a[1])||p.isFunction(a[1])||v.RegEx.isHex.test(a[1])?g=a[1]:(p.isString(a[1])&&!v.RegEx.isHex.test(a[1])||p.isArray(a[1]))&&(e=b?a[1]:j(a[1],h.duration),a[2]!==d&&(g=a[2]))):c=a,b||(e=e||h.easing),p.isFunction(c)&&(c=c.call(f,y,x)),p.isFunction(g)&&(g=g.call(f,y,x)),[c||0,e,g]}function l(a,b){var c,d;return d=(b||"0").toString().toLowerCase().replace(/[%A-z]+$/,function(a){return c=a,""}),c||(c=v.Values.getUnitType(a)),[d,c]}function n(){var a={myParent:f.parentNode||c.body,position:v.getPropertyValue(f,"position"),fontSize:v.getPropertyValue(f,"fontSize")},d=a.position===I.lastPosition&&a.myParent===I.lastParent,e=a.fontSize===I.lastFontSize;I.lastParent=a.myParent,I.lastPosition=a.position,I.lastFontSize=a.fontSize;var h=100,i={};if(e&&d)i.emToPx=I.lastEmToPx,i.percentToPxWidth=I.lastPercentToPxWidth,i.percentToPxHeight=I.lastPercentToPxHeight;else{var j=g(f).isSVG?c.createElementNS("http://www.w3.org/2000/svg","rect"):c.createElement("div");t.init(j),a.myParent.appendChild(j),m.each(["overflow","overflowX","overflowY"],function(a,b){t.CSS.setPropertyValue(j,b,"hidden")}),t.CSS.setPropertyValue(j,"position",a.position),t.CSS.setPropertyValue(j,"fontSize",a.fontSize),t.CSS.setPropertyValue(j,"boxSizing","content-box"),m.each(["minWidth","maxWidth","width","minHeight","maxHeight","height"],function(a,b){t.CSS.setPropertyValue(j,b,h+"%")}),t.CSS.setPropertyValue(j,"paddingLeft",h+"em"),i.percentToPxWidth=I.lastPercentToPxWidth=(parseFloat(v.getPropertyValue(j,"width",null,!0))||1)/h,i.percentToPxHeight=I.lastPercentToPxHeight=(parseFloat(v.getPropertyValue(j,"height",null,!0))||1)/h,i.emToPx=I.lastEmToPx=(parseFloat(v.getPropertyValue(j,"paddingLeft"))||1)/h,a.myParent.removeChild(j)}return null===I.remToPx&&(I.remToPx=parseFloat(v.getPropertyValue(c.body,"fontSize"))||16),null===I.vwToPx&&(I.vwToPx=parseFloat(b.innerWidth)/100,I.vhToPx=parseFloat(b.innerHeight)/100),i.remToPx=I.remToPx,i.vwToPx=I.vwToPx,i.vhToPx=I.vhToPx,t.debug>=1&&console.log("Unit ratios: "+JSON.stringify(i),f),i}if(h.begin&&0===y)try{h.begin.call(o,o)}catch(r){setTimeout(function(){throw r},1)}if("scroll"===C){var u,w,z,A=/^x$/i.test(h.axis)?"Left":"Top",D=parseFloat(h.offset)||0;h.container?p.isWrapped(h.container)||p.isNode(h.container)?(h.container=h.container[0]||h.container,u=h.container["scroll"+A],z=u+m(f).position()[A.toLowerCase()]+D):h.container=null:(u=t.State.scrollAnchor[t.State["scrollProperty"+A]],w=t.State.scrollAnchor[t.State["scrollProperty"+("Left"===A?"Top":"Left")]],z=m(f).offset()[A.toLowerCase()]+D),i={scroll:{rootPropertyValue:!1,startValue:u,currentValue:u,endValue:z,unitType:"",easing:h.easing,scrollData:{container:h.container,direction:A,alternateValue:w}},element:f},t.debug&&console.log("tweensContainer (scroll): ",i.scroll,f)}else if("reverse"===C){if(!g(f).tweensContainer)return void m.dequeue(f,h.queue);"none"===g(f).opts.display&&(g(f).opts.display="auto"),"hidden"===g(f).opts.visibility&&(g(f).opts.visibility="visible"),g(f).opts.loop=!1,g(f).opts.begin=null,g(f).opts.complete=null,s.easing||delete h.easing,s.duration||delete h.duration,h=m.extend({},g(f).opts,h);var E=m.extend(!0,{},g(f).tweensContainer);for(var F in E)if("element"!==F){var G=E[F].startValue;E[F].startValue=E[F].currentValue=E[F].endValue,E[F].endValue=G,p.isEmptyObject(s)||(E[F].easing=h.easing),t.debug&&console.log("reverse tweensContainer ("+F+"): "+JSON.stringify(E[F]),f)}i=E}else if("start"===C){var E;g(f).tweensContainer&&g(f).isAnimating===!0&&(E=g(f).tweensContainer),m.each(q,function(b,c){if(RegExp("^"+v.Lists.colors.join("$|^")+"$").test(b)){var e=a(c,!0),f=e[0],g=e[1],h=e[2];if(v.RegEx.isHex.test(f)){for(var i=["Red","Green","Blue"],j=v.Values.hexToRgb(f),k=h?v.Values.hexToRgb(h):d,l=0;lL;L++){var M={delay:E.delay,progress:E.progress};L===K-1&&(M.display=E.display,M.visibility=E.visibility,M.complete=E.complete),w(o,"reverse",M)}return a()}};t=m.extend(w,t),t.animate=w;var x=b.requestAnimationFrame||o;return t.State.isMobile||c.hidden===d||c.addEventListener("visibilitychange",function(){c.hidden?(x=function(a){return setTimeout(function(){a(!0)},16)},k()):x=b.requestAnimationFrame||o}),a.Velocity=t,a!==b&&(a.fn.velocity=w,a.fn.velocity.defaults=t.defaults),m.each(["Down","Up"],function(a,b){t.Redirects["slide"+b]=function(a,c,e,f,g,h){var i=m.extend({},c),j=i.begin,k=i.complete,l={height:"",marginTop:"",marginBottom:"",paddingTop:"",paddingBottom:""},n={};i.display===d&&(i.display="Down"===b?"inline"===t.CSS.Values.getDisplayType(a)?"inline-block":"block":"none"),i.begin=function(){j&&j.call(g,g);for(var c in l){n[c]=a.style[c];var d=t.CSS.getPropertyValue(a,c);l[c]="Down"===b?[d,0]:[0,d]}n.overflow=a.style.overflow,a.style.overflow="hidden"},i.complete=function(){for(var b in n)a.style[b]=n[b];k&&k.call(g,g),h&&h.resolver(g)},t(a,l,i)}}),m.each(["In","Out"],function(a,b){t.Redirects["fade"+b]=function(a,c,e,f,g,h){var i=m.extend({},c),j={opacity:"In"===b?1:0},k=i.complete;i.complete=e!==f-1?i.begin=null:function(){k&&k.call(g,g),h&&h.resolver(g)},i.display===d&&(i.display="In"===b?"auto":"none"),t(this,j,i)}}),t}(window.jQuery||window.Zepto||window,window,document)}); 5 | -------------------------------------------------------------------------------- /network_create.sh: -------------------------------------------------------------------------------- 1 | mn --custom topologies.py --topo evaluation --mac --link=tc --switch=ovsk,datapath=kernel --controller=remote 2 | 3 | -------------------------------------------------------------------------------- /start_controller.sh: -------------------------------------------------------------------------------- 1 | # Deleting first the flows which were already set on our topology 2 | 3 | if [[ $1 = "c" ]]; then 4 | ovs-ofctl -O OpenFlow13 del-flows bej 5 | ovs-ofctl -O OpenFlow13 del-flows haw 6 | ovs-ofctl -O OpenFlow13 del-flows hkg 7 | ovs-ofctl -O OpenFlow13 del-flows man 8 | ovs-ofctl -O OpenFlow13 del-flows sha 9 | ovs-ofctl -O OpenFlow13 del-flows sin 10 | ovs-ofctl -O OpenFlow13 del-flows syd 11 | ovs-ofctl -O OpenFlow13 del-flows tok 12 | fi 13 | 14 | # Start controller 15 | ryu-manager --observe-links --enable-debugger controller/mpsdn_controller.py 16 | -------------------------------------------------------------------------------- /topologies.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | 4 | ''' 5 | Evaluation topology for the controller 6 | ''' 7 | 8 | 9 | from mininet.topo import Topo 10 | from mininet.net import Mininet 11 | from mininet.link import TCLink 12 | from mininet.node import RemoteController, OVSKernelSwitch 13 | from mininet.cli import CLI 14 | 15 | __author__ = 'Dario Banfi' 16 | __license__ = 'Apache 2.0' 17 | __version__ = '1.0' 18 | __email__ = 'dario.banfi@tum.de' 19 | 20 | 21 | class EvaluationTopo(Topo): 22 | 23 | def __init__(self, **opts): 24 | Topo.__init__(self, **opts) 25 | 26 | bej_host = self.addHost('bej_h') 27 | haw_host = self.addHost('haw_h') 28 | hkg_host = self.addHost('hkg_h') 29 | man_host = self.addHost('man_h') 30 | sha_host = self.addHost('sha_h') 31 | sin_host = self.addHost('sin_h') 32 | syd_host = self.addHost('syd_h') 33 | tok_host = self.addHost('tok_h') 34 | 35 | bej = self.addSwitch( 36 | 'bej', dpid='0000000000000001', protocols='OpenFlow13') 37 | haw = self.addSwitch( 38 | 'haw', dpid='0000000000000002', protocols='OpenFlow13') 39 | hkg = self.addSwitch( 40 | 'hkg', dpid='0000000000000003', protocols='OpenFlow13') 41 | man = self.addSwitch( 42 | 'man', dpid='0000000000000004', protocols='OpenFlow13') 43 | sha = self.addSwitch( 44 | 'sha', dpid='0000000000000005', protocols='OpenFlow13') 45 | sin = self.addSwitch( 46 | 'sin', dpid='0000000000000006', protocols='OpenFlow13') 47 | syd = self.addSwitch( 48 | 'syd', dpid='0000000000000007', protocols='OpenFlow13') 49 | tok = self.addSwitch( 50 | 'tok', dpid='0000000000000008', protocols='OpenFlow13') 51 | 52 | self.addLink(syd, haw, port1=1, port2=1, delay='75ms', bw=30, loss=0) 53 | self.addLink(man, haw, port1=1, port2=2, delay='75ms', bw=30, loss=0) 54 | self.addLink(tok, haw, port1=1, port2=3, delay='75ms', bw=30, loss=0) 55 | 56 | self.addLink(syd, sin, port1=2, port2=1, delay='50ms', bw=30, loss=0) 57 | 58 | self.addLink(sin, man, port1=2, port2=2, delay='10ms', bw=10, loss=0) 59 | self.addLink(sin, tok, port1=3, port2=2, delay='10ms', bw=10, loss=0) 60 | self.addLink(sin, hkg, port1=4, port2=1, delay='10ms', bw=10, loss=0) 61 | self.addLink(man, hkg, port1=3, port2=2, delay='10ms', bw=10, loss=0) 62 | self.addLink(hkg, sha, port1=3, port2=1, delay='10ms', bw=10, loss=0) 63 | self.addLink(hkg, bej, port1=4, port2=1, delay='10ms', bw=10, loss=0) 64 | self.addLink(sha, bej, port1=2, port2=2, delay='10ms', bw=10, loss=0) 65 | self.addLink(sha, tok, port1=3, port2=3, delay='10ms', bw=10, loss=0) 66 | 67 | self.addLink(syd, tok, port1=3, port2=4, delay='60ms', bw=20, loss=0) 68 | 69 | self.addLink(bej, bej_host) 70 | self.addLink(haw, haw_host) 71 | self.addLink(hkg, hkg_host) 72 | self.addLink(man, man_host) 73 | self.addLink(sha, sha_host) 74 | self.addLink(sin, sin_host) 75 | self.addLink(syd, syd_host) 76 | self.addLink(tok, tok_host) 77 | 78 | 79 | topos = {'evaluation': (lambda: EvaluationTopo())} 80 | --------------------------------------------------------------------------------