1 """Openstack manage component of PG Sandman.
4 :synopsis: Module containing the OpenstackManage class.
5 .. moduleauthor: PG Sandman
16 from emuvim
.api
.openstack
.resources
import Net
, Port
17 from mininet
.node
import OVSSwitch
, RemoteController
, Node
20 class OpenstackManage(object):
22 OpenstackManage is a singleton and management component for the emulator.
23 It is the brain of the Openstack component and manages everything that is not datacenter specific like
24 network chains or load balancers.
29 if OpenstackManage
.__instance
is None:
30 OpenstackManage
.__instance
= object.__new
__(cls
)
31 return OpenstackManage
.__instance
33 def __init__(self
, ip
="0.0.0.0", port
=4000):
34 # we are a singleton, only initialize once!
35 self
.lock
= threading
.Lock()
37 if hasattr(self
, "init"):
41 self
.endpoints
= dict()
47 # to keep track which src_vnf(input port on the switch) handles a load balancer
48 self
.lb_flow_cookies
= dict()
49 self
.chain_flow_cookies
= dict()
51 # for the visualization also store the complete chain data incl. paths
52 self
.full_chain_data
= dict()
53 self
.full_lb_data
= dict()
55 # flow groups could be handled for each switch separately, but this global group counter should be easier to
56 # debug and to maintain
57 self
.flow_groups
= dict()
59 # we want one global chain api. this should not be datacenter dependent!
60 self
.chain
= chain_api
.ChainApi(ip
, port
, self
)
61 self
.thread
= threading
.Thread(target
=self
.chain
._start
_flask
, args
=())
62 self
.thread
.daemon
= True
63 self
.thread
.name
= self
.chain
.__class
__
66 # floating ip network setup
67 self
.floating_switch
= None
68 self
.floating_network
= None
69 self
.floating_netmask
= "192.168.100.0/24"
70 self
.floating_nodes
= dict()
71 self
.floating_cookies
= dict()
72 self
.floating_intf
= None
73 self
.floating_links
= dict()
83 self
.init_floating_network()
86 def init_floating_network(self
):
88 Initialize the floating network component for the emulator.
89 Will not do anything if already initialized.
91 if self
.net
is not None and self
.floating_switch
is None:
92 # create a floating network
93 fn
= self
.floating_network
= Net("default")
94 fn
.id = str(uuid
.uuid4())
95 fn
.set_cidr(self
.floating_netmask
)
98 fn
.subnet_id
= str(uuid
.uuid4())
99 fn
.subnet_name
= fn
.name
+ "-sub"
101 # create a port for the host
102 port
= Port("root-port")
103 #port.id = str(uuid.uuid4())
104 port
.net_name
= fn
.name
107 root_ip
= fn
.get_new_ip_address(port
.name
)
108 port
.ip_address
= root_ip
109 # floating ip network setup
110 # wierd way of getting a datacenter object
111 first_dc
= self
.net
.dcs
.values()[0]
112 # set a dpid for the switch. for this we have to get the id of the next possible dc
113 self
.floating_switch
= self
.net
.addSwitch("fs1", dpid
=hex(first_dc
._get
_next
_dc
_dpid
())[2:])
114 # this is the interface appearing on the physical host
115 self
.floating_root
= Node('root', inNamespace
=False)
116 self
.net
.hosts
.append(self
.floating_root
)
117 self
.net
.nameToNode
['root'] = self
.floating_root
118 self
.floating_intf
= self
.net
.addLink(self
.floating_root
, self
.floating_switch
).intf1
119 self
.floating_root
.setIP(root_ip
, intf
=self
.floating_intf
)
120 self
.floating_nodes
[(self
.floating_root
.name
, root_ip
)] = self
.floating_root
123 def stop_floating_network(self
):
125 self
.floating_switch
= None
127 def add_endpoint(self
, ep
):
129 Registers an openstack endpoint with manage
131 :param ep: Openstack API endpoint
132 :type ep: :class:`heat.openstack_api_endpoint`
134 key
= "%s:%s" % (ep
.ip
, ep
.port
)
135 self
.endpoints
[key
] = ep
137 def get_cookie(self
):
139 Get an unused cookie.
144 cookie
= int(max(self
.cookies
) + 1)
145 self
.cookies
.add(cookie
)
148 def get_flow_group(self
, src_vnf_name
, src_vnf_interface
):
150 Gets free group that is not currently used by any other flow for the specified interface / VNF.
152 :param src_vnf_name: Source VNF name
153 :type src_vnf_name: ``str``
154 :param src_vnf_interface: Source VNF interface name
155 :type src_vnf_interface: ``str``
156 :return: Flow group identifier.
159 if (src_vnf_name
, src_vnf_interface
) not in self
.flow_groups
:
160 grp
= int(len(self
.flow_groups
) + 1)
161 self
.flow_groups
[(src_vnf_name
, src_vnf_interface
)] = grp
163 grp
= self
.flow_groups
[(src_vnf_name
, src_vnf_interface
)]
166 def check_vnf_intf_pair(self
, vnf_name
, vnf_intf_name
):
168 Checks if a VNF exists and has the given interface
170 :param vnf_name: Name of the VNF to be checked
171 :type vnf_name: ``str``
172 :param vnf_intf_name: Name of the interface that belongst to the VNF
173 :type vnf_intf_name: ``str``
174 :return: ``True`` if it is valid pair, else ``False``
178 if vnf_name
in self
.net
:
179 vnf
= self
.net
.getNodeByName(vnf_name
)
180 return vnf_intf_name
in vnf
.nameToIntf
182 def network_action_start(self
, vnf_src_name
, vnf_dst_name
, **kwargs
):
184 Starts a network chain for a source destination pair
186 :param vnf_src_name: Name of the source VNF
187 :type vnf_src_name: ``str``
188 :param vnf_dst_name: Name of the source VNF interface
189 :type vnf_dst_name: ``str``
190 :param \**kwargs: See below
193 * *vnf_src_interface* (``str``): Name of source interface.
194 * *vnf_dst_interface* (``str``): Name of destination interface.
195 * *weight* (``int``): This value is fed into the shortest path computation if no path is specified.
196 * *match* (``str``): A custom match entry for the openflow flow rules. Only vlanid or port possible.
197 * *bidirectional* (``bool``): If set the chain will be set in both directions, else it will just set up \
198 from source to destination.
199 * *cookie* (``int``): Cookie value used by openflow. Used to identify the flows in the switches to be \
200 able to modify the correct flows.
201 * *no_route* (``bool``): If set a layer 3 route to the target interface will not be set up.
202 :return: The cookie chosen for the flow.
206 vnf_src_interface
= kwargs
.get('vnf_src_interface')
207 vnf_dst_interface
= kwargs
.get('vnf_dst_interface')
208 layer2
= kwargs
.get('layer2', True)
209 match
= kwargs
.get('match')
210 flow
= (vnf_src_name
, vnf_src_interface
, vnf_dst_name
, vnf_dst_interface
)
211 if flow
in self
.chain_flow_cookies
:
212 raise Exception("There is already a chain at the specified src/dst pair!")
213 # set up a layer 2 chain, this allows multiple chains for the same interface
214 src_node
= self
.net
.getNodeByName(vnf_src_name
)
215 dst_node
= self
.net
.getNodeByName(vnf_dst_name
)
216 dst_intf
= dst_node
.intf(vnf_dst_interface
)
218 switch
, inport
= self
._get
_connected
_switch
_data
(vnf_src_name
, vnf_src_interface
)
219 self
.setup_arp_reply_at(switch
, inport
, dst_intf
.IP(), dst_intf
.MAC())
220 if isinstance(match
, str):
221 match
+= ",dl_dst=%s" % dst_intf
.MAC()
223 match
= "dl_dst=%s" % dst_intf
.MAC()
225 cookie
= kwargs
.get('cookie', self
.get_cookie())
226 self
.cookies
.add(cookie
)
227 c
= self
.net
.setChain(
228 vnf_src_name
, vnf_dst_name
,
229 vnf_src_interface
=vnf_src_interface
,
230 vnf_dst_interface
=vnf_dst_interface
,
232 weight
=kwargs
.get('weight'),
236 path
=kwargs
.get('path'))
238 # to keep this logic seperate of the core son-emu do the housekeeping here
240 data
["src_vnf"] = vnf_src_name
241 data
["src_intf"] = vnf_src_interface
242 data
["dst_vnf"] = vnf_dst_name
243 data
["dst_intf"] = vnf_dst_interface
244 data
["cookie"] = cookie
245 data
["layer2"] = layer2
246 if kwargs
.get('path') is not None:
247 data
["path"] = kwargs
.get('path')
249 data
["path"] = self
._get
_path
(vnf_src_name
, vnf_dst_name
, vnf_src_interface
,
250 vnf_dst_interface
)[0]
252 # add route to dst ip to this interface
253 # this might block on containers that are still setting up, so start a new thread
254 if not kwargs
.get('no_route'):
255 # son_emu does not like concurrent commands for a container so we need to lock this if multiple chains
256 # on the same interface are created
257 src_node
.setHostRoute(dst_node
.intf(vnf_dst_interface
).IP(), vnf_src_interface
)
260 son_emu_data
= json
.loads(self
.get_son_emu_chain_data(vnf_src_name
))
262 son_emu_data
= dict()
263 if "son_emu_data" not in son_emu_data
:
264 son_emu_data
["son_emu_data"] = dict()
265 if "interfaces" not in son_emu_data
["son_emu_data"]:
266 son_emu_data
["son_emu_data"]["interfaces"] = dict()
267 if vnf_src_interface
not in son_emu_data
["son_emu_data"]["interfaces"]:
268 son_emu_data
["son_emu_data"]["interfaces"][vnf_src_interface
] = list()
269 son_emu_data
["son_emu_data"]["interfaces"][vnf_src_interface
].append(dst_intf
.IP())
271 self
.set_son_emu_chain_data(vnf_src_name
, son_emu_data
)
273 if kwargs
.get('bidirectional', False):
274 # call the reverse direction
275 path
= kwargs
.get('path')
277 path
= list(reversed(path
))
278 self
.network_action_start(vnf_dst_name
, vnf_src_name
, vnf_src_interface
=vnf_dst_interface
,
279 vnf_dst_interface
=vnf_src_interface
, bidirectional
=False,
280 layer2
=kwargs
.get('layer2', False), path
=path
,
281 no_route
=kwargs
.get('no_route'))
283 self
.full_chain_data
[flow
] = data
284 self
.chain_flow_cookies
[flow
] = cookie
286 except Exception as ex
:
287 logging
.exception("RPC error.")
288 raise Exception(ex
.message
)
290 def network_action_stop(self
, vnf_src_name
, vnf_dst_name
, **kwargs
):
292 Starts a network chain for a source destination pair
294 :param vnf_src_name: Name of the source VNF
295 :type vnf_src_name: ``str``
296 :param vnf_dst_name: Name of the source VNF interface
297 :type vnf_dst_name: ``str``
298 :param \**kwargs: See below
301 * *vnf_src_interface* (``str``): Name of source interface.
302 * *vnf_dst_interface* (``str``): Name of destination interface.
303 * *bidirectional* (``bool``): If set the chain will be torn down in both directions, else it will just\
304 be torn down from source to destination.
305 * *cookie* (``int``): Cookie value used by openflow. Used to identify the flows in the switches to be \
306 able to modify the correct flows.
309 if 'cookie' in kwargs
:
310 return self
.delete_flow_by_cookie(kwargs
.get('cookie'))
312 if kwargs
.get('bidirectional', False):
313 self
.delete_chain_by_intf(vnf_dst_name
, kwargs
.get('vnf_dst_interface'),
314 vnf_src_name
, kwargs
.get('vnf_src_interface'))
316 return self
.delete_chain_by_intf(vnf_src_name
, kwargs
.get('vnf_src_interface'),
317 vnf_dst_name
, kwargs
.get('vnf_dst_interface'))
318 except Exception as ex
:
319 logging
.exception("RPC error.")
322 def set_son_emu_chain_data(self
, vnf_name
, data
):
324 Set son-emu chain data for this node.
326 :param vnf_name: The name of the vnf where the data is stored.
327 :type vnf_name: ``str``
328 :param data: Raw data to store on the node.
331 self
.net
.getNodeByName(vnf_name
).cmd("echo \'%s\' > /tmp/son_emu_data.json" % json
.dumps(data
))
333 for intf
in data
['son_emu_data']['interfaces'].values():
336 self
.net
.getNodeByName(vnf_name
).cmd("echo \'%s\' > /tmp/son_emu_data" % "\n".join(ip_list
))
338 def get_son_emu_chain_data(self
, vnf_name
):
340 Get the current son-emu chain data set for this node.
342 :param vnf_name: The name of the vnf where the data is stored.
343 :type vnf_name: ``str``
344 :return: raw data stored on the node
347 return self
.net
.getNodeByName(vnf_name
).cmd("cat /tmp/son_emu_data.json")
349 def _get_connected_switch_data(self
, vnf_name
, vnf_interface
):
351 Get the switch an interface is connected to
352 :param vnf_name: Name of the VNF
353 :type vnf_name: ``str``
354 :param vnf_interface: Name of the VNF interface
355 :type vnf_interface: ``str``
356 :return: List containing the switch, and the inport number
357 :rtype: [``str``, ``int``]
360 src_sw_inport_nr
= None
361 for connected_sw
in self
.net
.DCNetwork_graph
.neighbors(vnf_name
):
362 link_dict
= self
.net
.DCNetwork_graph
[vnf_name
][connected_sw
]
363 for link
in link_dict
:
364 if (link_dict
[link
]['src_port_id'] == vnf_interface
or
366 'src_port_name'] == vnf_interface
):
367 # found the right link and connected switch
368 src_sw
= connected_sw
369 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
372 return src_sw
, src_sw_inport_nr
374 def _get_path(self
, src_vnf
, dst_vnf
, src_vnf_intf
, dst_vnf_intf
):
376 Own implementation of the get_path function from DCNetwork, because we just want the path and not set up
379 :param src_vnf: Name of the source VNF
380 :type src_vnf: ``str``
381 :param dst_vnf: Name of the destination VNF
382 :type dst_vnf: ``str``
383 :param src_vnf_intf: Name of the source VNF interface
384 :type src_vnf_intf: ``str``
385 :param dst_vnf_intf: Name of the destination VNF interface
386 :type dst_vnf_intf: ``str``
387 :return: path, src_sw, dst_sw
388 :rtype: ``list``, ``str``, ``str``
390 # modified version of the _chainAddFlow from emuvim.dcemulator.net._chainAddFlow
393 logging
.debug("Find shortest path from vnf %s to %s",
396 for connected_sw
in self
.net
.DCNetwork_graph
.neighbors(src_vnf
):
397 link_dict
= self
.net
.DCNetwork_graph
[src_vnf
][connected_sw
]
398 for link
in link_dict
:
399 if (link_dict
[link
]['src_port_id'] == src_vnf_intf
or
401 'src_port_name'] == src_vnf_intf
):
402 # found the right link and connected switch
403 src_sw
= connected_sw
406 for connected_sw
in self
.net
.DCNetwork_graph
.neighbors(dst_vnf
):
407 link_dict
= self
.net
.DCNetwork_graph
[connected_sw
][dst_vnf
]
408 for link
in link_dict
:
409 if link_dict
[link
]['dst_port_id'] == dst_vnf_intf
or \
411 'dst_port_name'] == dst_vnf_intf
:
412 # found the right link and connected
413 dst_sw
= connected_sw
415 logging
.debug("From switch %s to %s " % (src_sw
, dst_sw
))
419 # returns the first found shortest path
420 # if all shortest paths are wanted, use: all_shortest_paths
421 path
= nx
.shortest_path(self
.net
.DCNetwork_graph
, src_sw
, dst_sw
)
423 logging
.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
424 src_vnf
, dst_vnf
, src_sw
, dst_sw
))
425 logging
.debug("Graph nodes: %r" % self
.net
.DCNetwork_graph
.nodes())
426 logging
.debug("Graph edges: %r" % self
.net
.DCNetwork_graph
.edges())
427 for e
, v
in self
.net
.DCNetwork_graph
.edges():
428 logging
.debug("%r" % self
.net
.DCNetwork_graph
[e
][v
])
429 return "No path could be found between {0} and {1}".format(src_vnf
, dst_vnf
)
431 logging
.info("Shortest path between {0} and {1}: {2}".format(src_vnf
, dst_vnf
, path
))
432 return path
, src_sw
, dst_sw
434 def add_loadbalancer(self
, src_vnf_name
, src_vnf_interface
, lb_data
):
436 This function will set up a loadbalancer at the given interface.
438 :param src_vnf_name: Name of the source VNF
439 :type src_vnf_name: ``str``
440 :param src_vnf_interface: Name of the destination VNF
441 :type src_vnf_interface: ``str``
442 :param lb_data: A dictionary containing the destination data as well as custom path settings
443 :type lb_data: ``dict``
446 lbdata = {"dst_vnf_interfaces": {"dc2_man_web0": "port-man-2",
447 "dc3_man_web0": "port-man-4","dc4_man_web0": "port-man-6"}, "path": {"dc2_man_web0": {"port-man-2": [ "dc1.s1",\
453 dest_intfs_mapping
= lb_data
.get('dst_vnf_interfaces', dict())
454 # a custom path can be specified as a list of switches
455 custom_paths
= lb_data
.get('path', dict())
456 dest_vnf_outport_nrs
= list()
458 logging
.debug("Call to add_loadbalancer at %s intfs:%s" % (src_vnf_name
, src_vnf_interface
))
460 if not self
.check_vnf_intf_pair(src_vnf_name
, src_vnf_interface
):
461 raise Exception(u
"Source VNF %s or intfs %s does not exist" % (src_vnf_name
, src_vnf_interface
))
463 # find the switch belonging to the source interface, as well as the inport nr
464 for connected_sw
in net
.DCNetwork_graph
.neighbors(src_vnf_name
):
465 link_dict
= net
.DCNetwork_graph
[src_vnf_name
][connected_sw
]
466 for link
in link_dict
:
467 if link_dict
[link
]['src_port_name'] == src_vnf_interface
:
468 src_sw
= connected_sw
469 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
472 if src_sw
is None or src_sw_inport_nr
== 0:
473 raise Exception(u
"Source VNF or interface can not be found.")
475 # get all target interface outport numbers
476 for vnf_name
in dest_intfs_mapping
:
477 if vnf_name
not in net
.DCNetwork_graph
:
478 raise Exception(u
"Target VNF %s is not known." % vnf_name
)
479 for connected_sw
in net
.DCNetwork_graph
.neighbors(vnf_name
):
480 link_dict
= net
.DCNetwork_graph
[vnf_name
][connected_sw
]
481 for link
in link_dict
:
482 if link_dict
[link
]['src_port_name'] == dest_intfs_mapping
[vnf_name
]:
483 dest_vnf_outport_nrs
.append(int(link_dict
[link
]['dst_port_nr']))
485 if (src_vnf_name
, src_vnf_interface
) not in self
.lb_flow_cookies
:
486 self
.lb_flow_cookies
[(src_vnf_name
, src_vnf_interface
)] = list()
491 for intf
in net
[src_vnf_name
].intfs
.values():
492 if intf
.name
== src_vnf_interface
:
497 # set up paths for each destination vnf individually
499 cookie
= self
.get_cookie()
500 main_cmd
= "add-flow -OOpenFlow13"
501 self
.lb_flow_cookies
[(src_vnf_name
, src_vnf_interface
)].append(cookie
)
505 data
["src_vnf"] = src_vnf_name
506 data
["src_intf"] = src_vnf_interface
507 data
["paths"] = list()
508 data
["cookie"] = cookie
510 # lb mac for src -> target connections
511 lb_mac
= "31:33:70:%02x:%02x:%02x" % (random
.randint(0, 255),random
.randint(0, 255),random
.randint(0, 255))
513 # calculate lb ip as src_intf.ip +1
514 octets
= src_ip
.split('.')
515 octets
[3] = str(int(octets
[3]) + 1)
516 plus_one
= '.'.join(octets
)
518 # set up arp reply as well as add the route to the interface
519 self
.setup_arp_reply_at(src_sw
, src_sw_inport_nr
, plus_one
, lb_mac
, cookie
=cookie
)
520 net
.getNodeByName(src_vnf_name
).setHostRoute(plus_one
, src_vnf_interface
)
522 for dst_vnf_name
, dst_vnf_interface
in dest_intfs_mapping
.items():
523 path
, src_sw
, dst_sw
= self
._get
_path
(src_vnf_name
, dst_vnf_name
,
524 src_vnf_interface
, dst_vnf_interface
)
526 # use custom path if one is supplied
527 # json does not support hashing on tuples so we use nested dicts
528 if custom_paths
is not None and dst_vnf_name
in custom_paths
:
529 if dst_vnf_interface
in custom_paths
[dst_vnf_name
]:
530 path
= custom_paths
[dst_vnf_name
][dst_vnf_interface
]
531 logging
.debug("Taking custom path from %s to %s: %s" % (src_vnf_name
, dst_vnf_name
, path
))
533 if not self
.check_vnf_intf_pair(dst_vnf_name
, dst_vnf_interface
):
534 self
.delete_loadbalancer(src_vnf_name
, src_vnf_interface
)
535 raise Exception(u
"VNF %s or intfs %s does not exist" % (dst_vnf_name
, dst_vnf_interface
))
536 if isinstance(path
, dict):
537 self
.delete_loadbalancer(src_vnf_name
, src_vnf_interface
)
538 raise Exception(u
"Can not find a valid path. Are you specifying the right interfaces?.")
540 target_mac
= "fa:17:00:03:13:37"
541 target_ip
= "0.0.0.0"
542 for intf
in net
[dst_vnf_name
].intfs
.values():
543 if intf
.name
== dst_vnf_interface
:
544 target_mac
= str(intf
.mac
)
545 target_ip
= str(intf
.ip
)
546 dst_sw_outport_nr
= dest_vnf_outport_nrs
[index
]
548 switch_inport_nr
= src_sw_inport_nr
550 #self.setup_arp_reply_at(src_sw, src_sw_inport_nr, target_ip, target_mac, cookie=cookie)
551 net
.getNodeByName(dst_vnf_name
).setHostRoute(src_ip
, dst_vnf_interface
)
553 # choose free vlan if path contains more than 1 switch
555 vlan
= net
.vlans
.pop()
557 vlan
= net
.vlans
.pop()
561 single_flow_data
= dict()
562 single_flow_data
["dst_vnf"] = dst_vnf_name
563 single_flow_data
["dst_intf"] = dst_vnf_interface
564 single_flow_data
["path"] = path
565 single_flow_data
["vlan"] = vlan
566 single_flow_data
["cookie"] = cookie
568 data
["paths"].append(single_flow_data
)
571 for i
in range(0, len(path
)):
572 if i
< len(path
) - 1:
573 next_hop
= path
[i
+ 1]
575 # last switch reached
576 next_hop
= dst_vnf_name
577 next_node
= net
.getNodeByName(next_hop
)
578 if next_hop
== dst_vnf_name
:
579 switch_outport_nr
= dst_sw_outport_nr
580 logging
.info("end node reached: {0}".format(dst_vnf_name
))
581 elif not isinstance(next_node
, OVSSwitch
):
582 logging
.info("Next node: {0} is not a switch".format(next_hop
))
583 return "Next node: {0} is not a switch".format(next_hop
)
585 # take first link between switches by default
587 switch_outport_nr
= net
.DCNetwork_graph
[current_hop
][next_hop
][index_edge_out
]['src_port_nr']
589 cmd
= 'priority=1,in_port=%s,cookie=%s' % (switch_inport_nr
, cookie
)
590 cmd_back
= 'priority=1,in_port=%s,cookie=%s' % (switch_outport_nr
, cookie
)
591 # if a vlan is picked, the connection is routed through multiple switches
593 if path
.index(current_hop
) == 0: # first node
595 cmd
= 'in_port=%s' % src_sw_inport_nr
596 cmd
+= ',cookie=%s' % cookie
597 cmd
+= ',table=%s' % cookie
599 cmd
+= ',reg1=%s' % index
602 cmd
+= ',push_vlan:0x8100'
603 masked_vlan
= vlan |
0x1000
604 cmd
+= ',set_field:%s->vlan_vid' % masked_vlan
605 cmd
+= ',set_field:%s->eth_dst' % target_mac
606 cmd
+= ',set_field:%s->ip_dst' % target_ip
607 cmd
+= ',output:%s' % switch_outport_nr
609 # last switch for reverse route
610 # remove any vlan tags
611 cmd_back
+= ',dl_vlan=%s' % vlan
612 cmd_back
+= ',actions=pop_vlan,output:%s' % switch_inport_nr
613 elif next_hop
== dst_vnf_name
: # last switch
614 # remove any vlan tags
615 cmd
+= ',dl_vlan=%s' % vlan
616 cmd
+= ',actions=pop_vlan,output:%s' % switch_outport_nr
617 # set up arp replys at the port so the dst nodes know the src
618 self
.setup_arp_reply_at(current_hop
, switch_outport_nr
, src_ip
, src_mac
, cookie
=cookie
)
621 cmd_back
= 'in_port=%s' % switch_outport_nr
622 cmd_back
+= ',cookie=%s' % cookie
624 cmd_back
+= ',actions='
625 cmd_back
+= 'push_vlan:0x8100'
626 masked_vlan
= vlan |
0x1000
627 cmd_back
+= ',set_field:%s->vlan_vid' % masked_vlan
628 cmd_back
+= ',set_field:%s->eth_src' % lb_mac
629 cmd_back
+= ',set_field:%s->ip_src' % plus_one
630 cmd_back
+= ',output:%s' % switch_inport_nr
632 # if we have a circle in the path we need to specify this, as openflow will ignore the packet
633 # if we just output it on the same port as it came in
634 if switch_inport_nr
== switch_outport_nr
:
635 cmd
+= ',dl_vlan=%s,actions=IN_PORT' % (vlan
)
636 cmd_back
+= ',dl_vlan=%s,actions=IN_PORT' % (vlan
)
638 cmd
+= ',dl_vlan=%s,actions=output:%s' % (vlan
, switch_outport_nr
)
639 cmd_back
+= ',dl_vlan=%s,actions=output:%s' % (vlan
, switch_inport_nr
)
640 # output the packet at the correct outport
642 cmd
= 'in_port=%s' % src_sw_inport_nr
643 cmd
+= ',cookie=%s' % cookie
644 cmd
+= ',table=%s' % cookie
646 cmd
+= ',reg1=%s' % index
648 cmd
+= ',set_field:%s->eth_dst' % target_mac
649 cmd
+= ',set_field:%s->ip_dst' % target_ip
650 cmd
+= ',output:%s' % switch_outport_nr
653 cmd_back
= 'in_port=%s' % switch_outport_nr
654 cmd_back
+= ',cookie=%s' % cookie
656 cmd_back
+= ',actions='
657 cmd_back
+= ',set_field:%s->eth_src' % lb_mac
658 cmd_back
+= ',set_field:%s->ip_src' % plus_one
659 cmd_back
+= ',output:%s' % src_sw_inport_nr
661 self
.setup_arp_reply_at(current_hop
, switch_outport_nr
, src_ip
, src_mac
, cookie
=cookie
)
663 # excecute the command on the target switch
666 cmd_back
= "\"%s\"" % cmd_back
667 net
[current_hop
].dpctl(main_cmd
, cmd
)
668 net
[current_hop
].dpctl(main_cmd
, cmd_back
)
670 # set next hop for the next iteration step
671 if isinstance(next_node
, OVSSwitch
):
672 switch_inport_nr
= net
.DCNetwork_graph
[current_hop
][next_hop
][0]['dst_port_nr']
673 current_hop
= next_hop
675 # advance to next destination
678 # set up the actual load balancing rule as a multipath on the very first switch
679 cmd
= '"in_port=%s' % src_sw_inport_nr
680 cmd
+= ',cookie=%s' % (cookie
)
683 # push 0x01 into the first register
684 cmd
+= 'load:0x1->NXM_NX_REG0[]'
685 # load balance modulo n over all dest interfaces
686 # TODO: in newer openvswitch implementations this should be changed to symmetric_l3l4+udp
687 # to balance any kind of traffic
688 cmd
+= ',multipath(symmetric_l4,1024,modulo_n,%s,0,NXM_NX_REG1[0..12])' % len(dest_intfs_mapping
)
689 # reuse the cookie as table entry as it will be unique
690 cmd
+= ',resubmit(, %s)"' % cookie
692 # actually add the flow
693 logging
.debug("Switch: %s, CMD: %s" % (src_sw
, cmd
))
694 net
[src_sw
].dpctl(main_cmd
, cmd
)
696 # finally add all flow data to the internal data storage
697 self
.full_lb_data
[(src_vnf_name
, src_vnf_interface
)] = data
699 def add_floating_lb(self
, datacenter
, lb_data
):
701 This function will set up a loadbalancer at the given datacenter.
702 This function returns the floating ip assigned to the loadbalancer as multiple ones are possible.
704 :param datacenter: The datacenter entrypoint
705 :type datacenter: ``str``
706 :param lb_data: A dictionary containing the destination data as well as custom path settings
707 :type lb_data: ``dict``
710 lbdata = {"dst_vnf_interfaces": {"dc2_man_web0": "port-man-2",
711 "dc3_man_web0": "port-man-4","dc4_man_web0": "port-man-6"}, "path": {"dc2_man_web0": {"port-man-2": [ "dc1.s1",\
716 src_sw
= self
.floating_switch
.name
717 dest_intfs_mapping
= lb_data
.get('dst_vnf_interfaces', dict())
718 # a custom path can be specified as a list of switches
719 custom_paths
= lb_data
.get('path', dict())
720 dest_vnf_outport_nrs
= list()
722 if datacenter
not in self
.net
.dcs
:
723 raise Exception(u
"Source datacenter can not be found.")
725 # get all target interface outport numbers
726 for vnf_name
in dest_intfs_mapping
:
727 if vnf_name
not in net
.DCNetwork_graph
:
728 raise Exception(u
"Target VNF %s is not known." % vnf_name
)
729 for connected_sw
in net
.DCNetwork_graph
.neighbors(vnf_name
):
730 link_dict
= net
.DCNetwork_graph
[vnf_name
][connected_sw
]
731 for link
in link_dict
:
732 if link_dict
[link
]['src_port_name'] == dest_intfs_mapping
[vnf_name
]:
733 dest_vnf_outport_nrs
.append(int(link_dict
[link
]['dst_port_nr']))
735 if len(dest_vnf_outport_nrs
) == 0:
736 raise Exception("There are no paths specified for the loadbalancer")
737 src_ip
= self
.floating_intf
.IP()
738 src_mac
= self
.floating_intf
.MAC()
740 # set up paths for each destination vnf individually
742 cookie
= self
.get_cookie()
743 main_cmd
= "add-flow -OOpenFlow13"
744 floating_ip
= self
.floating_network
.get_new_ip_address("floating-ip").split("/")[0]
746 for dst_vnf_name
, dst_vnf_interface
in dest_intfs_mapping
.items():
748 # use custom path if one is supplied
749 # json does not support hashing on tuples so we use nested dicts
750 if custom_paths
is not None and dst_vnf_name
in custom_paths
:
751 if dst_vnf_interface
in custom_paths
[dst_vnf_name
]:
752 path
= custom_paths
[dst_vnf_name
][dst_vnf_interface
]
753 logging
.debug("Taking custom path to %s: %s" % (dst_vnf_name
, path
))
755 if datacenter
not in self
.floating_links
:
756 self
.floating_links
[datacenter
] = \
757 net
.addLink(self
.floating_switch
, datacenter
)
759 self
._get
_path
(self
.floating_root
.name
, dst_vnf_name
, self
.floating_intf
.name
, dst_vnf_interface
)[0]
761 if isinstance(path
, dict):
762 self
.delete_flow_by_cookie(cookie
)
763 raise Exception(u
"Can not find a valid path. Are you specifying the right interfaces?.")
765 intf
= net
[dst_vnf_name
].nameToIntf
[dst_vnf_interface
]
766 target_mac
= str(intf
.MAC())
767 target_ip
= str(intf
.IP())
768 dst_sw_outport_nr
= dest_vnf_outport_nrs
[index
]
770 switch_inport_nr
= src_sw_inport_nr
771 vlan
= net
.vlans
.pop()
773 # iterate all switches on the path
774 for i
in range(0, len(path
)):
775 if i
< len(path
) - 1:
776 next_hop
= path
[i
+ 1]
778 # last switch reached
779 next_hop
= dst_vnf_name
780 next_node
= net
.getNodeByName(next_hop
)
783 if next_hop
== dst_vnf_name
:
784 switch_outport_nr
= dst_sw_outport_nr
785 logging
.info("end node reached: {0}".format(dst_vnf_name
))
786 elif not isinstance(next_node
, OVSSwitch
):
787 logging
.info("Next node: {0} is not a switch".format(next_hop
))
788 return "Next node: {0} is not a switch".format(next_hop
)
790 # take first link between switches by default
792 switch_outport_nr
= net
.DCNetwork_graph
[current_hop
][next_hop
][index_edge_out
]['src_port_nr']
794 # default filters, just overwritten on the first node and last node
795 cmd
= 'priority=1,in_port=%s,cookie=%s' % (switch_inport_nr
, cookie
)
796 cmd_back
= 'priority=1,in_port=%s,cookie=%s' % (switch_outport_nr
, cookie
)
797 if i
== 0: # first node
798 cmd
= 'in_port=%s' % src_sw_inport_nr
799 cmd
+= ',cookie=%s' % cookie
800 cmd
+= ',table=%s' % cookie
802 cmd
+= ',ip_dst=%s' % floating_ip
803 cmd
+= ',reg1=%s' % index
806 cmd
+= ',push_vlan:0x8100'
807 masked_vlan
= vlan |
0x1000
808 cmd
+= ',set_field:%s->vlan_vid' % masked_vlan
809 cmd
+= ',set_field:%s->eth_dst' % target_mac
810 cmd
+= ',set_field:%s->ip_dst' % target_ip
811 cmd
+= ',output:%s' % switch_outport_nr
813 # last switch for reverse route
814 # remove any vlan tags
815 cmd_back
+= ',dl_vlan=%s' % vlan
816 cmd_back
+= ',actions=pop_vlan,output:%s' % switch_inport_nr
817 self
.setup_arp_reply_at(current_hop
, src_sw_inport_nr
, floating_ip
, target_mac
, cookie
=cookie
)
818 elif next_hop
== dst_vnf_name
: # last switch
819 # remove any vlan tags
820 cmd
+= ',dl_vlan=%s' % vlan
821 cmd
+= ',actions=pop_vlan,output:%s' % switch_outport_nr
822 # set up arp replys at the port so the dst nodes know the src
823 self
.setup_arp_reply_at(current_hop
, switch_outport_nr
, src_ip
, src_mac
, cookie
=cookie
)
826 cmd_back
= 'in_port=%s' % switch_outport_nr
827 cmd_back
+= ',cookie=%s' % cookie
829 cmd_back
+= ',actions='
830 cmd_back
+= 'push_vlan:0x8100'
831 masked_vlan
= vlan |
0x1000
832 cmd_back
+= ',set_field:%s->vlan_vid' % masked_vlan
833 cmd_back
+= ',set_field:%s->eth_src' % src_mac
834 cmd_back
+= ',set_field:%s->ip_src' % floating_ip
835 cmd_back
+= ',output:%s' % switch_inport_nr
836 net
.getNodeByName(dst_vnf_name
).setHostRoute(src_ip
, dst_vnf_interface
)
838 # if we have a circle in the path we need to specify this, as openflow will ignore the packet
839 # if we just output it on the same port as it came in
840 if switch_inport_nr
== switch_outport_nr
:
841 cmd
+= ',dl_vlan=%s,actions=IN_PORT' % (vlan
)
842 cmd_back
+= ',dl_vlan=%s,actions=IN_PORT' % (vlan
)
844 cmd
+= ',dl_vlan=%s,actions=output:%s' % (vlan
, switch_outport_nr
)
845 cmd_back
+= ',dl_vlan=%s,actions=output:%s' % (vlan
, switch_inport_nr
)
847 # excecute the command on the target switch
850 cmd_back
= "\"%s\"" % cmd_back
851 net
[current_hop
].dpctl(main_cmd
, cmd
)
852 net
[current_hop
].dpctl(main_cmd
, cmd_back
)
854 # set next hop for the next iteration step
855 if isinstance(next_node
, OVSSwitch
):
856 switch_inport_nr
= net
.DCNetwork_graph
[current_hop
][next_hop
][0]['dst_port_nr']
857 current_hop
= next_hop
859 # advance to next destination
862 # set up the actual load balancing rule as a multipath on the very first switch
863 cmd
= '"in_port=%s' % src_sw_inport_nr
864 cmd
+= ',cookie=%s' % (cookie
)
867 # push 0x01 into the first register
868 cmd
+= 'load:0x1->NXM_NX_REG0[]'
869 # load balance modulo n over all dest interfaces
870 # TODO: in newer openvswitch implementations this should be changed to symmetric_l3l4+udp
871 # to balance any kind of traffic
872 cmd
+= ',multipath(symmetric_l4,1024,modulo_n,%s,0,NXM_NX_REG1[0..12])' % len(dest_intfs_mapping
)
873 # reuse the cookie as table entry as it will be unique
874 cmd
+= ',resubmit(, %s)"' % cookie
876 # actually add the flow
877 logging
.debug("Switch: %s, CMD: %s" % (src_sw
, cmd
))
878 net
[src_sw
].dpctl(main_cmd
, cmd
)
880 self
.floating_cookies
[cookie
] = floating_ip
882 return cookie
, floating_ip
884 def setup_arp_reply_at(self
, switch
, port_nr
, target_ip
, target_mac
, cookie
=None):
886 Sets up a custom ARP reply at a switch.
887 An ARP request coming in on the `port_nr` for `target_ip` will be answered with target IP/MAC.
889 :param switch: The switch belonging to the interface
890 :type switch: ``str``
891 :param port_nr: The port number at the switch that is connected to the interface
892 :type port_nr: ``int``
893 :param target_ip: The IP for which to set up the ARP reply
894 :type target_ip: ``str``
895 :param target_mac: The MAC address of the target interface
896 :type target_mac: ``str``
897 :param cookie: cookie to identify the ARP request, if None a new one will be picked
898 :type cookie: ``int`` or ``None``
903 cookie
= self
.get_cookie()
904 main_cmd
= "add-flow -OOpenFlow13"
906 # first set up ARP requests for the source node, so it will always 'find' a partner
907 cmd
= '"in_port=%s' % port_nr
908 cmd
+= ',cookie=%s' % cookie
910 # only answer for target ip arp requests
911 cmd
+= ',arp_tpa=%s' % target_ip
913 # set message type to ARP reply
914 cmd
+= 'load:0x2->NXM_OF_ARP_OP[]'
915 # set src ip as dst ip
916 cmd
+= ',move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[]'
918 cmd
+= ',set_field:%s->eth_src' % target_mac
920 cmd
+= ',move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[], move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[]'
921 # set target mac as hex
922 cmd
+= ',load:0x%s->NXM_NX_ARP_SHA[]' % "".join(target_mac
.split(':'))
923 # set target ip as hex
924 octets
= target_ip
.split('.')
925 dst_ip_hex
= '{:02X}{:02X}{:02X}{:02X}'.format(*map(int, octets
))
926 cmd
+= ',load:0x%s->NXM_OF_ARP_SPA[]' % dst_ip_hex
927 # output to incoming port remember the closing "
929 self
.net
[switch
].dpctl(main_cmd
, cmd
)
931 "Set up ARP reply at %s port %s." % (switch
, port_nr
))
933 def delete_flow_by_cookie(self
, cookie
):
935 Removes a flow identified by the cookie
937 :param cookie: The cookie for the specified flow
938 :type cookie: ``int``
939 :return: True if successful, else false
944 logging
.debug("Deleting flow by cookie %d" % (cookie
))
946 # we have to call delete-group for each switch
947 for node
in self
.net
.switches
:
949 flow
["dpid"] = int(node
.dpid
, 16)
950 flow
["cookie"] = cookie
951 flow
['cookie_mask'] = int('0xffffffffffffffff', 16)
955 logging
.debug("Deleting flowentry with cookie %d" % (
957 if self
.net
.controller
== RemoteController
:
958 self
.net
.ryu_REST('stats/flowentry/delete', data
=flow
)
960 self
.cookies
.remove(cookie
)
963 def delete_chain_by_intf(self
, src_vnf_name
, src_vnf_intf
, dst_vnf_name
, dst_vnf_intf
):
965 Removes a flow identified by the vnf_name/vnf_intf pairs
967 :param src_vnf_name: The vnf name for the specified flow
968 :type src_vnf_name: ``str``
969 :param src_vnf_intf: The interface name for the specified flow
970 :type src_vnf_intf: ``str``
971 :param dst_vnf_name: The vnf name for the specified flow
972 :type dst_vnf_name: ``str``
973 :param dst_vnf_intf: The interface name for the specified flow
974 :type dst_vnf_intf: ``str``
975 :return: True if successful, else false
978 logging
.debug("Deleting flow for vnf/intf pair %s %s" % (src_vnf_name
, src_vnf_intf
))
979 if not self
.check_vnf_intf_pair(src_vnf_name
, src_vnf_intf
):
981 if not self
.check_vnf_intf_pair(dst_vnf_name
, dst_vnf_intf
):
983 target_flow
= (src_vnf_name
, src_vnf_intf
, dst_vnf_name
, dst_vnf_intf
)
984 if not target_flow
in self
.chain_flow_cookies
:
987 success
= self
.delete_flow_by_cookie(self
.chain_flow_cookies
[target_flow
])
990 del self
.chain_flow_cookies
[target_flow
]
991 del self
.full_chain_data
[target_flow
]
995 def delete_loadbalancer(self
, vnf_src_name
, vnf_src_interface
):
997 Removes a loadbalancer that is configured for the node and interface
999 :param src_vnf_name: Name of the source VNF
1000 :param src_vnf_interface: Name of the destination VNF
1003 # we have to call delete-group for each switch
1004 delete_group
= list()
1005 group_id
= self
.get_flow_group(vnf_src_name
, vnf_src_interface
)
1006 for node
in self
.net
.switches
:
1007 for cookie
in self
.lb_flow_cookies
[(vnf_src_name
, vnf_src_interface
)]:
1009 flow
["dpid"] = int(node
.dpid
, 16)
1010 flow
["cookie"] = cookie
1011 flow
['cookie_mask'] = int('0xffffffffffffffff', 16)
1015 group_del
["dpid"] = int(node
.dpid
, 16)
1016 group_del
["group_id"] = group_id
1017 delete_group
.append(group_del
)
1020 logging
.debug("Deleting flowentry with cookie %d belonging to lb at %s:%s" % (
1021 flow
["cookie"], vnf_src_name
, vnf_src_interface
))
1022 if self
.net
.controller
== RemoteController
:
1023 self
.net
.ryu_REST('stats/flowentry/delete', data
=flow
)
1025 logging
.debug("Deleting group with id %s" % group_id
)
1026 for switch_del_group
in delete_group
:
1027 if self
.net
.controller
== RemoteController
:
1028 self
.net
.ryu_REST("stats/groupentry/delete", data
=switch_del_group
)
1030 # unmap groupid from the interface
1031 target_pair
= (vnf_src_name
, vnf_src_interface
)
1032 if target_pair
in self
.flow_groups
:
1033 del self
.flow_groups
[target_pair
]
1034 if target_pair
in self
.full_lb_data
:
1035 del self
.full_lb_data
[target_pair
]
1037 def delete_floating_lb(self
, cookie
):
1039 Delete a floating loadbalancer.
1040 Floating loadbalancers are different from normal ones as there are multiple ones on the same interface.
1041 :param cookie: The cookie of the loadbalancer
1042 :type cookie: ``int``
1044 cookie
= int(cookie
)
1045 if cookie
not in self
.floating_cookies
:
1046 raise Exception("Can not delete floating loadbalancer as the flowcookie is not known")
1048 self
.delete_flow_by_cookie(cookie
)
1049 floating_ip
= self
.floating_cookies
[cookie
]
1050 self
.floating_network
.withdraw_ip_address(floating_ip
)
1052 def set_arp_entry(self
, vnf_name
, vnf_interface
, ip
, mac
):
1054 Sets an arp entry on the specified VNF. This is done on the node directly and not by open vswitch!
1055 :param vnf_name: Name of the VNF
1056 :type vnf_name: ``str``
1057 :param vnf_interface: Name of the interface
1058 :type vnf_interface: ``str``
1059 :param ip: IP to reply to
1061 :param mac: Answer with this MAC
1064 node
= self
.net
.getNodeByName(vnf_name
)
1065 node
.cmd("arp -i %s -s %s %s" % (vnf_interface
, ip
, mac
))