blob: e0eb7d317181335af1382265fc3c9f41284a1e79 [file] [log] [blame]
peustermcbcd4c22015-12-28 11:33:42 +01001"""
peusterm79ef6ae2016-07-08 13:53:57 +02002Copyright (c) 2015 SONATA-NFV and Paderborn University
3ALL RIGHTS RESERVED.
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16
17Neither the name of the SONATA-NFV [, ANY ADDITIONAL AFFILIATION]
18nor the names of its contributors may be used to endorse or promote
19products derived from this software without specific prior written
20permission.
21
22This work has been performed in the framework of the SONATA project,
23funded by the European Commission under Grant number 671517 through
24the Horizon 2020 and 5G-PPP programmes. The authors would like to
25acknowledge the contributions of their colleagues of the SONATA
26partner consortium (www.sonata-nfv.eu).
peustermcbcd4c22015-12-28 11:33:42 +010027"""
peusterm7aae6852016-01-12 14:53:18 +010028from mininet.node import Docker
peustermea8db832016-03-08 10:25:58 +010029from mininet.link import Link
peusterma41f7862016-04-27 11:01:24 +020030from emuvim.dcemulator.resourcemodel import NotEnoughResourcesAvailable
peustermcbcd4c22015-12-28 11:33:42 +010031import logging
peusterm60bf8b82016-04-06 14:12:35 +020032import time
33import json
peustermcbcd4c22015-12-28 11:33:42 +010034
peustermf9a817d2016-07-18 09:06:04 +020035LOG = logging.getLogger("dcemulator.node")
peusterm3444ae42016-03-16 20:46:41 +010036LOG.setLevel(logging.DEBUG)
37
peustermcbcd4c22015-12-28 11:33:42 +010038
39DCDPID_BASE = 1000 # start of switch dpid's used for data center switches
40
peusterm7aae6852016-01-12 14:53:18 +010041class EmulatorCompute(Docker):
42 """
43 Emulator specific compute node class.
peusterm5877ea22016-05-11 13:44:59 +020044 Inherits from Containernet's Docker host class.
peusterm7aae6852016-01-12 14:53:18 +010045 Represents a single container connected to a (logical)
46 data center.
47 We can add emulator specific helper functions to it.
48 """
49
50 def __init__(
51 self, name, dimage, **kwargs):
peusterm42f08be2016-03-10 21:56:34 +010052 self.datacenter = kwargs.get("datacenter") # pointer to current DC
53 self.flavor_name = kwargs.get("flavor_name")
peusterm3444ae42016-03-16 20:46:41 +010054 LOG.debug("Starting compute instance %r in data center %r" % (name, str(self.datacenter)))
peusterm7aae6852016-01-12 14:53:18 +010055 # call original Docker.__init__
56 Docker.__init__(self, name, dimage, **kwargs)
57
58 def getNetworkStatus(self):
59 """
60 Helper method to receive information about the virtual networks
61 this compute instance is connected to.
62 """
stevenvanrossem566779d2016-11-07 06:33:44 +010063 # get all links and find dc switch interface
64 networkStatusList = []
65 for i in self.intfList():
66 vnf_name = self.name
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': i.IP(), 'mac': i.MAC(), 'up': i.isUp(), 'status': i.status(), 'dc_portname': dc_port_name}
71 networkStatusList.append(intf_dict)
72
73 return networkStatusList
peusterm7aae6852016-01-12 14:53:18 +010074
75 def getStatus(self):
76 """
77 Helper method to receive information about this compute instance.
78 """
peusterm056fd452016-01-12 15:32:25 +010079 status = {}
80 status["name"] = self.name
81 status["network"] = self.getNetworkStatus()
stevenvanrosseme66ef122016-05-03 11:22:54 +020082 status["docker_network"] = self.dcinfo['NetworkSettings']['IPAddress']
peusterm056fd452016-01-12 15:32:25 +010083 status["image"] = self.dimage
peusterm36c070c2016-04-16 17:39:01 +020084 status["flavor_name"] = self.flavor_name
stevenvanrossem08272492017-02-10 17:18:44 +010085 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')
peusterm056fd452016-01-12 15:32:25 +010091 status["state"] = self.dcli.inspect_container(self.dc)["State"]
92 status["id"] = self.dcli.inspect_container(self.dc)["Id"]
stevenvanrossem8a9df3f2017-01-27 22:35:04 +010093 status["short_id"] = self.dcli.inspect_container(self.dc)["Id"][:12]
peusterm2ec74e12016-01-13 11:17:53 +010094 status["datacenter"] = (None if self.datacenter is None
peusterma47db032016-02-04 14:55:29 +010095 else self.datacenter.label)
peusterm056fd452016-01-12 15:32:25 +010096 return status
peusterm7aae6852016-01-12 14:53:18 +010097
98
peustermcbcd4c22015-12-28 11:33:42 +010099class Datacenter(object):
100 """
101 Represents a logical data center to which compute resources
102 (Docker containers) can be added at runtime.
peusterme4e89d32016-01-07 09:14:54 +0100103
104 Will also implement resource bookkeeping in later versions.
peustermcbcd4c22015-12-28 11:33:42 +0100105 """
106
peusterma47db032016-02-04 14:55:29 +0100107 DC_COUNTER = 1
108
peusterm60bf8b82016-04-06 14:12:35 +0200109 def __init__(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100110 self.net = None # DCNetwork to which we belong
peusterma47db032016-02-04 14:55:29 +0100111 # each node (DC) has a short internal name used by Mininet
112 # this is caused by Mininets naming limitations for swtiches etc.
113 self.name = "dc%d" % Datacenter.DC_COUNTER
114 Datacenter.DC_COUNTER += 1
peusterm53504942016-02-04 16:09:28 +0100115 # use this for user defined names that can be longer than self.name
edmaas7e084ea2016-11-28 13:50:23 +0100116 self.label = label
peusterm53504942016-02-04 16:09:28 +0100117 # dict to store arbitrary metadata (e.g. latitude and longitude)
118 self.metadata = metadata
peusterm60bf8b82016-04-06 14:12:35 +0200119 # path to which resource information should be logged (e.g. for experiments). None = no logging
120 self.resource_log_path = resource_log_path
peusterm42f08be2016-03-10 21:56:34 +0100121 # first prototype assumes one "bigswitch" per DC
122 self.switch = None
123 # keep track of running containers
124 self.containers = {}
125 # pointer to assigned resource model
126 self._resource_model = None
peustermcbcd4c22015-12-28 11:33:42 +0100127
peusterme26487b2016-03-08 14:00:21 +0100128 def __repr__(self):
129 return self.label
130
peustermcbcd4c22015-12-28 11:33:42 +0100131 def _get_next_dc_dpid(self):
132 global DCDPID_BASE
133 DCDPID_BASE += 1
134 return DCDPID_BASE
135
136 def create(self):
137 """
138 Each data center is represented by a single switch to which
139 compute resources can be connected at run time.
140
peusterm9c252b62016-01-06 16:59:53 +0100141 TODO: This will be changed in the future to support multiple networks
peustermcbcd4c22015-12-28 11:33:42 +0100142 per data center
143 """
peusterm293cbc32016-01-13 17:05:28 +0100144 self.switch = self.net.addSwitch(
peustermcbcd4c22015-12-28 11:33:42 +0100145 "%s.s1" % self.name, dpid=hex(self._get_next_dc_dpid())[2:])
peusterm3444ae42016-03-16 20:46:41 +0100146 LOG.debug("created data center switch: %s" % str(self.switch))
peustermcbcd4c22015-12-28 11:33:42 +0100147
148 def start(self):
149 pass
150
edmaas7e084ea2016-11-28 13:50:23 +0100151 def startCompute(self, name, image=None, command=None, network=None, flavor_name="tiny", **params):
peusterme4e89d32016-01-07 09:14:54 +0100152 """
153 Create a new container as compute resource and connect it to this
154 data center.
peusterm7f8e8402016-02-28 18:38:10 +0100155 :param name: name (string)
156 :param image: image name (string)
157 :param command: command (string)
158 :param network: networks list({"ip": "10.0.0.254/8"}, {"ip": "11.0.0.254/24"})
peusterm42f08be2016-03-10 21:56:34 +0100159 :param flavor_name: name of the flavor for this compute container
peusterm7f8e8402016-02-28 18:38:10 +0100160 :return:
peusterme4e89d32016-01-07 09:14:54 +0100161 """
peusterm4e98b632016-01-12 14:08:07 +0100162 assert name is not None
peusterm9165ef92016-01-13 13:50:39 +0100163 # no duplications
peustermbd44f4a2016-01-13 14:53:30 +0100164 if name in [c.name for c in self.net.getAllContainers()]:
peusterm9165ef92016-01-13 13:50:39 +0100165 raise Exception("Container with name %s already exists." % name)
peusterm4e98b632016-01-12 14:08:07 +0100166 # set default parameter
167 if image is None:
peusterm0dc3ae02016-04-27 09:33:28 +0200168 image = "ubuntu:trusty"
peusterm4e98b632016-01-12 14:08:07 +0100169 if network is None:
170 network = {} # {"ip": "10.0.0.254/8"}
peusterm7f8e8402016-02-28 18:38:10 +0100171 if isinstance(network, dict):
172 network = [network] # if we have only one network, put it in a list
173 if isinstance(network, list):
174 if len(network) < 1:
175 network.append({})
176
stevenvanrossemb3f34172016-11-16 23:30:57 +0100177 # apply hard-set resource limits=0
stevenvanrosseme8d86282017-01-28 00:52:22 +0100178 cpu_percentage = params.get('cpu_percent')
stevenvanrossemb3f34172016-11-16 23:30:57 +0100179 if cpu_percentage:
stevenvanrosseme8d86282017-01-28 00:52:22 +0100180 params['cpu_period'] = self.net.cpu_period
181 params['cpu_quota'] = self.net.cpu_period * float(cpu_percentage)
stevenvanrossemb3f34172016-11-16 23:30:57 +0100182
peusterm7f8e8402016-02-28 18:38:10 +0100183 # create the container
peusterm42f08be2016-03-10 21:56:34 +0100184 d = self.net.addDocker(
185 "%s" % (name),
186 dimage=image,
187 dcmd=command,
188 datacenter=self,
stevenvanrossemb3f34172016-11-16 23:30:57 +0100189 flavor_name=flavor_name,
stevenvanrosseme8d86282017-01-28 00:52:22 +0100190 environment = {'VNF_NAME':name},
edmaas7e084ea2016-11-28 13:50:23 +0100191 **params
peusterm71b3a2f2016-03-19 12:56:11 +0100192 )
peustermd18559d2016-04-16 04:59:23 +0200193
stevenvanrossemb3f34172016-11-16 23:30:57 +0100194
195
peustermd18559d2016-04-16 04:59:23 +0200196 # apply resource limits to container if a resource model is defined
197 if self._resource_model is not None:
peusterma41f7862016-04-27 11:01:24 +0200198 try:
199 self._resource_model.allocate(d)
200 self._resource_model.write_allocation_log(d, self.resource_log_path)
201 except NotEnoughResourcesAvailable as ex:
202 LOG.warning("Allocation of container %r was blocked by resource model." % name)
203 LOG.info(ex.message)
204 # ensure that we remove the container
205 self.net.removeDocker(name)
206 return None
peustermd18559d2016-04-16 04:59:23 +0200207
peusterm7f8e8402016-02-28 18:38:10 +0100208 # connect all given networks
stevenvanrossem14c89052016-04-10 23:49:59 +0200209 # if no --net option is given, network = [{}], so 1 empty dict in the list
210 # this results in 1 default interface with a default ip address
peusterm7f8e8402016-02-28 18:38:10 +0100211 for nw in network:
peusterm761c14d2016-07-19 09:31:19 +0200212 # clean up network configuration (e.g. RTNETLINK does not allow ':' in intf names
213 if nw.get("id") is not None:
214 nw["id"] = self._clean_ifname(nw["id"])
peusterm5877ea22016-05-11 13:44:59 +0200215 # TODO we cannot use TCLink here (see: https://github.com/mpeuster/containernet/issues/3)
stevenvanrossem5b376412016-05-04 15:34:49 +0200216 self.net.addLink(d, self.switch, params1=nw, cls=Link, intfName1=nw.get('id'))
peusterm2ec74e12016-01-13 11:17:53 +0100217 # do bookkeeping
peusterma2ad9ff2016-01-11 17:10:07 +0100218 self.containers[name] = d
peustermfa4bcc72016-01-15 11:08:09 +0100219 return d # we might use UUIDs for naming later on
peustermcbcd4c22015-12-28 11:33:42 +0100220
peusterm7aae6852016-01-12 14:53:18 +0100221 def stopCompute(self, name):
peusterma2ad9ff2016-01-11 17:10:07 +0100222 """
223 Stop and remove a container from this data center.
224 """
peusterm9165ef92016-01-13 13:50:39 +0100225 assert name is not None
226 if name not in self.containers:
227 raise Exception("Container with name %s not found." % name)
peusterm60bf8b82016-04-06 14:12:35 +0200228 LOG.debug("Stopping compute instance %r in data center %r" % (name, str(self)))
peustermd18559d2016-04-16 04:59:23 +0200229
stevenvanrossem461941c2016-05-10 11:41:29 +0200230 # stop the monitored metrics
231 if self.net.monitor_agent is not None:
232 self.net.monitor_agent.stop_metric(name)
233
peusterm42f08be2016-03-10 21:56:34 +0100234 # call resource model and free resources
235 if self._resource_model is not None:
peustermd18559d2016-04-16 04:59:23 +0200236 self._resource_model.free(self.containers[name])
peusterm36c070c2016-04-16 17:39:01 +0200237 self._resource_model.write_free_log(self.containers[name], self.resource_log_path)
peusterm60bf8b82016-04-06 14:12:35 +0200238
peustermd18559d2016-04-16 04:59:23 +0200239 # remove links
240 self.net.removeLink(
241 link=None, node1=self.containers[name], node2=self.switch)
242
243 # remove container
244 self.net.removeDocker("%s" % (name))
245 del self.containers[name]
246
peusterm4e98b632016-01-12 14:08:07 +0100247 return True
248
stevenvanrossemce032e12017-04-05 17:31:20 +0200249 def attachExternalSAP(self, sap_name, sap_ip):
250 # create SAP as OVS internal interface
251 sap_intf = self.switch.attachInternalIntf(sap_name, sap_ip)
252
253 # add this as a link to the DCnetwork graph, so it is available for routing
254 attr_dict2 = {'src_port_id': sap_name, 'src_port_nr': None,
255 'src_port_name': sap_name,
256 'dst_port_id': self.switch.ports[sap_intf], 'dst_port_nr': self.switch.ports[sap_intf],
257 'dst_port_name': sap_intf.name}
258 self.net.DCNetwork_graph.add_edge(sap_name, self.switch.name, attr_dict=attr_dict2)
259
260 attr_dict2 = {'dst_port_id': sap_name, 'dst_port_nr': None,
261 'dst_port_name': sap_name,
262 'src_port_id': self.switch.ports[sap_intf], 'src_port_nr': self.switch.ports[sap_intf],
263 'src_port_name': sap_intf.name}
264 self.net.DCNetwork_graph.add_edge(self.switch.name, sap_name, attr_dict=attr_dict2)
265
266
peusterm4e98b632016-01-12 14:08:07 +0100267 def listCompute(self):
268 """
269 Return a list of all running containers assigned to this
270 data center.
271 """
peusterm5aa8cf22016-01-15 11:12:17 +0100272 return list(self.containers.itervalues())
peustermd313dc12016-02-04 15:36:02 +0100273
274 def getStatus(self):
275 """
276 Return a dict with status information about this DC.
277 """
peusterm53504942016-02-04 16:09:28 +0100278 return {
279 "label": self.label,
280 "internalname": self.name,
281 "switch": self.switch.name,
282 "n_running_containers": len(self.containers),
283 "metadata": self.metadata
284 }
peusterm42f08be2016-03-10 21:56:34 +0100285
286 def assignResourceModel(self, rm):
peusterm279565d2016-03-19 10:36:52 +0100287 """
288 Assign a resource model to this DC.
289 :param rm: a BaseResourceModel object
290 :return:
291 """
peusterm42f08be2016-03-10 21:56:34 +0100292 if self._resource_model is not None:
293 raise Exception("There is already an resource model assigned to this DC.")
294 self._resource_model = rm
295 self.net.rm_registrar.register(self, rm)
peusterm3444ae42016-03-16 20:46:41 +0100296 LOG.info("Assigned RM: %r to DC: %r" % (rm, self))
peusterm42f08be2016-03-10 21:56:34 +0100297
peusterm761c14d2016-07-19 09:31:19 +0200298 @staticmethod
299 def _clean_ifname(name):
300 """
301 Cleans up given string to be a
302 RTNETLINK compatible interface name.
303 :param name: string
304 :return: string
305 """
306 if name is None:
307 return "if0"
308 name = name.replace(":", "-")
309 name = name.replace(" ", "-")
310 name = name.replace(".", "-")
311 name = name.replace("_", "-")
312 return name
313