ed6a91c5ff4ce934aebeea14ddef9528b79f1463
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
18 from emuvim
.api
.openstack
.monitor_api
import MonitorDummyApi
21 class OpenstackManage(object):
23 OpenstackManage is a singleton and management component for the emulator.
24 It is the brain of the Openstack component and manages everything that is not datacenter specific like
25 network chains or load balancers.
30 if OpenstackManage
.__instance
is None:
31 OpenstackManage
.__instance
= object.__new
__(cls
)
32 return OpenstackManage
.__instance
34 def __init__(self
, ip
="0.0.0.0", port
=4000):
35 # we are a singleton, only initialize once!
36 self
.lock
= threading
.Lock()
38 if hasattr(self
, "init"):
42 self
.endpoints
= dict()
48 # to keep track which src_vnf(input port on the switch) handles a load balancer
49 self
.lb_flow_cookies
= dict()
50 self
.chain_flow_cookies
= dict()
52 # for the visualization also store the complete chain data incl. paths
53 self
.full_chain_data
= dict()
54 self
.full_lb_data
= dict()
56 # flow groups could be handled for each switch separately, but this global group counter should be easier to
57 # debug and to maintain
58 self
.flow_groups
= dict()
60 # we want one global chain api. this should not be datacenter dependent!
61 self
.chain
= chain_api
.ChainApi(ip
, port
, self
)
62 self
.thread
= threading
.Thread(target
=self
.chain
._start
_flask
, args
=())
63 self
.thread
.daemon
= True
64 self
.thread
.name
= self
.chain
.__class
__
67 self
.monitoring
= MonitorDummyApi(self
.ip
, 3000)
68 self
.thread
= threading
.Thread(target
=self
.monitoring
._start
_flask
, args
=())
69 self
.thread
.daemon
= True
70 self
.thread
.name
= self
.monitoring
.__class
__
73 # floating ip network setup
74 self
.floating_switch
= None
75 self
.floating_network
= None
76 self
.floating_netmask
= "192.168.100.0/24"
77 self
.floating_nodes
= dict()
78 self
.floating_cookies
= dict()
79 self
.floating_intf
= None
80 self
.floating_links
= dict()
90 self
.init_floating_network()
93 def init_floating_network(self
):
95 Initialize the floating network component for the emulator.
96 Will not do anything if already initialized.
98 if self
.net
is not None and self
.floating_switch
is None:
99 # create a floating network
100 fn
= self
.floating_network
= Net("default")
101 fn
.id = str(uuid
.uuid4())
102 fn
.set_cidr(self
.floating_netmask
)
105 fn
.subnet_id
= str(uuid
.uuid4())
106 fn
.subnet_name
= fn
.name
+ "-sub"
108 # create a port for the host
109 port
= Port("root-port")
110 #port.id = str(uuid.uuid4())
111 port
.net_name
= fn
.name
114 root_ip
= fn
.get_new_ip_address(port
.name
)
115 port
.ip_address
= root_ip
116 # floating ip network setup
117 # wierd way of getting a datacenter object
118 first_dc
= self
.net
.dcs
.values()[0]
119 # set a dpid for the switch. for this we have to get the id of the next possible dc
120 self
.floating_switch
= self
.net
.addSwitch("fs1", dpid
=hex(first_dc
._get
_next
_dc
_dpid
())[2:])
121 # this is the interface appearing on the physical host
122 self
.floating_root
= Node('root', inNamespace
=False)
123 self
.net
.hosts
.append(self
.floating_root
)
124 self
.net
.nameToNode
['root'] = self
.floating_root
125 self
.floating_intf
= self
.net
.addLink(self
.floating_root
, self
.floating_switch
).intf1
126 self
.floating_root
.setIP(root_ip
, intf
=self
.floating_intf
)
127 self
.floating_nodes
[(self
.floating_root
.name
, root_ip
)] = self
.floating_root
130 def stop_floating_network(self
):
132 self
.floating_switch
= None
134 def add_endpoint(self
, ep
):
136 Registers an openstack endpoint with manage
138 :param ep: Openstack API endpoint
139 :type ep: :class:`heat.openstack_api_endpoint`
141 key
= "%s:%s" % (ep
.ip
, ep
.port
)
142 self
.endpoints
[key
] = ep
144 def get_cookie(self
):
146 Get an unused cookie.
151 cookie
= int(max(self
.cookies
) + 1)
152 self
.cookies
.add(cookie
)
155 def get_flow_group(self
, src_vnf_name
, src_vnf_interface
):
157 Gets free group that is not currently used by any other flow for the specified interface / VNF.
159 :param src_vnf_name: Source VNF name
160 :type src_vnf_name: ``str``
161 :param src_vnf_interface: Source VNF interface name
162 :type src_vnf_interface: ``str``
163 :return: Flow group identifier.
166 if (src_vnf_name
, src_vnf_interface
) not in self
.flow_groups
:
167 grp
= int(len(self
.flow_groups
) + 1)
168 self
.flow_groups
[(src_vnf_name
, src_vnf_interface
)] = grp
170 grp
= self
.flow_groups
[(src_vnf_name
, src_vnf_interface
)]
173 def check_vnf_intf_pair(self
, vnf_name
, vnf_intf_name
):
175 Checks if a VNF exists and has the given interface
177 :param vnf_name: Name of the VNF to be checked
178 :type vnf_name: ``str``
179 :param vnf_intf_name: Name of the interface that belongst to the VNF
180 :type vnf_intf_name: ``str``
181 :return: ``True`` if it is valid pair, else ``False``
185 if vnf_name
in self
.net
:
186 vnf
= self
.net
.getNodeByName(vnf_name
)
187 return vnf_intf_name
in vnf
.nameToIntf
189 def network_action_start(self
, vnf_src_name
, vnf_dst_name
, **kwargs
):
191 Starts a network chain for a source destination pair
193 :param vnf_src_name: Name of the source VNF
194 :type vnf_src_name: ``str``
195 :param vnf_dst_name: Name of the source VNF interface
196 :type vnf_dst_name: ``str``
197 :param \**kwargs: See below
200 * *vnf_src_interface* (``str``): Name of source interface.
201 * *vnf_dst_interface* (``str``): Name of destination interface.
202 * *weight* (``int``): This value is fed into the shortest path computation if no path is specified.
203 * *match* (``str``): A custom match entry for the openflow flow rules. Only vlanid or port possible.
204 * *bidirectional* (``bool``): If set the chain will be set in both directions, else it will just set up \
205 from source to destination.
206 * *cookie* (``int``): Cookie value used by openflow. Used to identify the flows in the switches to be \
207 able to modify the correct flows.
208 * *no_route* (``bool``): If set a layer 3 route to the target interface will not be set up.
209 :return: The cookie chosen for the flow.
213 vnf_src_interface
= kwargs
.get('vnf_src_interface')
214 vnf_dst_interface
= kwargs
.get('vnf_dst_interface')
215 layer2
= kwargs
.get('layer2', True)
216 match
= kwargs
.get('match')
217 flow
= (vnf_src_name
, vnf_src_interface
, vnf_dst_name
, vnf_dst_interface
)
218 if flow
in self
.chain_flow_cookies
:
219 raise Exception("There is already a chain at the specified src/dst pair!")
220 # set up a layer 2 chain, this allows multiple chains for the same interface
221 src_node
= self
.net
.getNodeByName(vnf_src_name
)
222 dst_node
= self
.net
.getNodeByName(vnf_dst_name
)
223 dst_intf
= dst_node
.intf(vnf_dst_interface
)
225 switch
, inport
= self
._get
_connected
_switch
_data
(vnf_src_name
, vnf_src_interface
)
226 self
.setup_arp_reply_at(switch
, inport
, dst_intf
.IP(), dst_intf
.MAC())
227 if isinstance(match
, str):
228 match
+= ",dl_dst=%s" % dst_intf
.MAC()
230 match
= "dl_dst=%s" % dst_intf
.MAC()
232 cookie
= kwargs
.get('cookie', self
.get_cookie())
233 self
.cookies
.add(cookie
)
234 c
= self
.net
.setChain(
235 vnf_src_name
, vnf_dst_name
,
236 vnf_src_interface
=vnf_src_interface
,
237 vnf_dst_interface
=vnf_dst_interface
,
239 weight
=kwargs
.get('weight'),
243 path
=kwargs
.get('path'))
245 # to keep this logic seperate of the core son-emu do the housekeeping here
247 data
["src_vnf"] = vnf_src_name
248 data
["src_intf"] = vnf_src_interface
249 data
["dst_vnf"] = vnf_dst_name
250 data
["dst_intf"] = vnf_dst_interface
251 data
["cookie"] = cookie
252 data
["layer2"] = layer2
253 if kwargs
.get('path') is not None:
254 data
["path"] = kwargs
.get('path')
256 data
["path"] = self
._get
_path
(vnf_src_name
, vnf_dst_name
, vnf_src_interface
,
257 vnf_dst_interface
)[0]
259 # add route to dst ip to this interface
260 # this might block on containers that are still setting up, so start a new thread
261 if not kwargs
.get('no_route'):
262 # son_emu does not like concurrent commands for a container so we need to lock this if multiple chains
263 # on the same interface are created
264 src_node
.setHostRoute(dst_node
.intf(vnf_dst_interface
).IP(), vnf_src_interface
)
267 son_emu_data
= json
.loads(self
.get_son_emu_chain_data(vnf_src_name
))
269 son_emu_data
= dict()
270 if "son_emu_data" not in son_emu_data
:
271 son_emu_data
["son_emu_data"] = dict()
272 if "interfaces" not in son_emu_data
["son_emu_data"]:
273 son_emu_data
["son_emu_data"]["interfaces"] = dict()
274 if vnf_src_interface
not in son_emu_data
["son_emu_data"]["interfaces"]:
275 son_emu_data
["son_emu_data"]["interfaces"][vnf_src_interface
] = list()
276 son_emu_data
["son_emu_data"]["interfaces"][vnf_src_interface
].append(dst_intf
.IP())
278 self
.set_son_emu_chain_data(vnf_src_name
, son_emu_data
)
280 if kwargs
.get('bidirectional', False):
281 # call the reverse direction
282 path
= kwargs
.get('path')
284 path
= list(reversed(path
))
285 self
.network_action_start(vnf_dst_name
, vnf_src_name
, vnf_src_interface
=vnf_dst_interface
,
286 vnf_dst_interface
=vnf_src_interface
, bidirectional
=False,
287 layer2
=kwargs
.get('layer2', False), path
=path
,
288 no_route
=kwargs
.get('no_route'))
290 self
.full_chain_data
[flow
] = data
291 self
.chain_flow_cookies
[flow
] = cookie
293 except Exception as ex
:
294 logging
.exception("RPC error.")
295 raise Exception(ex
.message
)
297 def network_action_stop(self
, vnf_src_name
, vnf_dst_name
, **kwargs
):
299 Starts a network chain for a source destination pair
301 :param vnf_src_name: Name of the source VNF
302 :type vnf_src_name: ``str``
303 :param vnf_dst_name: Name of the source VNF interface
304 :type vnf_dst_name: ``str``
305 :param \**kwargs: See below
308 * *vnf_src_interface* (``str``): Name of source interface.
309 * *vnf_dst_interface* (``str``): Name of destination interface.
310 * *bidirectional* (``bool``): If set the chain will be torn down in both directions, else it will just\
311 be torn down from source to destination.
312 * *cookie* (``int``): Cookie value used by openflow. Used to identify the flows in the switches to be \
313 able to modify the correct flows.
316 if 'cookie' in kwargs
:
317 return self
.delete_flow_by_cookie(kwargs
.get('cookie'))
319 if kwargs
.get('bidirectional', False):
320 self
.delete_chain_by_intf(vnf_dst_name
, kwargs
.get('vnf_dst_interface'),
321 vnf_src_name
, kwargs
.get('vnf_src_interface'))
323 return self
.delete_chain_by_intf(vnf_src_name
, kwargs
.get('vnf_src_interface'),
324 vnf_dst_name
, kwargs
.get('vnf_dst_interface'))
325 except Exception as ex
:
326 logging
.exception("RPC error.")
329 def set_son_emu_chain_data(self
, vnf_name
, data
):
331 Set son-emu chain data for this node.
333 :param vnf_name: The name of the vnf where the data is stored.
334 :type vnf_name: ``str``
335 :param data: Raw data to store on the node.
338 self
.net
.getNodeByName(vnf_name
).cmd("echo \'%s\' > /tmp/son_emu_data.json" % json
.dumps(data
))
340 for intf
in data
['son_emu_data']['interfaces'].values():
343 self
.net
.getNodeByName(vnf_name
).cmd("echo \'%s\' > /tmp/son_emu_data" % "\n".join(ip_list
))
345 def get_son_emu_chain_data(self
, vnf_name
):
347 Get the current son-emu chain data set for this node.
349 :param vnf_name: The name of the vnf where the data is stored.
350 :type vnf_name: ``str``
351 :return: raw data stored on the node
354 return self
.net
.getNodeByName(vnf_name
).cmd("cat /tmp/son_emu_data.json")
356 def _get_connected_switch_data(self
, vnf_name
, vnf_interface
):
358 Get the switch an interface is connected to
359 :param vnf_name: Name of the VNF
360 :type vnf_name: ``str``
361 :param vnf_interface: Name of the VNF interface
362 :type vnf_interface: ``str``
363 :return: List containing the switch, and the inport number
364 :rtype: [``str``, ``int``]
367 src_sw_inport_nr
= None
368 for connected_sw
in self
.net
.DCNetwork_graph
.neighbors(vnf_name
):
369 link_dict
= self
.net
.DCNetwork_graph
[vnf_name
][connected_sw
]
370 for link
in link_dict
:
371 if (link_dict
[link
]['src_port_id'] == vnf_interface
or
373 'src_port_name'] == vnf_interface
):
374 # found the right link and connected switch
375 src_sw
= connected_sw
376 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
379 return src_sw
, src_sw_inport_nr
381 def _get_path(self
, src_vnf
, dst_vnf
, src_vnf_intf
, dst_vnf_intf
):
383 Own implementation of the get_path function from DCNetwork, because we just want the path and not set up
386 :param src_vnf: Name of the source VNF
387 :type src_vnf: ``str``
388 :param dst_vnf: Name of the destination VNF
389 :type dst_vnf: ``str``
390 :param src_vnf_intf: Name of the source VNF interface
391 :type src_vnf_intf: ``str``
392 :param dst_vnf_intf: Name of the destination VNF interface
393 :type dst_vnf_intf: ``str``
394 :return: path, src_sw, dst_sw
395 :rtype: ``list``, ``str``, ``str``
397 # modified version of the _chainAddFlow from emuvim.dcemulator.net._chainAddFlow
400 logging
.debug("Find shortest path from vnf %s to %s",
403 for connected_sw
in self
.net
.DCNetwork_graph
.neighbors(src_vnf
):
404 link_dict
= self
.net
.DCNetwork_graph
[src_vnf
][connected_sw
]
405 for link
in link_dict
:
406 if (link_dict
[link
]['src_port_id'] == src_vnf_intf
or
408 'src_port_name'] == src_vnf_intf
):
409 # found the right link and connected switch
410 src_sw
= connected_sw
413 for connected_sw
in self
.net
.DCNetwork_graph
.neighbors(dst_vnf
):
414 link_dict
= self
.net
.DCNetwork_graph
[connected_sw
][dst_vnf
]
415 for link
in link_dict
:
416 if link_dict
[link
]['dst_port_id'] == dst_vnf_intf
or \
418 'dst_port_name'] == dst_vnf_intf
:
419 # found the right link and connected
420 dst_sw
= connected_sw
422 logging
.debug("From switch %s to %s " % (src_sw
, dst_sw
))
426 # returns the first found shortest path
427 # if all shortest paths are wanted, use: all_shortest_paths
428 path
= nx
.shortest_path(self
.net
.DCNetwork_graph
, src_sw
, dst_sw
)
430 logging
.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
431 src_vnf
, dst_vnf
, src_sw
, dst_sw
))
432 logging
.debug("Graph nodes: %r" % self
.net
.DCNetwork_graph
.nodes())
433 logging
.debug("Graph edges: %r" % self
.net
.DCNetwork_graph
.edges())
434 for e
, v
in self
.net
.DCNetwork_graph
.edges():
435 logging
.debug("%r" % self
.net
.DCNetwork_graph
[e
][v
])
436 return "No path could be found between {0} and {1}".format(src_vnf
, dst_vnf
)
438 logging
.info("Shortest path between {0} and {1}: {2}".format(src_vnf
, dst_vnf
, path
))
439 return path
, src_sw
, dst_sw
441 def add_loadbalancer(self
, src_vnf_name
, src_vnf_interface
, lb_data
):
443 This function will set up a loadbalancer at the given interface.
445 :param src_vnf_name: Name of the source VNF
446 :type src_vnf_name: ``str``
447 :param src_vnf_interface: Name of the destination VNF
448 :type src_vnf_interface: ``str``
449 :param lb_data: A dictionary containing the destination data as well as custom path settings
450 :type lb_data: ``dict``
453 lbdata = {"dst_vnf_interfaces": {"dc2_man_web0": "port-man-2",
454 "dc3_man_web0": "port-man-4","dc4_man_web0": "port-man-6"}, "path": {"dc2_man_web0": {"port-man-2": [ "dc1.s1",\
460 dest_intfs_mapping
= lb_data
.get('dst_vnf_interfaces', dict())
461 # a custom path can be specified as a list of switches
462 custom_paths
= lb_data
.get('path', dict())
463 dest_vnf_outport_nrs
= list()
465 logging
.debug("Call to add_loadbalancer at %s intfs:%s" % (src_vnf_name
, src_vnf_interface
))
467 if not self
.check_vnf_intf_pair(src_vnf_name
, src_vnf_interface
):
468 raise Exception(u
"Source VNF %s or intfs %s does not exist" % (src_vnf_name
, src_vnf_interface
))
470 # find the switch belonging to the source interface, as well as the inport nr
471 for connected_sw
in net
.DCNetwork_graph
.neighbors(src_vnf_name
):
472 link_dict
= net
.DCNetwork_graph
[src_vnf_name
][connected_sw
]
473 for link
in link_dict
:
474 if link_dict
[link
]['src_port_name'] == src_vnf_interface
:
475 src_sw
= connected_sw
476 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
479 if src_sw
is None or src_sw_inport_nr
== 0:
480 raise Exception(u
"Source VNF or interface can not be found.")
482 # get all target interface outport numbers
483 for vnf_name
in dest_intfs_mapping
:
484 if vnf_name
not in net
.DCNetwork_graph
:
485 raise Exception(u
"Target VNF %s is not known." % vnf_name
)
486 for connected_sw
in net
.DCNetwork_graph
.neighbors(vnf_name
):
487 link_dict
= net
.DCNetwork_graph
[vnf_name
][connected_sw
]
488 for link
in link_dict
:
489 if link_dict
[link
]['src_port_name'] == dest_intfs_mapping
[vnf_name
]:
490 dest_vnf_outport_nrs
.append(int(link_dict
[link
]['dst_port_nr']))
492 if (src_vnf_name
, src_vnf_interface
) not in self
.lb_flow_cookies
:
493 self
.lb_flow_cookies
[(src_vnf_name
, src_vnf_interface
)] = list()
498 for intf
in net
[src_vnf_name
].intfs
.values():
499 if intf
.name
== src_vnf_interface
:
504 # set up paths for each destination vnf individually
506 cookie
= self
.get_cookie()
507 main_cmd
= "add-flow -OOpenFlow13"
508 self
.lb_flow_cookies
[(src_vnf_name
, src_vnf_interface
)].append(cookie
)
512 data
["src_vnf"] = src_vnf_name
513 data
["src_intf"] = src_vnf_interface
514 data
["paths"] = list()
515 data
["cookie"] = cookie
517 # lb mac for src -> target connections
518 lb_mac
= "31:33:70:%02x:%02x:%02x" % (random
.randint(0, 255),random
.randint(0, 255),random
.randint(0, 255))
520 # calculate lb ip as src_intf.ip +1
521 octets
= src_ip
.split('.')
522 octets
[3] = str(int(octets
[3]) + 1)
523 plus_one
= '.'.join(octets
)
525 # set up arp reply as well as add the route to the interface
526 self
.setup_arp_reply_at(src_sw
, src_sw_inport_nr
, plus_one
, lb_mac
, cookie
=cookie
)
527 net
.getNodeByName(src_vnf_name
).setHostRoute(plus_one
, src_vnf_interface
)
529 for dst_vnf_name
, dst_vnf_interface
in dest_intfs_mapping
.items():
530 path
, src_sw
, dst_sw
= self
._get
_path
(src_vnf_name
, dst_vnf_name
,
531 src_vnf_interface
, dst_vnf_interface
)
533 # use custom path if one is supplied
534 # json does not support hashing on tuples so we use nested dicts
535 if custom_paths
is not None and dst_vnf_name
in custom_paths
:
536 if dst_vnf_interface
in custom_paths
[dst_vnf_name
]:
537 path
= custom_paths
[dst_vnf_name
][dst_vnf_interface
]
538 logging
.debug("Taking custom path from %s to %s: %s" % (src_vnf_name
, dst_vnf_name
, path
))
540 if not self
.check_vnf_intf_pair(dst_vnf_name
, dst_vnf_interface
):
541 self
.delete_loadbalancer(src_vnf_name
, src_vnf_interface
)
542 raise Exception(u
"VNF %s or intfs %s does not exist" % (dst_vnf_name
, dst_vnf_interface
))
543 if isinstance(path
, dict):
544 self
.delete_loadbalancer(src_vnf_name
, src_vnf_interface
)
545 raise Exception(u
"Can not find a valid path. Are you specifying the right interfaces?.")
547 target_mac
= "fa:17:00:03:13:37"
548 target_ip
= "0.0.0.0"
549 for intf
in net
[dst_vnf_name
].intfs
.values():
550 if intf
.name
== dst_vnf_interface
:
551 target_mac
= str(intf
.mac
)
552 target_ip
= str(intf
.ip
)
553 dst_sw_outport_nr
= dest_vnf_outport_nrs
[index
]
555 switch_inport_nr
= src_sw_inport_nr
557 #self.setup_arp_reply_at(src_sw, src_sw_inport_nr, target_ip, target_mac, cookie=cookie)
558 net
.getNodeByName(dst_vnf_name
).setHostRoute(src_ip
, dst_vnf_interface
)
560 # choose free vlan if path contains more than 1 switch
562 vlan
= net
.vlans
.pop()
564 vlan
= net
.vlans
.pop()
568 single_flow_data
= dict()
569 single_flow_data
["dst_vnf"] = dst_vnf_name
570 single_flow_data
["dst_intf"] = dst_vnf_interface
571 single_flow_data
["path"] = path
572 single_flow_data
["vlan"] = vlan
573 single_flow_data
["cookie"] = cookie
575 data
["paths"].append(single_flow_data
)
578 for i
in range(0, len(path
)):
579 if i
< len(path
) - 1:
580 next_hop
= path
[i
+ 1]
582 # last switch reached
583 next_hop
= dst_vnf_name
584 next_node
= net
.getNodeByName(next_hop
)
585 if next_hop
== dst_vnf_name
:
586 switch_outport_nr
= dst_sw_outport_nr
587 logging
.info("end node reached: {0}".format(dst_vnf_name
))
588 elif not isinstance(next_node
, OVSSwitch
):
589 logging
.info("Next node: {0} is not a switch".format(next_hop
))
590 return "Next node: {0} is not a switch".format(next_hop
)
592 # take first link between switches by default
594 switch_outport_nr
= net
.DCNetwork_graph
[current_hop
][next_hop
][index_edge_out
]['src_port_nr']
596 cmd
= 'priority=1,in_port=%s,cookie=%s' % (switch_inport_nr
, cookie
)
597 cmd_back
= 'priority=1,in_port=%s,cookie=%s' % (switch_outport_nr
, cookie
)
598 # if a vlan is picked, the connection is routed through multiple switches
600 if path
.index(current_hop
) == 0: # first node
602 cmd
= 'in_port=%s' % src_sw_inport_nr
603 cmd
+= ',cookie=%s' % cookie
604 cmd
+= ',table=%s' % cookie
606 cmd
+= ',reg1=%s' % index
609 cmd
+= ',push_vlan:0x8100'
610 masked_vlan
= vlan |
0x1000
611 cmd
+= ',set_field:%s->vlan_vid' % masked_vlan
612 cmd
+= ',set_field:%s->eth_dst' % target_mac
613 cmd
+= ',set_field:%s->ip_dst' % target_ip
614 cmd
+= ',output:%s' % switch_outport_nr
616 # last switch for reverse route
617 # remove any vlan tags
618 cmd_back
+= ',dl_vlan=%s' % vlan
619 cmd_back
+= ',actions=pop_vlan,output:%s' % switch_inport_nr
620 elif next_hop
== dst_vnf_name
: # last switch
621 # remove any vlan tags
622 cmd
+= ',dl_vlan=%s' % vlan
623 cmd
+= ',actions=pop_vlan,output:%s' % switch_outport_nr
624 # set up arp replys at the port so the dst nodes know the src
625 self
.setup_arp_reply_at(current_hop
, switch_outport_nr
, src_ip
, src_mac
, cookie
=cookie
)
628 cmd_back
= 'in_port=%s' % switch_outport_nr
629 cmd_back
+= ',cookie=%s' % cookie
631 cmd_back
+= ',actions='
632 cmd_back
+= 'push_vlan:0x8100'
633 masked_vlan
= vlan |
0x1000
634 cmd_back
+= ',set_field:%s->vlan_vid' % masked_vlan
635 cmd_back
+= ',set_field:%s->eth_src' % lb_mac
636 cmd_back
+= ',set_field:%s->ip_src' % plus_one
637 cmd_back
+= ',output:%s' % switch_inport_nr
639 # if we have a circle in the path we need to specify this, as openflow will ignore the packet
640 # if we just output it on the same port as it came in
641 if switch_inport_nr
== switch_outport_nr
:
642 cmd
+= ',dl_vlan=%s,actions=IN_PORT' % (vlan
)
643 cmd_back
+= ',dl_vlan=%s,actions=IN_PORT' % (vlan
)
645 cmd
+= ',dl_vlan=%s,actions=output:%s' % (vlan
, switch_outport_nr
)
646 cmd_back
+= ',dl_vlan=%s,actions=output:%s' % (vlan
, switch_inport_nr
)
647 # output the packet at the correct outport
649 cmd
= 'in_port=%s' % src_sw_inport_nr
650 cmd
+= ',cookie=%s' % cookie
651 cmd
+= ',table=%s' % cookie
653 cmd
+= ',reg1=%s' % index
655 cmd
+= ',set_field:%s->eth_dst' % target_mac
656 cmd
+= ',set_field:%s->ip_dst' % target_ip
657 cmd
+= ',output:%s' % switch_outport_nr
660 cmd_back
= 'in_port=%s' % switch_outport_nr
661 cmd_back
+= ',cookie=%s' % cookie
663 cmd_back
+= ',actions='
664 cmd_back
+= ',set_field:%s->eth_src' % lb_mac
665 cmd_back
+= ',set_field:%s->ip_src' % plus_one
666 cmd_back
+= ',output:%s' % src_sw_inport_nr
668 self
.setup_arp_reply_at(current_hop
, switch_outport_nr
, src_ip
, src_mac
, cookie
=cookie
)
670 # excecute the command on the target switch
673 cmd_back
= "\"%s\"" % cmd_back
674 net
[current_hop
].dpctl(main_cmd
, cmd
)
675 net
[current_hop
].dpctl(main_cmd
, cmd_back
)
677 # set next hop for the next iteration step
678 if isinstance(next_node
, OVSSwitch
):
679 switch_inport_nr
= net
.DCNetwork_graph
[current_hop
][next_hop
][0]['dst_port_nr']
680 current_hop
= next_hop
682 # advance to next destination
685 # set up the actual load balancing rule as a multipath on the very first switch
686 cmd
= '"in_port=%s' % src_sw_inport_nr
687 cmd
+= ',cookie=%s' % (cookie
)
690 # push 0x01 into the first register
691 cmd
+= 'load:0x1->NXM_NX_REG0[]'
692 # load balance modulo n over all dest interfaces
693 # TODO: in newer openvswitch implementations this should be changed to symmetric_l3l4+udp
694 # to balance any kind of traffic
695 cmd
+= ',multipath(symmetric_l4,1024,modulo_n,%s,0,NXM_NX_REG1[0..12])' % len(dest_intfs_mapping
)
696 # reuse the cookie as table entry as it will be unique
697 cmd
+= ',resubmit(, %s)"' % cookie
699 # actually add the flow
700 logging
.debug("Switch: %s, CMD: %s" % (src_sw
, cmd
))
701 net
[src_sw
].dpctl(main_cmd
, cmd
)
703 # finally add all flow data to the internal data storage
704 self
.full_lb_data
[(src_vnf_name
, src_vnf_interface
)] = data
706 def add_floating_lb(self
, datacenter
, lb_data
):
708 This function will set up a loadbalancer at the given datacenter.
709 This function returns the floating ip assigned to the loadbalancer as multiple ones are possible.
711 :param datacenter: The datacenter entrypoint
712 :type datacenter: ``str``
713 :param lb_data: A dictionary containing the destination data as well as custom path settings
714 :type lb_data: ``dict``
717 lbdata = {"dst_vnf_interfaces": {"dc2_man_web0": "port-man-2",
718 "dc3_man_web0": "port-man-4","dc4_man_web0": "port-man-6"}, "path": {"dc2_man_web0": {"port-man-2": [ "dc1.s1",\
723 src_sw
= self
.floating_switch
.name
724 dest_intfs_mapping
= lb_data
.get('dst_vnf_interfaces', dict())
725 # a custom path can be specified as a list of switches
726 custom_paths
= lb_data
.get('path', dict())
727 dest_vnf_outport_nrs
= list()
729 if datacenter
not in self
.net
.dcs
:
730 raise Exception(u
"Source datacenter can not be found.")
732 # get all target interface outport numbers
733 for vnf_name
in dest_intfs_mapping
:
734 if vnf_name
not in net
.DCNetwork_graph
:
735 raise Exception(u
"Target VNF %s is not known." % vnf_name
)
736 for connected_sw
in net
.DCNetwork_graph
.neighbors(vnf_name
):
737 link_dict
= net
.DCNetwork_graph
[vnf_name
][connected_sw
]
738 for link
in link_dict
:
739 if link_dict
[link
]['src_port_name'] == dest_intfs_mapping
[vnf_name
]:
740 dest_vnf_outport_nrs
.append(int(link_dict
[link
]['dst_port_nr']))
742 if len(dest_vnf_outport_nrs
) == 0:
743 raise Exception("There are no paths specified for the loadbalancer")
744 src_ip
= self
.floating_intf
.IP()
745 src_mac
= self
.floating_intf
.MAC()
747 # set up paths for each destination vnf individually
749 cookie
= self
.get_cookie()
750 main_cmd
= "add-flow -OOpenFlow13"
751 floating_ip
= self
.floating_network
.get_new_ip_address("floating-ip").split("/")[0]
753 for dst_vnf_name
, dst_vnf_interface
in dest_intfs_mapping
.items():
755 # use custom path if one is supplied
756 # json does not support hashing on tuples so we use nested dicts
757 if custom_paths
is not None and dst_vnf_name
in custom_paths
:
758 if dst_vnf_interface
in custom_paths
[dst_vnf_name
]:
759 path
= custom_paths
[dst_vnf_name
][dst_vnf_interface
]
760 logging
.debug("Taking custom path to %s: %s" % (dst_vnf_name
, path
))
762 if datacenter
not in self
.floating_links
:
763 self
.floating_links
[datacenter
] = \
764 net
.addLink(self
.floating_switch
, datacenter
)
766 self
._get
_path
(self
.floating_root
.name
, dst_vnf_name
, self
.floating_intf
.name
, dst_vnf_interface
)[0]
768 if isinstance(path
, dict):
769 self
.delete_flow_by_cookie(cookie
)
770 raise Exception(u
"Can not find a valid path. Are you specifying the right interfaces?.")
772 intf
= net
[dst_vnf_name
].nameToIntf
[dst_vnf_interface
]
773 target_mac
= str(intf
.MAC())
774 target_ip
= str(intf
.IP())
775 dst_sw_outport_nr
= dest_vnf_outport_nrs
[index
]
777 switch_inport_nr
= src_sw_inport_nr
778 vlan
= net
.vlans
.pop()
780 # iterate all switches on the path
781 for i
in range(0, len(path
)):
782 if i
< len(path
) - 1:
783 next_hop
= path
[i
+ 1]
785 # last switch reached
786 next_hop
= dst_vnf_name
787 next_node
= net
.getNodeByName(next_hop
)
790 if next_hop
== dst_vnf_name
:
791 switch_outport_nr
= dst_sw_outport_nr
792 logging
.info("end node reached: {0}".format(dst_vnf_name
))
793 elif not isinstance(next_node
, OVSSwitch
):
794 logging
.info("Next node: {0} is not a switch".format(next_hop
))
795 return "Next node: {0} is not a switch".format(next_hop
)
797 # take first link between switches by default
799 switch_outport_nr
= net
.DCNetwork_graph
[current_hop
][next_hop
][index_edge_out
]['src_port_nr']
801 # default filters, just overwritten on the first node and last node
802 cmd
= 'priority=1,in_port=%s,cookie=%s' % (switch_inport_nr
, cookie
)
803 cmd_back
= 'priority=1,in_port=%s,cookie=%s' % (switch_outport_nr
, cookie
)
804 if i
== 0: # first node
805 cmd
= 'in_port=%s' % src_sw_inport_nr
806 cmd
+= ',cookie=%s' % cookie
807 cmd
+= ',table=%s' % cookie
809 cmd
+= ',ip_dst=%s' % floating_ip
810 cmd
+= ',reg1=%s' % index
813 cmd
+= ',push_vlan:0x8100'
814 masked_vlan
= vlan |
0x1000
815 cmd
+= ',set_field:%s->vlan_vid' % masked_vlan
816 cmd
+= ',set_field:%s->eth_dst' % target_mac
817 cmd
+= ',set_field:%s->ip_dst' % target_ip
818 cmd
+= ',output:%s' % switch_outport_nr
820 # last switch for reverse route
821 # remove any vlan tags
822 cmd_back
+= ',dl_vlan=%s' % vlan
823 cmd_back
+= ',actions=pop_vlan,output:%s' % switch_inport_nr
824 self
.setup_arp_reply_at(current_hop
, src_sw_inport_nr
, floating_ip
, target_mac
, cookie
=cookie
)
825 elif next_hop
== dst_vnf_name
: # last switch
826 # remove any vlan tags
827 cmd
+= ',dl_vlan=%s' % vlan
828 cmd
+= ',actions=pop_vlan,output:%s' % switch_outport_nr
829 # set up arp replys at the port so the dst nodes know the src
830 self
.setup_arp_reply_at(current_hop
, switch_outport_nr
, src_ip
, src_mac
, cookie
=cookie
)
833 cmd_back
= 'in_port=%s' % switch_outport_nr
834 cmd_back
+= ',cookie=%s' % cookie
836 cmd_back
+= ',actions='
837 cmd_back
+= 'push_vlan:0x8100'
838 masked_vlan
= vlan |
0x1000
839 cmd_back
+= ',set_field:%s->vlan_vid' % masked_vlan
840 cmd_back
+= ',set_field:%s->eth_src' % src_mac
841 cmd_back
+= ',set_field:%s->ip_src' % floating_ip
842 cmd_back
+= ',output:%s' % switch_inport_nr
843 net
.getNodeByName(dst_vnf_name
).setHostRoute(src_ip
, dst_vnf_interface
)
845 # if we have a circle in the path we need to specify this, as openflow will ignore the packet
846 # if we just output it on the same port as it came in
847 if switch_inport_nr
== switch_outport_nr
:
848 cmd
+= ',dl_vlan=%s,actions=IN_PORT' % (vlan
)
849 cmd_back
+= ',dl_vlan=%s,actions=IN_PORT' % (vlan
)
851 cmd
+= ',dl_vlan=%s,actions=output:%s' % (vlan
, switch_outport_nr
)
852 cmd_back
+= ',dl_vlan=%s,actions=output:%s' % (vlan
, switch_inport_nr
)
854 # excecute the command on the target switch
857 cmd_back
= "\"%s\"" % cmd_back
858 net
[current_hop
].dpctl(main_cmd
, cmd
)
859 net
[current_hop
].dpctl(main_cmd
, cmd_back
)
861 # set next hop for the next iteration step
862 if isinstance(next_node
, OVSSwitch
):
863 switch_inport_nr
= net
.DCNetwork_graph
[current_hop
][next_hop
][0]['dst_port_nr']
864 current_hop
= next_hop
866 # advance to next destination
869 # set up the actual load balancing rule as a multipath on the very first switch
870 cmd
= '"in_port=%s' % src_sw_inport_nr
871 cmd
+= ',cookie=%s' % (cookie
)
874 # push 0x01 into the first register
875 cmd
+= 'load:0x1->NXM_NX_REG0[]'
876 # load balance modulo n over all dest interfaces
877 # TODO: in newer openvswitch implementations this should be changed to symmetric_l3l4+udp
878 # to balance any kind of traffic
879 cmd
+= ',multipath(symmetric_l4,1024,modulo_n,%s,0,NXM_NX_REG1[0..12])' % len(dest_intfs_mapping
)
880 # reuse the cookie as table entry as it will be unique
881 cmd
+= ',resubmit(, %s)"' % cookie
883 # actually add the flow
884 logging
.debug("Switch: %s, CMD: %s" % (src_sw
, cmd
))
885 net
[src_sw
].dpctl(main_cmd
, cmd
)
887 self
.floating_cookies
[cookie
] = floating_ip
889 return cookie
, floating_ip
891 def setup_arp_reply_at(self
, switch
, port_nr
, target_ip
, target_mac
, cookie
=None):
893 Sets up a custom ARP reply at a switch.
894 An ARP request coming in on the `port_nr` for `target_ip` will be answered with target IP/MAC.
896 :param switch: The switch belonging to the interface
897 :type switch: ``str``
898 :param port_nr: The port number at the switch that is connected to the interface
899 :type port_nr: ``int``
900 :param target_ip: The IP for which to set up the ARP reply
901 :type target_ip: ``str``
902 :param target_mac: The MAC address of the target interface
903 :type target_mac: ``str``
904 :param cookie: cookie to identify the ARP request, if None a new one will be picked
905 :type cookie: ``int`` or ``None``
910 cookie
= self
.get_cookie()
911 main_cmd
= "add-flow -OOpenFlow13"
913 # first set up ARP requests for the source node, so it will always 'find' a partner
914 cmd
= '"in_port=%s' % port_nr
915 cmd
+= ',cookie=%s' % cookie
917 # only answer for target ip arp requests
918 cmd
+= ',arp_tpa=%s' % target_ip
920 # set message type to ARP reply
921 cmd
+= 'load:0x2->NXM_OF_ARP_OP[]'
922 # set src ip as dst ip
923 cmd
+= ',move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[]'
925 cmd
+= ',set_field:%s->eth_src' % target_mac
927 cmd
+= ',move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[], move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[]'
928 # set target mac as hex
929 cmd
+= ',load:0x%s->NXM_NX_ARP_SHA[]' % "".join(target_mac
.split(':'))
930 # set target ip as hex
931 octets
= target_ip
.split('.')
932 dst_ip_hex
= '{:02X}{:02X}{:02X}{:02X}'.format(*map(int, octets
))
933 cmd
+= ',load:0x%s->NXM_OF_ARP_SPA[]' % dst_ip_hex
934 # output to incoming port remember the closing "
936 self
.net
[switch
].dpctl(main_cmd
, cmd
)
938 "Set up ARP reply at %s port %s." % (switch
, port_nr
))
940 def delete_flow_by_cookie(self
, cookie
):
942 Removes a flow identified by the cookie
944 :param cookie: The cookie for the specified flow
945 :type cookie: ``int``
946 :return: True if successful, else false
951 logging
.debug("Deleting flow by cookie %d" % (cookie
))
953 # we have to call delete-group for each switch
954 for node
in self
.net
.switches
:
956 flow
["dpid"] = int(node
.dpid
, 16)
957 flow
["cookie"] = cookie
958 flow
['cookie_mask'] = int('0xffffffffffffffff', 16)
962 logging
.debug("Deleting flowentry with cookie %d" % (
964 if self
.net
.controller
== RemoteController
:
965 self
.net
.ryu_REST('stats/flowentry/delete', data
=flow
)
967 self
.cookies
.remove(cookie
)
970 def delete_chain_by_intf(self
, src_vnf_name
, src_vnf_intf
, dst_vnf_name
, dst_vnf_intf
):
972 Removes a flow identified by the vnf_name/vnf_intf pairs
974 :param src_vnf_name: The vnf name for the specified flow
975 :type src_vnf_name: ``str``
976 :param src_vnf_intf: The interface name for the specified flow
977 :type src_vnf_intf: ``str``
978 :param dst_vnf_name: The vnf name for the specified flow
979 :type dst_vnf_name: ``str``
980 :param dst_vnf_intf: The interface name for the specified flow
981 :type dst_vnf_intf: ``str``
982 :return: True if successful, else false
985 logging
.debug("Deleting flow for vnf/intf pair %s %s" % (src_vnf_name
, src_vnf_intf
))
986 if not self
.check_vnf_intf_pair(src_vnf_name
, src_vnf_intf
):
988 if not self
.check_vnf_intf_pair(dst_vnf_name
, dst_vnf_intf
):
990 target_flow
= (src_vnf_name
, src_vnf_intf
, dst_vnf_name
, dst_vnf_intf
)
991 if not target_flow
in self
.chain_flow_cookies
:
994 success
= self
.delete_flow_by_cookie(self
.chain_flow_cookies
[target_flow
])
997 del self
.chain_flow_cookies
[target_flow
]
998 del self
.full_chain_data
[target_flow
]
1002 def delete_loadbalancer(self
, vnf_src_name
, vnf_src_interface
):
1004 Removes a loadbalancer that is configured for the node and interface
1006 :param src_vnf_name: Name of the source VNF
1007 :param src_vnf_interface: Name of the destination VNF
1010 # we have to call delete-group for each switch
1011 delete_group
= list()
1012 group_id
= self
.get_flow_group(vnf_src_name
, vnf_src_interface
)
1013 for node
in self
.net
.switches
:
1014 for cookie
in self
.lb_flow_cookies
[(vnf_src_name
, vnf_src_interface
)]:
1016 flow
["dpid"] = int(node
.dpid
, 16)
1017 flow
["cookie"] = cookie
1018 flow
['cookie_mask'] = int('0xffffffffffffffff', 16)
1022 group_del
["dpid"] = int(node
.dpid
, 16)
1023 group_del
["group_id"] = group_id
1024 delete_group
.append(group_del
)
1027 logging
.debug("Deleting flowentry with cookie %d belonging to lb at %s:%s" % (
1028 flow
["cookie"], vnf_src_name
, vnf_src_interface
))
1029 if self
.net
.controller
== RemoteController
:
1030 self
.net
.ryu_REST('stats/flowentry/delete', data
=flow
)
1032 logging
.debug("Deleting group with id %s" % group_id
)
1033 for switch_del_group
in delete_group
:
1034 if self
.net
.controller
== RemoteController
:
1035 self
.net
.ryu_REST("stats/groupentry/delete", data
=switch_del_group
)
1037 # unmap groupid from the interface
1038 target_pair
= (vnf_src_name
, vnf_src_interface
)
1039 if target_pair
in self
.flow_groups
:
1040 del self
.flow_groups
[target_pair
]
1041 if target_pair
in self
.full_lb_data
:
1042 del self
.full_lb_data
[target_pair
]
1044 def delete_floating_lb(self
, cookie
):
1046 Delete a floating loadbalancer.
1047 Floating loadbalancers are different from normal ones as there are multiple ones on the same interface.
1048 :param cookie: The cookie of the loadbalancer
1049 :type cookie: ``int``
1051 cookie
= int(cookie
)
1052 if cookie
not in self
.floating_cookies
:
1053 raise Exception("Can not delete floating loadbalancer as the flowcookie is not known")
1055 self
.delete_flow_by_cookie(cookie
)
1056 floating_ip
= self
.floating_cookies
[cookie
]
1057 self
.floating_network
.withdraw_ip_address(floating_ip
)
1059 def set_arp_entry(self
, vnf_name
, vnf_interface
, ip
, mac
):
1061 Sets an arp entry on the specified VNF. This is done on the node directly and not by open vswitch!
1062 :param vnf_name: Name of the VNF
1063 :type vnf_name: ``str``
1064 :param vnf_interface: Name of the interface
1065 :type vnf_interface: ``str``
1066 :param ip: IP to reply to
1068 :param mac: Answer with this MAC
1071 node
= self
.net
.getNodeByName(vnf_name
)
1072 node
.cmd("arp -i %s -s %s %s" % (vnf_interface
, ip
, mac
))