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).
26 from mininet
.node
import Docker
27 from mininet
.link
import Link
28 from emuvim
.dcemulator
.resourcemodel
import NotEnoughResourcesAvailable
32 LOG
= logging
.getLogger("dcemulator.node")
33 LOG
.setLevel(logging
.DEBUG
)
36 DCDPID_BASE
= 1000 # start of switch dpid's used for data center switches
37 EXTSAPDPID_BASE
= 2000 # start of switch dpid's used for external SAP switches
40 class EmulatorCompute(Docker
):
42 Emulator specific compute node class.
43 Inherits from Containernet's Docker host class.
44 Represents a single container connected to a (logical)
46 We can add emulator specific helper functions to it.
50 self
, name
, dimage
, **kwargs
):
51 self
.datacenter
= kwargs
.get("datacenter") # pointer to current DC
52 self
.flavor_name
= kwargs
.get("flavor_name")
53 LOG
.debug("Starting compute instance %r in data center %r" %
54 (name
, str(self
.datacenter
)))
55 # call original Docker.__init__
56 Docker
.__init
__(self
, name
, dimage
, **kwargs
)
58 def getNetworkStatus(self
):
60 Helper method to receive information about the virtual networks
61 this compute instance is connected to.
63 # get all links and find dc switch interface
64 networkStatusList
= []
65 for i
in self
.intfList():
67 vnf_interface
= str(i
)
68 dc_port_name
= self
.datacenter
.net
.find_connected_dc_interface(
69 vnf_name
, vnf_interface
)
70 # format list of tuples (name, Ip, MAC, isUp, status, dc_portname)
71 intf_dict
= {'intf_name': str(i
), 'ip': "{0}/{1}".format(i
.IP(), i
.prefixLen
), 'netmask': i
.prefixLen
,
72 'mac': i
.MAC(), 'up': i
.isUp(), 'status': i
.status(), 'dc_portname': dc_port_name
}
73 networkStatusList
.append(intf_dict
)
75 return networkStatusList
79 Helper method to receive information about this compute instance.
82 status
["name"] = self
.name
83 status
["network"] = self
.getNetworkStatus()
84 status
["docker_network"] = self
.dcinfo
['NetworkSettings']['IPAddress']
85 status
["image"] = self
.dimage
86 status
["flavor_name"] = self
.flavor_name
87 status
["cpu_quota"] = self
.resources
.get('cpu_quota')
88 status
["cpu_period"] = self
.resources
.get('cpu_period')
89 status
["cpu_shares"] = self
.resources
.get('cpu_shares')
90 status
["cpuset"] = self
.resources
.get('cpuset_cpus')
91 status
["mem_limit"] = self
.resources
.get('mem_limit')
92 status
["memswap_limit"] = self
.resources
.get('memswap_limit')
93 status
["state"] = self
.dcli
.inspect_container(self
.dc
)["State"]
94 status
["id"] = self
.dcli
.inspect_container(self
.dc
)["Id"]
95 status
["short_id"] = self
.dcli
.inspect_container(self
.dc
)["Id"][:12]
96 status
["hostname"] = self
.dcli
.inspect_container(self
.dc
)[
98 status
["datacenter"] = (None if self
.datacenter
is None
99 else self
.datacenter
.label
)
104 class EmulatorExtSAP(object):
106 Emulator specific class that defines an external service access point (SAP) for the service.
107 Inherits from Containernet's OVSBridge class.
108 Represents a single OVS switch connected to a (logical)
110 We can add emulator specific helper functions to it.
113 def __init__(self
, sap_name
, sap_net
, datacenter
, **kwargs
):
115 self
.datacenter
= datacenter
# pointer to current DC
116 self
.net
= self
.datacenter
.net
119 LOG
.debug("Starting ext SAP instance %r in data center %r" %
120 (sap_name
, str(self
.datacenter
)))
122 # create SAP as separate OVS switch with an assigned ip address
123 self
.ip
= str(sap_net
[1]) + '/' + str(sap_net
.prefixlen
)
124 self
.subnet
= sap_net
125 # allow connection to the external internet through the host
126 params
= dict(NAT
=True)
127 self
.switch
= self
.net
.addExtSAP(sap_name
, self
.ip
, dpid
=hex(
128 self
._get
_next
_extSAP
_dpid
())[2:], **params
)
131 def _get_next_extSAP_dpid(self
):
132 global EXTSAPDPID_BASE
134 return EXTSAPDPID_BASE
136 def getNetworkStatus(self
):
138 Helper method to receive information about the virtual networks
139 this compute instance is connected to.
141 # get all links and find dc switch interface
142 networkStatusList
= []
143 for i
in self
.switch
.intfList():
145 vnf_interface
= str(i
)
146 if vnf_interface
== 'lo':
148 dc_port_name
= self
.datacenter
.net
.find_connected_dc_interface(
149 vnf_name
, vnf_interface
)
150 # format list of tuples (name, Ip, MAC, isUp, status, dc_portname)
151 intf_dict
= {'intf_name': str(i
), 'ip': self
.ip
, 'netmask': i
.prefixLen
, 'mac': i
.MAC(
152 ), 'up': i
.isUp(), 'status': i
.status(), 'dc_portname': dc_port_name
}
153 networkStatusList
.append(intf_dict
)
155 return networkStatusList
159 "name": self
.switch
.name
,
160 "datacenter": self
.datacenter
.name
,
161 "network": self
.getNetworkStatus()
165 class Datacenter(object):
167 Represents a logical data center to which compute resources
168 (Docker containers) can be added at runtime.
170 Will also implement resource bookkeeping in later versions.
175 def __init__(self
, label
, metadata
={}, resource_log_path
=None):
176 self
.net
= None # DCNetwork to which we belong
177 # each node (DC) has a short internal name used by Mininet
178 # this is caused by Mininets naming limitations for swtiches etc.
179 self
.name
= "dc%d" % Datacenter
.DC_COUNTER
180 Datacenter
.DC_COUNTER
+= 1
181 # use this for user defined names that can be longer than self.name
183 # dict to store arbitrary metadata (e.g. latitude and longitude)
184 self
.metadata
= metadata
185 # path to which resource information should be logged (e.g. for
186 # experiments). None = no logging
187 self
.resource_log_path
= resource_log_path
188 # first prototype assumes one "bigswitch" per DC
190 # keep track of running containers
192 # keep track of attached external access points
194 # pointer to assigned resource model
195 self
._resource
_model
= None
200 def _get_next_dc_dpid(self
):
207 Each data center is represented by a single switch to which
208 compute resources can be connected at run time.
210 TODO: This will be changed in the future to support multiple networks
213 self
.switch
= self
.net
.addSwitch(
214 "%s.s1" % self
.name
, dpid
=hex(self
._get
_next
_dc
_dpid
())[2:])
215 LOG
.debug("created data center switch: %s" % str(self
.switch
))
220 def startCompute(self
, name
, image
=None, command
=None, network
=None,
221 flavor_name
="tiny", properties
=dict(), **params
):
223 Create a new container as compute resource and connect it to this
225 :param name: name (string)
226 :param image: image name (string)
227 :param command: command (string)
228 :param network: networks list({"ip": "10.0.0.254/8"}, {"ip": "11.0.0.254/24"})
229 :param flavor_name: name of the flavor for this compute container
230 :param properties: dictionary of properties (key-value) that will be passed as environment variables
233 assert name
is not None
235 if name
in [c
.name
for c
in self
.net
.getAllContainers()]:
236 raise Exception("Container with name %s already exists." % name
)
237 # set default parameter
239 image
= "ubuntu:trusty"
241 network
= {} # {"ip": "10.0.0.254/8"}
242 if isinstance(network
, dict):
243 # if we have only one network, put it in a list
245 if isinstance(network
, list):
249 # apply hard-set resource limits=0
250 cpu_percentage
= params
.get('cpu_percent')
252 params
['cpu_period'] = self
.net
.cpu_period
253 params
['cpu_quota'] = self
.net
.cpu_period
* float(cpu_percentage
)
256 properties
['VNF_NAME'] = name
257 # create the container
258 d
= self
.net
.addDocker(
263 flavor_name
=flavor_name
,
268 # apply resource limits to container if a resource model is defined
269 if self
._resource
_model
is not None:
271 self
._resource
_model
.allocate(d
)
272 self
._resource
_model
.write_allocation_log(
273 d
, self
.resource_log_path
)
274 except NotEnoughResourcesAvailable
as ex
:
276 "Allocation of container %r was blocked by resource model." % name
)
278 # ensure that we remove the container
279 self
.net
.removeDocker(name
)
282 # connect all given networks
283 # if no --net option is given, network = [{}], so 1 empty dict in the list
284 # this results in 1 default interface with a default ip address
286 # clean up network configuration (e.g. RTNETLINK does not allow ':'
288 if nw
.get("id") is not None:
289 nw
["id"] = self
._clean
_ifname
(nw
["id"])
290 # TODO we cannot use TCLink here (see:
291 # https://github.com/mpeuster/containernet/issues/3)
292 self
.net
.addLink(d
, self
.switch
, params1
=nw
,
293 cls
=Link
, intfName1
=nw
.get('id'))
295 self
.containers
[name
] = d
296 return d
# we might use UUIDs for naming later on
298 def stopCompute(self
, name
):
300 Stop and remove a container from this data center.
302 assert name
is not None
303 if name
not in self
.containers
:
304 raise Exception("Container with name %s not found." % name
)
305 LOG
.debug("Stopping compute instance %r in data center %r" %
308 # stop the monitored metrics
309 if self
.net
.monitor_agent
is not None:
310 self
.net
.monitor_agent
.stop_metric(name
)
312 # call resource model and free resources
313 if self
._resource
_model
is not None:
314 self
._resource
_model
.free(self
.containers
[name
])
315 self
._resource
_model
.write_free_log(
316 self
.containers
[name
], self
.resource_log_path
)
320 link
=None, node1
=self
.containers
[name
], node2
=self
.switch
)
323 self
.net
.removeDocker("%s" % (name
))
324 del self
.containers
[name
]
328 def attachExternalSAP(self
, sap_name
, sap_net
, **params
):
329 extSAP
= EmulatorExtSAP(sap_name
, sap_net
, self
, **params
)
330 # link SAP to the DC switch
331 self
.net
.addLink(extSAP
.switch
, self
.switch
, cls
=Link
)
332 self
.extSAPs
[sap_name
] = extSAP
334 def removeExternalSAP(self
, sap_name
):
335 sap_switch
= self
.extSAPs
[sap_name
].switch
336 # sap_switch = self.net.getNodeByName(sap_name)
337 # remove link of SAP to the DC switch
338 self
.net
.removeLink(link
=None, node1
=sap_switch
, node2
=self
.switch
)
339 self
.net
.removeExtSAP(sap_name
)
340 del self
.extSAPs
[sap_name
]
342 def listCompute(self
):
344 Return a list of all running containers assigned to this
347 return list(self
.containers
.itervalues())
349 def listExtSAPs(self
):
351 Return a list of all external SAPs assigned to this
354 return list(self
.extSAPs
.itervalues())
358 Return a dict with status information about this DC.
360 container_list
= [name
for name
in self
.containers
]
361 ext_saplist
= [sap_name
for sap_name
in self
.extSAPs
]
364 "internalname": self
.name
,
365 "switch": self
.switch
.name
,
366 "n_running_containers": len(self
.containers
),
367 "metadata": self
.metadata
,
368 "vnf_list": container_list
,
369 "ext SAP list": ext_saplist
372 def assignResourceModel(self
, rm
):
374 Assign a resource model to this DC.
375 :param rm: a BaseResourceModel object
378 if self
._resource
_model
is not None:
380 "There is already an resource model assigned to this DC.")
381 self
._resource
_model
= rm
382 self
.net
.rm_registrar
.register(self
, rm
)
383 LOG
.info("Assigned RM: %r to DC: %r" % (rm
, self
))
386 def _clean_ifname(name
):
388 Cleans up given string to be a
389 RTNETLINK compatible interface name.
395 name
= name
.replace(":", "-")
396 name
= name
.replace(" ", "-")
397 name
= name
.replace(".", "-")
398 name
= name
.replace("_", "-")