2 Copyright (c) 2015 SONATA-NFV and Paderborn University
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
9 http://www.apache.org/licenses/LICENSE-2.0
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
17 Neither the name of the SONATA-NFV, Paderborn University
18 nor the names of its contributors may be used to endorse or promote
19 products derived from this software without specific prior written
22 This work has been performed in the framework of the SONATA project,
23 funded by the European Commission under Grant number 671517 through
24 the Horizon 2020 and 5G-PPP programmes. The authors would like to
25 acknowledge the contributions of their colleagues of the SONATA
26 partner consortium (www.sonata-nfv.eu).
28 from mininet
.node
import Docker
, OVSBridge
29 from mininet
.link
import Link
30 from emuvim
.dcemulator
.resourcemodel
import NotEnoughResourcesAvailable
34 LOG
= logging
.getLogger("dcemulator.node")
35 LOG
.setLevel(logging
.DEBUG
)
38 DCDPID_BASE
= 1000 # start of switch dpid's used for data center switches
39 EXTSAPDPID_BASE
= 2000 # start of switch dpid's used for external SAP switches
41 class EmulatorCompute(Docker
):
43 Emulator specific compute node class.
44 Inherits from Containernet's Docker host class.
45 Represents a single container connected to a (logical)
47 We can add emulator specific helper functions to it.
51 self
, name
, dimage
, **kwargs
):
52 self
.datacenter
= kwargs
.get("datacenter") # pointer to current DC
53 self
.flavor_name
= kwargs
.get("flavor_name")
54 LOG
.debug("Starting compute instance %r in data center %r" % (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(vnf_name
, vnf_interface
)
69 # format list of tuples (name, Ip, MAC, isUp, status, dc_portname)
70 intf_dict
= {'intf_name': str(i
), 'ip': "{0}/{1}".format(i
.IP(), i
.prefixLen
), 'netmask': i
.prefixLen
, 'mac': i
.MAC(), 'up': i
.isUp(), 'status': i
.status(), 'dc_portname': dc_port_name
}
71 networkStatusList
.append(intf_dict
)
73 return networkStatusList
77 Helper method to receive information about this compute instance.
80 status
["name"] = self
.name
81 status
["network"] = self
.getNetworkStatus()
82 status
["docker_network"] = self
.dcinfo
['NetworkSettings']['IPAddress']
83 status
["image"] = self
.dimage
84 status
["flavor_name"] = self
.flavor_name
85 status
["cpu_quota"] = self
.resources
.get('cpu_quota')
86 status
["cpu_period"] = self
.resources
.get('cpu_period')
87 status
["cpu_shares"] = self
.resources
.get('cpu_shares')
88 status
["cpuset"] = self
.resources
.get('cpuset_cpus')
89 status
["mem_limit"] = self
.resources
.get('mem_limit')
90 status
["memswap_limit"] = self
.resources
.get('memswap_limit')
91 status
["state"] = self
.dcli
.inspect_container(self
.dc
)["State"]
92 status
["id"] = self
.dcli
.inspect_container(self
.dc
)["Id"]
93 status
["short_id"] = self
.dcli
.inspect_container(self
.dc
)["Id"][:12]
94 status
["hostname"] = self
.dcli
.inspect_container(self
.dc
)["Config"]['Hostname']
95 status
["datacenter"] = (None if self
.datacenter
is None
96 else self
.datacenter
.label
)
101 class EmulatorExtSAP(object):
103 Emulator specific class that defines an external service access point (SAP) for the service.
104 Inherits from Containernet's OVSBridge class.
105 Represents a single OVS switch connected to a (logical)
107 We can add emulator specific helper functions to it.
110 def __init__(self
, sap_name
, sap_net
, datacenter
, **kwargs
):
112 self
.datacenter
= datacenter
# pointer to current DC
113 self
.net
= self
.datacenter
.net
116 LOG
.debug("Starting ext SAP instance %r in data center %r" % (sap_name
, str(self
.datacenter
)))
118 # create SAP as separate OVS switch with an assigned ip address
119 self
.ip
= str(sap_net
[1]) + '/' + str(sap_net
.prefixlen
)
120 self
.subnet
= sap_net
121 # allow connection to the external internet through the host
122 params
= dict(NAT
=True)
123 self
.switch
= self
.net
.addExtSAP(sap_name
, self
.ip
, dpid
=hex(self
._get
_next
_extSAP
_dpid
())[2:], **params
)
126 def _get_next_extSAP_dpid(self
):
127 global EXTSAPDPID_BASE
129 return EXTSAPDPID_BASE
131 def getNetworkStatus(self
):
133 Helper method to receive information about the virtual networks
134 this compute instance is connected to.
136 # get all links and find dc switch interface
137 networkStatusList
= []
138 for i
in self
.switch
.intfList():
140 vnf_interface
= str(i
)
141 if vnf_interface
== 'lo':
143 dc_port_name
= self
.datacenter
.net
.find_connected_dc_interface(vnf_name
, vnf_interface
)
144 # format list of tuples (name, Ip, MAC, isUp, status, dc_portname)
145 intf_dict
= {'intf_name': str(i
), 'ip': self
.ip
, 'netmask': i
.prefixLen
, 'mac': i
.MAC(), 'up': i
.isUp(), 'status': i
.status(), 'dc_portname': dc_port_name
}
146 networkStatusList
.append(intf_dict
)
148 return networkStatusList
152 "name": self
.switch
.name
,
153 "datacenter": self
.datacenter
.name
,
154 "network": self
.getNetworkStatus()
157 class Datacenter(object):
159 Represents a logical data center to which compute resources
160 (Docker containers) can be added at runtime.
162 Will also implement resource bookkeeping in later versions.
167 def __init__(self
, label
, metadata
={}, resource_log_path
=None):
168 self
.net
= None # DCNetwork to which we belong
169 # each node (DC) has a short internal name used by Mininet
170 # this is caused by Mininets naming limitations for swtiches etc.
171 self
.name
= "dc%d" % Datacenter
.DC_COUNTER
172 Datacenter
.DC_COUNTER
+= 1
173 # use this for user defined names that can be longer than self.name
175 # dict to store arbitrary metadata (e.g. latitude and longitude)
176 self
.metadata
= metadata
177 # path to which resource information should be logged (e.g. for experiments). None = no logging
178 self
.resource_log_path
= resource_log_path
179 # first prototype assumes one "bigswitch" per DC
181 # keep track of running containers
183 # keep track of attached external access points
185 # pointer to assigned resource model
186 self
._resource
_model
= None
191 def _get_next_dc_dpid(self
):
198 Each data center is represented by a single switch to which
199 compute resources can be connected at run time.
201 TODO: This will be changed in the future to support multiple networks
204 self
.switch
= self
.net
.addSwitch(
205 "%s.s1" % self
.name
, dpid
=hex(self
._get
_next
_dc
_dpid
())[2:])
206 LOG
.debug("created data center switch: %s" % str(self
.switch
))
211 def startCompute(self
, name
, image
=None, command
=None, network
=None, flavor_name
="tiny", properties
=dict(), **params
):
213 Create a new container as compute resource and connect it to this
215 :param name: name (string)
216 :param image: image name (string)
217 :param command: command (string)
218 :param network: networks list({"ip": "10.0.0.254/8"}, {"ip": "11.0.0.254/24"})
219 :param flavor_name: name of the flavor for this compute container
220 :param properties: dictionary of properties (key-value) that will be passed as environment variables
223 assert name
is not None
225 if name
in [c
.name
for c
in self
.net
.getAllContainers()]:
226 raise Exception("Container with name %s already exists." % name
)
227 # set default parameter
229 image
= "ubuntu:trusty"
231 network
= {} # {"ip": "10.0.0.254/8"}
232 if isinstance(network
, dict):
233 network
= [network
] # if we have only one network, put it in a list
234 if isinstance(network
, list):
238 # apply hard-set resource limits=0
239 cpu_percentage
= params
.get('cpu_percent')
241 params
['cpu_period'] = self
.net
.cpu_period
242 params
['cpu_quota'] = self
.net
.cpu_period
* float(cpu_percentage
)
245 properties
['VNF_NAME'] = name
246 # create the container
247 d
= self
.net
.addDocker(
252 flavor_name
=flavor_name
,
259 # apply resource limits to container if a resource model is defined
260 if self
._resource
_model
is not None:
262 self
._resource
_model
.allocate(d
)
263 self
._resource
_model
.write_allocation_log(d
, self
.resource_log_path
)
264 except NotEnoughResourcesAvailable
as ex
:
265 LOG
.warning("Allocation of container %r was blocked by resource model." % name
)
267 # ensure that we remove the container
268 self
.net
.removeDocker(name
)
271 # connect all given networks
272 # if no --net option is given, network = [{}], so 1 empty dict in the list
273 # this results in 1 default interface with a default ip address
275 # clean up network configuration (e.g. RTNETLINK does not allow ':' in intf names
276 if nw
.get("id") is not None:
277 nw
["id"] = self
._clean
_ifname
(nw
["id"])
278 # TODO we cannot use TCLink here (see: https://github.com/mpeuster/containernet/issues/3)
279 self
.net
.addLink(d
, self
.switch
, params1
=nw
, cls
=Link
, intfName1
=nw
.get('id'))
281 self
.containers
[name
] = d
283 return d
# we might use UUIDs for naming later on
285 def stopCompute(self
, name
):
287 Stop and remove a container from this data center.
289 assert name
is not None
290 if name
not in self
.containers
:
291 raise Exception("Container with name %s not found." % name
)
292 LOG
.debug("Stopping compute instance %r in data center %r" % (name
, str(self
)))
294 # stop the monitored metrics
295 if self
.net
.monitor_agent
is not None:
296 self
.net
.monitor_agent
.stop_metric(name
)
298 # call resource model and free resources
299 if self
._resource
_model
is not None:
300 self
._resource
_model
.free(self
.containers
[name
])
301 self
._resource
_model
.write_free_log(self
.containers
[name
], self
.resource_log_path
)
305 link
=None, node1
=self
.containers
[name
], node2
=self
.switch
)
308 self
.net
.removeDocker("%s" % (name
))
309 del self
.containers
[name
]
313 def attachExternalSAP(self
, sap_name
, sap_net
, **params
):
314 extSAP
= EmulatorExtSAP(sap_name
, sap_net
, self
, **params
)
315 # link SAP to the DC switch
316 self
.net
.addLink(extSAP
.switch
, self
.switch
, cls
=Link
)
317 self
.extSAPs
[sap_name
] = extSAP
319 def removeExternalSAP(self
, sap_name
):
320 sap_switch
= self
.extSAPs
[sap_name
].switch
321 #sap_switch = self.net.getNodeByName(sap_name)
322 # remove link of SAP to the DC switch
323 self
.net
.removeLink(link
=None, node1
=sap_switch
, node2
=self
.switch
)
324 self
.net
.removeExtSAP(sap_name
)
325 del self
.extSAPs
[sap_name
]
327 def listCompute(self
):
329 Return a list of all running containers assigned to this
332 return list(self
.containers
.itervalues())
334 def listExtSAPs(self
):
336 Return a list of all external SAPs assigned to this
339 return list(self
.extSAPs
.itervalues())
343 Return a dict with status information about this DC.
345 container_list
= [name
for name
in self
.containers
]
346 ext_saplist
= [sap_name
for sap_name
in self
.extSAPs
]
349 "internalname": self
.name
,
350 "switch": self
.switch
.name
,
351 "n_running_containers": len(self
.containers
),
352 "metadata": self
.metadata
,
353 "vnf_list" : container_list
,
354 "ext SAP list" : ext_saplist
357 def assignResourceModel(self
, rm
):
359 Assign a resource model to this DC.
360 :param rm: a BaseResourceModel object
363 if self
._resource
_model
is not None:
364 raise Exception("There is already an resource model assigned to this DC.")
365 self
._resource
_model
= rm
366 self
.net
.rm_registrar
.register(self
, rm
)
367 LOG
.info("Assigned RM: %r to DC: %r" % (rm
, self
))
370 def _clean_ifname(name
):
372 Cleans up given string to be a
373 RTNETLINK compatible interface name.
379 name
= name
.replace(":", "-")
380 name
= name
.replace(" ", "-")
381 name
= name
.replace(".", "-")
382 name
= name
.replace("_", "-")