1 # Copyright (c) 2015 SONATA-NFV and Paderborn University
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 # Neither the name of the SONATA-NFV, Paderborn University
17 # nor the names of its contributors may be used to endorse or promote
18 # products derived from this software without specific prior written
21 # This work has been performed in the framework of the SONATA project,
22 # funded by the European Commission under Grant number 671517 through
23 # the Horizon 2020 and 5G-PPP programmes. The authors would like to
24 # acknowledge the contributions of their colleagues of the SONATA
25 # partner consortium (www.sonata-nfv.eu).
33 from emuvim
.api
.openstack
.resources
.net
import Net
34 from emuvim
.api
.openstack
.resources
.port
import Port
35 from mininet
.node
import OVSSwitch
, RemoteController
, Node
38 class OpenstackManage(object):
40 OpenstackManage is a singleton and management component for the emulator.
41 It is the brain of the Openstack component and manages everything that is not datacenter specific like
42 network chains or load balancers.
47 if OpenstackManage
.__instance
is None:
48 OpenstackManage
.__instance
= object.__new
__(cls
)
49 return OpenstackManage
.__instance
51 def __init__(self
, ip
="0.0.0.0", port
=4000):
52 # we are a singleton, only initialize once!
53 self
.lock
= threading
.Lock()
55 if hasattr(self
, "init"):
59 self
.endpoints
= dict()
65 # to keep track which src_vnf(input port on the switch) handles a load
67 self
.lb_flow_cookies
= dict()
68 self
.chain_flow_cookies
= dict()
70 # for the visualization also store the complete chain data incl. paths
71 self
.full_chain_data
= dict()
72 self
.full_lb_data
= dict()
74 # flow groups could be handled for each switch separately, but this global group counter should be easier to
75 # debug and to maintain
76 self
.flow_groups
= dict()
78 # we want one global chain api. this should not be datacenter
80 self
.chain
= chain_api
.ChainApi(ip
, port
, self
)
81 self
.thread
= threading
.Thread(target
=self
.chain
._start
_flask
, args
=())
82 self
.thread
.daemon
= True
83 self
.thread
.name
= self
.chain
.__class
__
86 # floating ip network setup
87 self
.floating_switch
= None
88 self
.floating_network
= None
89 self
.floating_netmask
= "192.168.100.0/24"
90 self
.floating_nodes
= dict()
91 self
.floating_cookies
= dict()
92 self
.floating_intf
= None
93 self
.floating_links
= dict()
100 def net(self
, value
):
101 if self
._net
is None:
103 # create default networks
104 self
.init_floating_network()
107 def init_floating_network(self
, name
="default"):
109 Initialize the floating network component for the emulator.
110 Will not do anything if already initialized.
112 if self
.net
is not None and self
.floating_switch
is None:
113 # create a floating network
114 fn
= self
.floating_network
= Net(name
)
115 fn
.id = str(uuid
.uuid4())
116 fn
.set_cidr(self
.floating_netmask
)
119 fn
.subnet_id
= str(uuid
.uuid4())
120 fn
.subnet_name
= fn
.name
+ "-sub"
122 # create a port for the host
123 port
= Port("root-port")
124 # port.id = str(uuid.uuid4())
125 port
.net_name
= fn
.name
128 root_ip
= fn
.get_new_ip_address(port
.name
)
129 port
.ip_address
= root_ip
130 # floating ip network setup
131 # wierd way of getting a datacenter object
132 first_dc
= self
.net
.dcs
.values()[0]
133 # set a dpid for the switch. for this we have to get the id of the
135 self
.floating_switch
= self
.net
.addSwitch(
136 "fs1", dpid
=hex(first_dc
._get
_next
_dc
_dpid
())[2:])
137 # this is the interface appearing on the physical host
138 self
.floating_root
= Node('root', inNamespace
=False)
139 self
.net
.hosts
.append(self
.floating_root
)
140 self
.net
.nameToNode
['root'] = self
.floating_root
141 self
.floating_intf
= self
.net
.addLink(
142 self
.floating_root
, self
.floating_switch
).intf1
143 self
.floating_root
.setIP(root_ip
, intf
=self
.floating_intf
)
144 self
.floating_nodes
[(self
.floating_root
.name
,
145 root_ip
)] = self
.floating_root
147 def stop_floating_network(self
):
149 self
.floating_switch
= None
151 def add_endpoint(self
, ep
):
153 Registers an openstack endpoint with manage
155 :param ep: Openstack API endpoint
156 :type ep: :class:`heat.openstack_api_endpoint`
158 key
= "%s:%s" % (ep
.ip
, ep
.port
)
159 self
.endpoints
[key
] = ep
161 def get_cookie(self
):
163 Get an unused cookie.
168 cookie
= int(max(self
.cookies
) + 1)
169 self
.cookies
.add(cookie
)
172 def get_flow_group(self
, src_vnf_name
, src_vnf_interface
):
174 Gets free group that is not currently used by any other flow for the specified interface / VNF.
176 :param src_vnf_name: Source VNF name
177 :type src_vnf_name: ``str``
178 :param src_vnf_interface: Source VNF interface name
179 :type src_vnf_interface: ``str``
180 :return: Flow group identifier.
183 if (src_vnf_name
, src_vnf_interface
) not in self
.flow_groups
:
184 grp
= int(len(self
.flow_groups
) + 1)
185 self
.flow_groups
[(src_vnf_name
, src_vnf_interface
)] = grp
187 grp
= self
.flow_groups
[(src_vnf_name
, src_vnf_interface
)]
190 def check_vnf_intf_pair(self
, vnf_name
, vnf_intf_name
):
192 Checks if a VNF exists and has the given interface
194 :param vnf_name: Name of the VNF to be checked
195 :type vnf_name: ``str``
196 :param vnf_intf_name: Name of the interface that belongst to the VNF
197 :type vnf_intf_name: ``str``
198 :return: ``True`` if it is valid pair, else ``False``
202 if vnf_name
in self
.net
:
203 vnf
= self
.net
.getNodeByName(vnf_name
)
204 return vnf_intf_name
in vnf
.nameToIntf
206 def network_action_start(self
, vnf_src_name
, vnf_dst_name
, **kwargs
):
208 Starts a network chain for a source destination pair
210 :param vnf_src_name: Name of the source VNF
211 :type vnf_src_name: ``str``
212 :param vnf_dst_name: Name of the source VNF interface
213 :type vnf_dst_name: ``str``
214 :param \**kwargs: See below
217 * *vnf_src_interface* (``str``): Name of source interface.
218 * *vnf_dst_interface* (``str``): Name of destination interface.
219 * *weight* (``int``): This value is fed into the shortest path computation if no path is specified.
220 * *match* (``str``): A custom match entry for the openflow flow rules. Only vlanid or port possible.
221 * *bidirectional* (``bool``): If set the chain will be set in both directions, else it will just set up \
222 from source to destination.
223 * *cookie* (``int``): Cookie value used by openflow. Used to identify the flows in the switches to be \
224 able to modify the correct flows.
225 * *no_route* (``bool``): If set a layer 3 route to the target interface will not be set up.
226 :return: The cookie chosen for the flow.
230 vnf_src_interface
= kwargs
.get('vnf_src_interface')
231 vnf_dst_interface
= kwargs
.get('vnf_dst_interface')
232 layer2
= kwargs
.get('layer2', True)
233 match
= kwargs
.get('match')
234 flow
= (vnf_src_name
, vnf_src_interface
,
235 vnf_dst_name
, vnf_dst_interface
)
236 if flow
in self
.chain_flow_cookies
:
238 "There is already a chain at the specified src/dst pair!")
239 # set up a layer 2 chain, this allows multiple chains for the same
241 src_node
= self
.net
.getNodeByName(vnf_src_name
)
242 dst_node
= self
.net
.getNodeByName(vnf_dst_name
)
243 dst_intf
= dst_node
.intf(vnf_dst_interface
)
245 switch
, inport
= self
._get
_connected
_switch
_data
(
246 vnf_src_name
, vnf_src_interface
)
247 self
.setup_arp_reply_at(
248 switch
, inport
, dst_intf
.IP(), dst_intf
.MAC())
249 if isinstance(match
, str):
250 match
+= ",dl_dst=%s" % dst_intf
.MAC()
252 match
= "dl_dst=%s" % dst_intf
.MAC()
254 cookie
= kwargs
.get('cookie', self
.get_cookie())
255 self
.cookies
.add(cookie
)
257 vnf_src_name
, vnf_dst_name
,
258 vnf_src_interface
=vnf_src_interface
,
259 vnf_dst_interface
=vnf_dst_interface
,
261 weight
=kwargs
.get('weight'),
265 path
=kwargs
.get('path'))
267 # to keep this logic seperate of the core son-emu do the
270 data
["src_vnf"] = vnf_src_name
271 data
["src_intf"] = vnf_src_interface
272 data
["dst_vnf"] = vnf_dst_name
273 data
["dst_intf"] = vnf_dst_interface
274 data
["cookie"] = cookie
275 data
["layer2"] = layer2
276 if kwargs
.get('path') is not None:
277 data
["path"] = kwargs
.get('path')
279 data
["path"] = self
._get
_path
(vnf_src_name
, vnf_dst_name
, vnf_src_interface
,
280 vnf_dst_interface
)[0]
282 # add route to dst ip to this interface
283 # this might block on containers that are still setting up, so
285 if not kwargs
.get('no_route'):
286 # son_emu does not like concurrent commands for a container so we need to lock this if multiple chains
287 # on the same interface are created
288 src_node
.setHostRoute(dst_node
.intf(
289 vnf_dst_interface
).IP(), vnf_src_interface
)
292 son_emu_data
= json
.loads(
293 self
.get_son_emu_chain_data(vnf_src_name
))
294 except BaseException
:
295 son_emu_data
= dict()
296 if "son_emu_data" not in son_emu_data
:
297 son_emu_data
["son_emu_data"] = dict()
298 if "interfaces" not in son_emu_data
["son_emu_data"]:
299 son_emu_data
["son_emu_data"]["interfaces"] = dict()
300 if vnf_src_interface
not in son_emu_data
["son_emu_data"]["interfaces"]:
301 son_emu_data
["son_emu_data"]["interfaces"][vnf_src_interface
] = list()
302 son_emu_data
["son_emu_data"]["interfaces"][vnf_src_interface
].append(
305 self
.set_son_emu_chain_data(vnf_src_name
, son_emu_data
)
307 if kwargs
.get('bidirectional', False):
308 # call the reverse direction
309 path
= kwargs
.get('path')
311 path
= list(reversed(path
))
312 self
.network_action_start(vnf_dst_name
, vnf_src_name
, vnf_src_interface
=vnf_dst_interface
,
313 vnf_dst_interface
=vnf_src_interface
, bidirectional
=False,
314 layer2
=kwargs
.get('layer2', False), path
=path
,
315 no_route
=kwargs
.get('no_route'))
317 self
.full_chain_data
[flow
] = data
318 self
.chain_flow_cookies
[flow
] = cookie
320 except Exception as ex
:
321 logging
.exception("RPC error.")
322 raise Exception(ex
.message
)
324 def network_action_stop(self
, vnf_src_name
, vnf_dst_name
, **kwargs
):
326 Starts a network chain for a source destination pair
328 :param vnf_src_name: Name of the source VNF
329 :type vnf_src_name: ``str``
330 :param vnf_dst_name: Name of the source VNF interface
331 :type vnf_dst_name: ``str``
332 :param \**kwargs: See below
335 * *vnf_src_interface* (``str``): Name of source interface.
336 * *vnf_dst_interface* (``str``): Name of destination interface.
337 * *bidirectional* (``bool``): If set the chain will be torn down in both directions, else it will just\
338 be torn down from source to destination.
339 * *cookie* (``int``): Cookie value used by openflow. Used to identify the flows in the switches to be \
340 able to modify the correct flows.
343 if 'cookie' in kwargs
:
344 return self
.delete_flow_by_cookie(kwargs
.get('cookie'))
346 if kwargs
.get('bidirectional', False):
347 self
.delete_chain_by_intf(vnf_dst_name
, kwargs
.get('vnf_dst_interface'),
348 vnf_src_name
, kwargs
.get('vnf_src_interface'))
350 return self
.delete_chain_by_intf(vnf_src_name
, kwargs
.get('vnf_src_interface'),
351 vnf_dst_name
, kwargs
.get('vnf_dst_interface'))
352 except Exception as ex
:
353 logging
.exception("RPC error.")
356 def set_son_emu_chain_data(self
, vnf_name
, data
):
358 Set son-emu chain data for this node.
360 :param vnf_name: The name of the vnf where the data is stored.
361 :type vnf_name: ``str``
362 :param data: Raw data to store on the node.
365 self
.net
.getNodeByName(vnf_name
).cmd(
366 "echo \'%s\' > /tmp/son_emu_data.json" % json
.dumps(data
))
368 for intf
in data
['son_emu_data']['interfaces'].values():
371 self
.net
.getNodeByName(vnf_name
).cmd(
372 "echo \'%s\' > /tmp/son_emu_data" % "\n".join(ip_list
))
374 def get_son_emu_chain_data(self
, vnf_name
):
376 Get the current son-emu chain data set for this node.
378 :param vnf_name: The name of the vnf where the data is stored.
379 :type vnf_name: ``str``
380 :return: raw data stored on the node
383 return self
.net
.getNodeByName(vnf_name
).cmd(
384 "cat /tmp/son_emu_data.json")
386 def _get_connected_switch_data(self
, vnf_name
, vnf_interface
):
388 Get the switch an interface is connected to
389 :param vnf_name: Name of the VNF
390 :type vnf_name: ``str``
391 :param vnf_interface: Name of the VNF interface
392 :type vnf_interface: ``str``
393 :return: List containing the switch, and the inport number
394 :rtype: [``str``, ``int``]
397 src_sw_inport_nr
= None
398 for connected_sw
in self
.net
.DCNetwork_graph
.neighbors(vnf_name
):
399 link_dict
= self
.net
.DCNetwork_graph
[vnf_name
][connected_sw
]
400 for link
in link_dict
:
401 if (link_dict
[link
]['src_port_id'] == vnf_interface
or
403 'src_port_name'] == vnf_interface
):
404 # found the right link and connected switch
405 src_sw
= connected_sw
406 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
409 return src_sw
, src_sw_inport_nr
411 def _get_path(self
, src_vnf
, dst_vnf
, src_vnf_intf
, dst_vnf_intf
):
413 Own implementation of the get_path function from DCNetwork, because we just want the path and not set up
416 :param src_vnf: Name of the source VNF
417 :type src_vnf: ``str``
418 :param dst_vnf: Name of the destination VNF
419 :type dst_vnf: ``str``
420 :param src_vnf_intf: Name of the source VNF interface
421 :type src_vnf_intf: ``str``
422 :param dst_vnf_intf: Name of the destination VNF interface
423 :type dst_vnf_intf: ``str``
424 :return: path, src_sw, dst_sw
425 :rtype: ``list``, ``str``, ``str``
427 # modified version of the _chainAddFlow from
428 # emuvim.dcemulator.net._chainAddFlow
431 logging
.debug("Find shortest path from vnf %s to %s",
434 for connected_sw
in self
.net
.DCNetwork_graph
.neighbors(src_vnf
):
435 link_dict
= self
.net
.DCNetwork_graph
[src_vnf
][connected_sw
]
436 for link
in link_dict
:
437 if (link_dict
[link
]['src_port_id'] == src_vnf_intf
or
439 'src_port_name'] == src_vnf_intf
):
440 # found the right link and connected switch
441 src_sw
= connected_sw
444 for connected_sw
in self
.net
.DCNetwork_graph
.neighbors(dst_vnf
):
445 link_dict
= self
.net
.DCNetwork_graph
[connected_sw
][dst_vnf
]
446 for link
in link_dict
:
447 if link_dict
[link
]['dst_port_id'] == dst_vnf_intf
or \
449 'dst_port_name'] == dst_vnf_intf
:
450 # found the right link and connected
451 dst_sw
= connected_sw
453 logging
.debug("From switch %s to %s " % (src_sw
, dst_sw
))
457 # returns the first found shortest path
458 # if all shortest paths are wanted, use: all_shortest_paths
459 path
= nx
.shortest_path(self
.net
.DCNetwork_graph
, src_sw
, dst_sw
)
460 except BaseException
:
461 logging
.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
462 src_vnf
, dst_vnf
, src_sw
, dst_sw
))
463 logging
.debug("Graph nodes: %r" % self
.net
.DCNetwork_graph
.nodes())
464 logging
.debug("Graph edges: %r" % self
.net
.DCNetwork_graph
.edges())
465 for e
, v
in self
.net
.DCNetwork_graph
.edges():
466 logging
.debug("%r" % self
.net
.DCNetwork_graph
[e
][v
])
467 return "No path could be found between {0} and {1}".format(
470 logging
.info("Shortest path between {0} and {1}: {2}".format(
471 src_vnf
, dst_vnf
, path
))
472 return path
, src_sw
, dst_sw
474 def add_loadbalancer(self
, src_vnf_name
, src_vnf_interface
, lb_data
):
476 This function will set up a loadbalancer at the given interface.
478 :param src_vnf_name: Name of the source VNF
479 :type src_vnf_name: ``str``
480 :param src_vnf_interface: Name of the destination VNF
481 :type src_vnf_interface: ``str``
482 :param lb_data: A dictionary containing the destination data as well as custom path settings
483 :type lb_data: ``dict``
486 lbdata = {"dst_vnf_interfaces": {"dc2_man_web0": "port-man-2",
487 "dc3_man_web0": "port-man-4","dc4_man_web0": "port-man-6"}, "path": {"dc2_man_web0": {"port-man-2": [ "dc1.s1",\
493 dest_intfs_mapping
= lb_data
.get('dst_vnf_interfaces', dict())
494 # a custom path can be specified as a list of switches
495 custom_paths
= lb_data
.get('path', dict())
496 dest_vnf_outport_nrs
= list()
498 logging
.debug("Call to add_loadbalancer at %s intfs:%s" %
499 (src_vnf_name
, src_vnf_interface
))
501 if not self
.check_vnf_intf_pair(src_vnf_name
, src_vnf_interface
):
502 raise Exception(u
"Source VNF %s or intfs %s does not exist" % (
503 src_vnf_name
, src_vnf_interface
))
505 # find the switch belonging to the source interface, as well as the
507 for connected_sw
in net
.DCNetwork_graph
.neighbors(src_vnf_name
):
508 link_dict
= net
.DCNetwork_graph
[src_vnf_name
][connected_sw
]
509 for link
in link_dict
:
510 if link_dict
[link
]['src_port_name'] == src_vnf_interface
:
511 src_sw
= connected_sw
512 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
515 if src_sw
is None or src_sw_inport_nr
== 0:
516 raise Exception(u
"Source VNF or interface can not be found.")
518 # get all target interface outport numbers
519 for vnf_name
in dest_intfs_mapping
:
520 if vnf_name
not in net
.DCNetwork_graph
:
521 raise Exception(u
"Target VNF %s is not known." % vnf_name
)
522 for connected_sw
in net
.DCNetwork_graph
.neighbors(vnf_name
):
523 link_dict
= net
.DCNetwork_graph
[vnf_name
][connected_sw
]
524 for link
in link_dict
:
525 if link_dict
[link
]['src_port_name'] == dest_intfs_mapping
[vnf_name
]:
526 dest_vnf_outport_nrs
.append(
527 int(link_dict
[link
]['dst_port_nr']))
529 if (src_vnf_name
, src_vnf_interface
) not in self
.lb_flow_cookies
:
530 self
.lb_flow_cookies
[(src_vnf_name
, src_vnf_interface
)] = list()
534 for intf
in net
[src_vnf_name
].intfs
.values():
535 if intf
.name
== src_vnf_interface
:
539 # set up paths for each destination vnf individually
541 cookie
= self
.get_cookie()
542 main_cmd
= "add-flow -OOpenFlow13"
543 self
.lb_flow_cookies
[(src_vnf_name
, src_vnf_interface
)].append(cookie
)
547 data
["src_vnf"] = src_vnf_name
548 data
["src_intf"] = src_vnf_interface
549 data
["paths"] = list()
550 data
["cookie"] = cookie
552 # lb mac for src -> target connections
553 lb_mac
= "31:33:70:%02x:%02x:%02x" % (random
.randint(
554 0, 255), random
.randint(0, 255), random
.randint(0, 255))
556 # calculate lb ip as src_intf.ip +1
557 octets
= src_ip
.split('.')
558 octets
[3] = str(int(octets
[3]) + 1)
559 plus_one
= '.'.join(octets
)
561 # set up arp reply as well as add the route to the interface
562 self
.setup_arp_reply_at(src_sw
, src_sw_inport_nr
,
563 plus_one
, lb_mac
, cookie
=cookie
)
564 net
.getNodeByName(src_vnf_name
).setHostRoute(
565 plus_one
, src_vnf_interface
)
567 for dst_vnf_name
, dst_vnf_interface
in dest_intfs_mapping
.items():
568 path
, src_sw
, dst_sw
= self
._get
_path
(src_vnf_name
, dst_vnf_name
,
569 src_vnf_interface
, dst_vnf_interface
)
571 # use custom path if one is supplied
572 # json does not support hashing on tuples so we use nested dicts
573 if custom_paths
is not None and dst_vnf_name
in custom_paths
:
574 if dst_vnf_interface
in custom_paths
[dst_vnf_name
]:
575 path
= custom_paths
[dst_vnf_name
][dst_vnf_interface
]
576 logging
.debug("Taking custom path from %s to %s: %s" % (
577 src_vnf_name
, dst_vnf_name
, path
))
579 if not self
.check_vnf_intf_pair(dst_vnf_name
, dst_vnf_interface
):
580 self
.delete_loadbalancer(src_vnf_name
, src_vnf_interface
)
581 raise Exception(u
"VNF %s or intfs %s does not exist" %
582 (dst_vnf_name
, dst_vnf_interface
))
583 if isinstance(path
, dict):
584 self
.delete_loadbalancer(src_vnf_name
, src_vnf_interface
)
586 u
"Can not find a valid path. Are you specifying the right interfaces?.")
588 target_mac
= "fa:17:00:03:13:37"
589 target_ip
= "0.0.0.0"
590 for intf
in net
[dst_vnf_name
].intfs
.values():
591 if intf
.name
== dst_vnf_interface
:
592 target_mac
= str(intf
.mac
)
593 target_ip
= str(intf
.ip
)
594 dst_sw_outport_nr
= dest_vnf_outport_nrs
[index
]
596 switch_inport_nr
= src_sw_inport_nr
598 # self.setup_arp_reply_at(src_sw, src_sw_inport_nr, target_ip, target_mac, cookie=cookie)
599 net
.getNodeByName(dst_vnf_name
).setHostRoute(
600 src_ip
, dst_vnf_interface
)
602 # choose free vlan if path contains more than 1 switch
604 vlan
= net
.vlans
.pop()
606 vlan
= net
.vlans
.pop()
610 single_flow_data
= dict()
611 single_flow_data
["dst_vnf"] = dst_vnf_name
612 single_flow_data
["dst_intf"] = dst_vnf_interface
613 single_flow_data
["path"] = path
614 single_flow_data
["vlan"] = vlan
615 single_flow_data
["cookie"] = cookie
617 data
["paths"].append(single_flow_data
)
620 for i
in range(0, len(path
)):
621 if i
< len(path
) - 1:
622 next_hop
= path
[i
+ 1]
624 # last switch reached
625 next_hop
= dst_vnf_name
626 next_node
= net
.getNodeByName(next_hop
)
627 if next_hop
== dst_vnf_name
:
628 switch_outport_nr
= dst_sw_outport_nr
629 logging
.info("end node reached: {0}".format(dst_vnf_name
))
630 elif not isinstance(next_node
, OVSSwitch
):
632 "Next node: {0} is not a switch".format(next_hop
))
633 return "Next node: {0} is not a switch".format(next_hop
)
635 # take first link between switches by default
637 switch_outport_nr
= net
.DCNetwork_graph
[current_hop
][next_hop
][index_edge_out
]['src_port_nr']
639 cmd
= 'priority=1,in_port=%s,cookie=%s' % (
640 switch_inport_nr
, cookie
)
641 cmd_back
= 'priority=1,in_port=%s,cookie=%s' % (
642 switch_outport_nr
, cookie
)
643 # if a vlan is picked, the connection is routed through
646 if path
.index(current_hop
) == 0: # first node
648 cmd
= 'in_port=%s' % src_sw_inport_nr
649 cmd
+= ',cookie=%s' % cookie
650 cmd
+= ',table=%s' % cookie
652 cmd
+= ',reg1=%s' % index
655 cmd
+= ',push_vlan:0x8100'
656 masked_vlan
= vlan |
0x1000
657 cmd
+= ',set_field:%s->vlan_vid' % masked_vlan
658 cmd
+= ',set_field:%s->eth_dst' % target_mac
659 cmd
+= ',set_field:%s->ip_dst' % target_ip
660 cmd
+= ',output:%s' % switch_outport_nr
662 # last switch for reverse route
663 # remove any vlan tags
664 cmd_back
+= ',dl_vlan=%s' % vlan
665 cmd_back
+= ',actions=pop_vlan,output:%s' % switch_inport_nr
666 elif next_hop
== dst_vnf_name
: # last switch
667 # remove any vlan tags
668 cmd
+= ',dl_vlan=%s' % vlan
669 cmd
+= ',actions=pop_vlan,output:%s' % switch_outport_nr
670 # set up arp replys at the port so the dst nodes know
672 self
.setup_arp_reply_at(
673 current_hop
, switch_outport_nr
, src_ip
, src_mac
, cookie
=cookie
)
676 cmd_back
= 'in_port=%s' % switch_outport_nr
677 cmd_back
+= ',cookie=%s' % cookie
679 cmd_back
+= ',actions='
680 cmd_back
+= 'push_vlan:0x8100'
681 masked_vlan
= vlan |
0x1000
682 cmd_back
+= ',set_field:%s->vlan_vid' % masked_vlan
683 cmd_back
+= ',set_field:%s->eth_src' % lb_mac
684 cmd_back
+= ',set_field:%s->ip_src' % plus_one
685 cmd_back
+= ',output:%s' % switch_inport_nr
687 # if we have a circle in the path we need to specify this, as openflow will ignore the packet
688 # if we just output it on the same port as it came in
689 if switch_inport_nr
== switch_outport_nr
:
690 cmd
+= ',dl_vlan=%s,actions=IN_PORT' % (vlan
)
691 cmd_back
+= ',dl_vlan=%s,actions=IN_PORT' % (vlan
)
693 cmd
+= ',dl_vlan=%s,actions=output:%s' % (
694 vlan
, switch_outport_nr
)
695 cmd_back
+= ',dl_vlan=%s,actions=output:%s' % (
696 vlan
, switch_inport_nr
)
697 # output the packet at the correct outport
699 cmd
= 'in_port=%s' % src_sw_inport_nr
700 cmd
+= ',cookie=%s' % cookie
701 cmd
+= ',table=%s' % cookie
703 cmd
+= ',reg1=%s' % index
705 cmd
+= ',set_field:%s->eth_dst' % target_mac
706 cmd
+= ',set_field:%s->ip_dst' % target_ip
707 cmd
+= ',output:%s' % switch_outport_nr
710 cmd_back
= 'in_port=%s' % switch_outport_nr
711 cmd_back
+= ',cookie=%s' % cookie
713 cmd_back
+= ',actions='
714 cmd_back
+= ',set_field:%s->eth_src' % lb_mac
715 cmd_back
+= ',set_field:%s->ip_src' % plus_one
716 cmd_back
+= ',output:%s' % src_sw_inport_nr
718 self
.setup_arp_reply_at(
719 current_hop
, switch_outport_nr
, src_ip
, src_mac
, cookie
=cookie
)
721 # excecute the command on the target switch
724 cmd_back
= "\"%s\"" % cmd_back
725 net
[current_hop
].dpctl(main_cmd
, cmd
)
726 net
[current_hop
].dpctl(main_cmd
, cmd_back
)
728 # set next hop for the next iteration step
729 if isinstance(next_node
, OVSSwitch
):
730 switch_inport_nr
= net
.DCNetwork_graph
[current_hop
][next_hop
][0]['dst_port_nr']
731 current_hop
= next_hop
733 # advance to next destination
736 # set up the actual load balancing rule as a multipath on the very
738 cmd
= '"in_port=%s' % src_sw_inport_nr
739 cmd
+= ',cookie=%s' % (cookie
)
742 # push 0x01 into the first register
743 cmd
+= 'load:0x1->NXM_NX_REG0[]'
744 # load balance modulo n over all dest interfaces
745 # TODO: in newer openvswitch implementations this should be changed to symmetric_l3l4+udp
746 # to balance any kind of traffic
747 cmd
+= ',multipath(symmetric_l4,1024,modulo_n,%s,0,NXM_NX_REG1[0..12])' % len(
749 # reuse the cookie as table entry as it will be unique
750 cmd
+= ',resubmit(, %s)"' % cookie
752 # actually add the flow
753 logging
.debug("Switch: %s, CMD: %s" % (src_sw
, cmd
))
754 net
[src_sw
].dpctl(main_cmd
, cmd
)
756 # finally add all flow data to the internal data storage
757 self
.full_lb_data
[(src_vnf_name
, src_vnf_interface
)] = data
759 def add_floating_lb(self
, datacenter
, lb_data
):
761 This function will set up a loadbalancer at the given datacenter.
762 This function returns the floating ip assigned to the loadbalancer as multiple ones are possible.
764 :param datacenter: The datacenter entrypoint
765 :type datacenter: ``str``
766 :param lb_data: A dictionary containing the destination data as well as custom path settings
767 :type lb_data: ``dict``
770 lbdata = {"dst_vnf_interfaces": {"dc2_man_web0": "port-man-2",
771 "dc3_man_web0": "port-man-4","dc4_man_web0": "port-man-6"}, "path": {"dc2_man_web0": {"port-man-2": [ "dc1.s1",\
776 src_sw
= self
.floating_switch
.name
777 dest_intfs_mapping
= lb_data
.get('dst_vnf_interfaces', dict())
778 # a custom path can be specified as a list of switches
779 custom_paths
= lb_data
.get('path', dict())
780 dest_vnf_outport_nrs
= list()
782 if datacenter
not in self
.net
.dcs
:
783 raise Exception(u
"Source datacenter can not be found.")
785 # get all target interface outport numbers
786 for vnf_name
in dest_intfs_mapping
:
787 if vnf_name
not in net
.DCNetwork_graph
:
788 raise Exception(u
"Target VNF %s is not known." % vnf_name
)
789 for connected_sw
in net
.DCNetwork_graph
.neighbors(vnf_name
):
790 link_dict
= net
.DCNetwork_graph
[vnf_name
][connected_sw
]
791 for link
in link_dict
:
792 if link_dict
[link
]['src_port_name'] == dest_intfs_mapping
[vnf_name
]:
793 dest_vnf_outport_nrs
.append(
794 int(link_dict
[link
]['dst_port_nr']))
796 if len(dest_vnf_outport_nrs
) == 0:
798 "There are no paths specified for the loadbalancer")
799 src_ip
= self
.floating_intf
.IP()
800 src_mac
= self
.floating_intf
.MAC()
802 # set up paths for each destination vnf individually
804 cookie
= self
.get_cookie()
805 main_cmd
= "add-flow -OOpenFlow13"
806 floating_ip
= self
.floating_network
.get_new_ip_address(
807 "floating-ip").split("/")[0]
809 for dst_vnf_name
, dst_vnf_interface
in dest_intfs_mapping
.items():
811 # use custom path if one is supplied
812 # json does not support hashing on tuples so we use nested dicts
813 if custom_paths
is not None and dst_vnf_name
in custom_paths
:
814 if dst_vnf_interface
in custom_paths
[dst_vnf_name
]:
815 path
= custom_paths
[dst_vnf_name
][dst_vnf_interface
]
816 logging
.debug("Taking custom path to %s: %s" %
817 (dst_vnf_name
, path
))
819 if datacenter
not in self
.floating_links
:
820 self
.floating_links
[datacenter
] = \
821 net
.addLink(self
.floating_switch
, datacenter
)
823 self
._get
_path
(self
.floating_root
.name
, dst_vnf_name
,
824 self
.floating_intf
.name
, dst_vnf_interface
)[0]
826 if isinstance(path
, dict):
827 self
.delete_flow_by_cookie(cookie
)
829 u
"Can not find a valid path. Are you specifying the right interfaces?.")
831 intf
= net
[dst_vnf_name
].nameToIntf
[dst_vnf_interface
]
832 target_mac
= str(intf
.MAC())
833 target_ip
= str(intf
.IP())
834 dst_sw_outport_nr
= dest_vnf_outport_nrs
[index
]
836 switch_inport_nr
= src_sw_inport_nr
837 vlan
= net
.vlans
.pop()
839 # iterate all switches on the path
840 for i
in range(0, len(path
)):
841 if i
< len(path
) - 1:
842 next_hop
= path
[i
+ 1]
844 # last switch reached
845 next_hop
= dst_vnf_name
846 next_node
= net
.getNodeByName(next_hop
)
849 if next_hop
== dst_vnf_name
:
850 switch_outport_nr
= dst_sw_outport_nr
851 logging
.info("end node reached: {0}".format(dst_vnf_name
))
852 elif not isinstance(next_node
, OVSSwitch
):
854 "Next node: {0} is not a switch".format(next_hop
))
855 return "Next node: {0} is not a switch".format(next_hop
)
857 # take first link between switches by default
859 switch_outport_nr
= net
.DCNetwork_graph
[current_hop
][next_hop
][index_edge_out
]['src_port_nr']
861 # default filters, just overwritten on the first node and last
863 cmd
= 'priority=1,in_port=%s,cookie=%s' % (
864 switch_inport_nr
, cookie
)
865 cmd_back
= 'priority=1,in_port=%s,cookie=%s' % (
866 switch_outport_nr
, cookie
)
867 if i
== 0: # first node
868 cmd
= 'in_port=%s' % src_sw_inport_nr
869 cmd
+= ',cookie=%s' % cookie
870 cmd
+= ',table=%s' % cookie
872 cmd
+= ',ip_dst=%s' % floating_ip
873 cmd
+= ',reg1=%s' % index
876 cmd
+= ',push_vlan:0x8100'
877 masked_vlan
= vlan |
0x1000
878 cmd
+= ',set_field:%s->vlan_vid' % masked_vlan
879 cmd
+= ',set_field:%s->eth_dst' % target_mac
880 cmd
+= ',set_field:%s->ip_dst' % target_ip
881 cmd
+= ',output:%s' % switch_outport_nr
883 # last switch for reverse route
884 # remove any vlan tags
885 cmd_back
+= ',dl_vlan=%s' % vlan
886 cmd_back
+= ',actions=pop_vlan,output:%s' % switch_inport_nr
887 self
.setup_arp_reply_at(
888 current_hop
, src_sw_inport_nr
, floating_ip
, target_mac
, cookie
=cookie
)
889 elif next_hop
== dst_vnf_name
: # last switch
890 # remove any vlan tags
891 cmd
+= ',dl_vlan=%s' % vlan
892 cmd
+= ',actions=pop_vlan,output:%s' % switch_outport_nr
893 # set up arp replys at the port so the dst nodes know the
895 self
.setup_arp_reply_at(
896 current_hop
, switch_outport_nr
, src_ip
, src_mac
, cookie
=cookie
)
899 cmd_back
= 'in_port=%s' % switch_outport_nr
900 cmd_back
+= ',cookie=%s' % cookie
902 cmd_back
+= ',actions='
903 cmd_back
+= 'push_vlan:0x8100'
904 masked_vlan
= vlan |
0x1000
905 cmd_back
+= ',set_field:%s->vlan_vid' % masked_vlan
906 cmd_back
+= ',set_field:%s->eth_src' % src_mac
907 cmd_back
+= ',set_field:%s->ip_src' % floating_ip
908 cmd_back
+= ',output:%s' % switch_inport_nr
909 net
.getNodeByName(dst_vnf_name
).setHostRoute(
910 src_ip
, dst_vnf_interface
)
912 # if we have a circle in the path we need to specify this, as openflow will ignore the packet
913 # if we just output it on the same port as it came in
914 if switch_inport_nr
== switch_outport_nr
:
915 cmd
+= ',dl_vlan=%s,actions=IN_PORT' % (vlan
)
916 cmd_back
+= ',dl_vlan=%s,actions=IN_PORT' % (vlan
)
918 cmd
+= ',dl_vlan=%s,actions=output:%s' % (
919 vlan
, switch_outport_nr
)
920 cmd_back
+= ',dl_vlan=%s,actions=output:%s' % (
921 vlan
, switch_inport_nr
)
923 # excecute the command on the target switch
926 cmd_back
= "\"%s\"" % cmd_back
927 net
[current_hop
].dpctl(main_cmd
, cmd
)
928 net
[current_hop
].dpctl(main_cmd
, cmd_back
)
930 # set next hop for the next iteration step
931 if isinstance(next_node
, OVSSwitch
):
932 switch_inport_nr
= net
.DCNetwork_graph
[current_hop
][next_hop
][0]['dst_port_nr']
933 current_hop
= next_hop
935 # advance to next destination
938 # set up the actual load balancing rule as a multipath on the very
940 cmd
= '"in_port=%s' % src_sw_inport_nr
941 cmd
+= ',cookie=%s' % (cookie
)
944 # push 0x01 into the first register
945 cmd
+= 'load:0x1->NXM_NX_REG0[]'
946 # load balance modulo n over all dest interfaces
947 # TODO: in newer openvswitch implementations this should be changed to symmetric_l3l4+udp
948 # to balance any kind of traffic
949 cmd
+= ',multipath(symmetric_l4,1024,modulo_n,%s,0,NXM_NX_REG1[0..12])' % len(
951 # reuse the cookie as table entry as it will be unique
952 cmd
+= ',resubmit(, %s)"' % cookie
954 # actually add the flow
955 logging
.debug("Switch: %s, CMD: %s" % (src_sw
, cmd
))
956 net
[src_sw
].dpctl(main_cmd
, cmd
)
958 self
.floating_cookies
[cookie
] = floating_ip
960 return cookie
, floating_ip
962 def setup_arp_reply_at(self
, switch
, port_nr
,
963 target_ip
, target_mac
, cookie
=None):
965 Sets up a custom ARP reply at a switch.
966 An ARP request coming in on the `port_nr` for `target_ip` will be answered with target IP/MAC.
968 :param switch: The switch belonging to the interface
969 :type switch: ``str``
970 :param port_nr: The port number at the switch that is connected to the interface
971 :type port_nr: ``int``
972 :param target_ip: The IP for which to set up the ARP reply
973 :type target_ip: ``str``
974 :param target_mac: The MAC address of the target interface
975 :type target_mac: ``str``
976 :param cookie: cookie to identify the ARP request, if None a new one will be picked
977 :type cookie: ``int`` or ``None``
982 cookie
= self
.get_cookie()
983 main_cmd
= "add-flow -OOpenFlow13"
985 # first set up ARP requests for the source node, so it will always
987 cmd
= '"in_port=%s' % port_nr
988 cmd
+= ',cookie=%s' % cookie
990 # only answer for target ip arp requests
991 cmd
+= ',arp_tpa=%s' % target_ip
993 # set message type to ARP reply
994 cmd
+= 'load:0x2->NXM_OF_ARP_OP[]'
995 # set src ip as dst ip
996 cmd
+= ',move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[]'
998 cmd
+= ',set_field:%s->eth_src' % target_mac
1000 cmd
+= ',move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[], move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[]'
1001 # set target mac as hex
1002 cmd
+= ',load:0x%s->NXM_NX_ARP_SHA[]' % "".join(target_mac
.split(':'))
1003 # set target ip as hex
1004 octets
= target_ip
.split('.')
1005 dst_ip_hex
= '{:02X}{:02X}{:02X}{:02X}'.format(*map(int, octets
))
1006 cmd
+= ',load:0x%s->NXM_OF_ARP_SPA[]' % dst_ip_hex
1007 # output to incoming port remember the closing "
1009 self
.net
[switch
].dpctl(main_cmd
, cmd
)
1011 "Set up ARP reply at %s port %s." % (switch
, port_nr
))
1013 def delete_flow_by_cookie(self
, cookie
):
1015 Removes a flow identified by the cookie
1017 :param cookie: The cookie for the specified flow
1018 :type cookie: ``int``
1019 :return: True if successful, else false
1024 logging
.debug("Deleting flow by cookie %d" % (cookie
))
1026 # we have to call delete-group for each switch
1027 for node
in self
.net
.switches
:
1029 flow
["dpid"] = int(node
.dpid
, 16)
1030 flow
["cookie"] = cookie
1031 flow
['cookie_mask'] = int('0xffffffffffffffff', 16)
1035 logging
.debug("Deleting flowentry with cookie %d" % (
1037 if self
.net
.controller
== RemoteController
:
1038 self
.net
.ryu_REST('stats/flowentry/delete', data
=flow
)
1040 self
.cookies
.remove(cookie
)
1043 def delete_chain_by_intf(
1044 self
, src_vnf_name
, src_vnf_intf
, dst_vnf_name
, dst_vnf_intf
):
1046 Removes a flow identified by the vnf_name/vnf_intf pairs
1048 :param src_vnf_name: The vnf name for the specified flow
1049 :type src_vnf_name: ``str``
1050 :param src_vnf_intf: The interface name for the specified flow
1051 :type src_vnf_intf: ``str``
1052 :param dst_vnf_name: The vnf name for the specified flow
1053 :type dst_vnf_name: ``str``
1054 :param dst_vnf_intf: The interface name for the specified flow
1055 :type dst_vnf_intf: ``str``
1056 :return: True if successful, else false
1059 logging
.debug("Deleting flow for vnf/intf pair %s %s" %
1060 (src_vnf_name
, src_vnf_intf
))
1061 if not self
.check_vnf_intf_pair(src_vnf_name
, src_vnf_intf
):
1063 if not self
.check_vnf_intf_pair(dst_vnf_name
, dst_vnf_intf
):
1065 target_flow
= (src_vnf_name
, src_vnf_intf
, dst_vnf_name
, dst_vnf_intf
)
1066 if target_flow
not in self
.chain_flow_cookies
:
1069 success
= self
.delete_flow_by_cookie(
1070 self
.chain_flow_cookies
[target_flow
])
1073 del self
.chain_flow_cookies
[target_flow
]
1074 del self
.full_chain_data
[target_flow
]
1078 def delete_loadbalancer(self
, vnf_src_name
, vnf_src_interface
):
1080 Removes a loadbalancer that is configured for the node and interface
1082 :param src_vnf_name: Name of the source VNF
1083 :param src_vnf_interface: Name of the destination VNF
1086 # we have to call delete-group for each switch
1087 delete_group
= list()
1088 group_id
= self
.get_flow_group(vnf_src_name
, vnf_src_interface
)
1089 for node
in self
.net
.switches
:
1090 for cookie
in self
.lb_flow_cookies
[(
1091 vnf_src_name
, vnf_src_interface
)]:
1093 flow
["dpid"] = int(node
.dpid
, 16)
1094 flow
["cookie"] = cookie
1095 flow
['cookie_mask'] = int('0xffffffffffffffff', 16)
1099 group_del
["dpid"] = int(node
.dpid
, 16)
1100 group_del
["group_id"] = group_id
1101 delete_group
.append(group_del
)
1104 logging
.debug("Deleting flowentry with cookie %d belonging to lb at %s:%s" % (
1105 flow
["cookie"], vnf_src_name
, vnf_src_interface
))
1106 if self
.net
.controller
== RemoteController
:
1107 self
.net
.ryu_REST('stats/flowentry/delete', data
=flow
)
1109 logging
.debug("Deleting group with id %s" % group_id
)
1110 for switch_del_group
in delete_group
:
1111 if self
.net
.controller
== RemoteController
:
1112 self
.net
.ryu_REST("stats/groupentry/delete",
1113 data
=switch_del_group
)
1115 # unmap groupid from the interface
1116 target_pair
= (vnf_src_name
, vnf_src_interface
)
1117 if target_pair
in self
.flow_groups
:
1118 del self
.flow_groups
[target_pair
]
1119 if target_pair
in self
.full_lb_data
:
1120 del self
.full_lb_data
[target_pair
]
1122 def delete_floating_lb(self
, cookie
):
1124 Delete a floating loadbalancer.
1125 Floating loadbalancers are different from normal ones as there are multiple ones on the same interface.
1126 :param cookie: The cookie of the loadbalancer
1127 :type cookie: ``int``
1129 cookie
= int(cookie
)
1130 if cookie
not in self
.floating_cookies
:
1132 "Can not delete floating loadbalancer as the flowcookie is not known")
1134 self
.delete_flow_by_cookie(cookie
)
1135 floating_ip
= self
.floating_cookies
[cookie
]
1136 self
.floating_network
.withdraw_ip_address(floating_ip
)
1138 def set_arp_entry(self
, vnf_name
, vnf_interface
, ip
, mac
):
1140 Sets an arp entry on the specified VNF. This is done on the node directly and not by open vswitch!
1141 :param vnf_name: Name of the VNF
1142 :type vnf_name: ``str``
1143 :param vnf_interface: Name of the interface
1144 :type vnf_interface: ``str``
1145 :param ip: IP to reply to
1147 :param mac: Answer with this MAC
1150 node
= self
.net
.getNodeByName(vnf_name
)
1151 node
.cmd("arp -i %s -s %s %s" % (vnf_interface
, ip
, mac
))