blob: 8e7a63ac08a88ca254661b7f7d19139e85d322e9 [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
peusterm056fd452016-01-12 15:32:25 +010085 status["cpu_quota"] = self.cpu_quota
86 status["cpu_period"] = self.cpu_period
87 status["cpu_shares"] = self.cpu_shares
88 status["cpuset"] = self.cpuset
89 status["mem_limit"] = self.mem_limit
90 status["memswap_limit"] = self.memswap_limit
91 status["state"] = self.dcli.inspect_container(self.dc)["State"]
92 status["id"] = self.dcli.inspect_container(self.dc)["Id"]
peusterm2ec74e12016-01-13 11:17:53 +010093 status["datacenter"] = (None if self.datacenter is None
peusterma47db032016-02-04 14:55:29 +010094 else self.datacenter.label)
peusterm056fd452016-01-12 15:32:25 +010095 return status
peusterm7aae6852016-01-12 14:53:18 +010096
97
peustermcbcd4c22015-12-28 11:33:42 +010098class Datacenter(object):
99 """
100 Represents a logical data center to which compute resources
101 (Docker containers) can be added at runtime.
peusterme4e89d32016-01-07 09:14:54 +0100102
103 Will also implement resource bookkeeping in later versions.
peustermcbcd4c22015-12-28 11:33:42 +0100104 """
105
peusterma47db032016-02-04 14:55:29 +0100106 DC_COUNTER = 1
107
peusterm60bf8b82016-04-06 14:12:35 +0200108 def __init__(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100109 self.net = None # DCNetwork to which we belong
peusterma47db032016-02-04 14:55:29 +0100110 # each node (DC) has a short internal name used by Mininet
111 # this is caused by Mininets naming limitations for swtiches etc.
112 self.name = "dc%d" % Datacenter.DC_COUNTER
113 Datacenter.DC_COUNTER += 1
peusterm53504942016-02-04 16:09:28 +0100114 # use this for user defined names that can be longer than self.name
115 self.label = label
116 # dict to store arbitrary metadata (e.g. latitude and longitude)
117 self.metadata = metadata
peusterm60bf8b82016-04-06 14:12:35 +0200118 # path to which resource information should be logged (e.g. for experiments). None = no logging
119 self.resource_log_path = resource_log_path
peusterm42f08be2016-03-10 21:56:34 +0100120 # first prototype assumes one "bigswitch" per DC
121 self.switch = None
122 # keep track of running containers
123 self.containers = {}
124 # pointer to assigned resource model
125 self._resource_model = None
peustermcbcd4c22015-12-28 11:33:42 +0100126
peusterme26487b2016-03-08 14:00:21 +0100127 def __repr__(self):
128 return self.label
129
peustermcbcd4c22015-12-28 11:33:42 +0100130 def _get_next_dc_dpid(self):
131 global DCDPID_BASE
132 DCDPID_BASE += 1
133 return DCDPID_BASE
134
135 def create(self):
136 """
137 Each data center is represented by a single switch to which
138 compute resources can be connected at run time.
139
peusterm9c252b62016-01-06 16:59:53 +0100140 TODO: This will be changed in the future to support multiple networks
peustermcbcd4c22015-12-28 11:33:42 +0100141 per data center
142 """
peusterm293cbc32016-01-13 17:05:28 +0100143 self.switch = self.net.addSwitch(
peustermcbcd4c22015-12-28 11:33:42 +0100144 "%s.s1" % self.name, dpid=hex(self._get_next_dc_dpid())[2:])
peusterm3444ae42016-03-16 20:46:41 +0100145 LOG.debug("created data center switch: %s" % str(self.switch))
peustermcbcd4c22015-12-28 11:33:42 +0100146
147 def start(self):
148 pass
149
stevenvanrossemb3f34172016-11-16 23:30:57 +0100150 def startCompute(self, name, image=None, command=None, network=None, flavor_name="tiny", **kwargs):
peusterme4e89d32016-01-07 09:14:54 +0100151 """
152 Create a new container as compute resource and connect it to this
153 data center.
peusterm7f8e8402016-02-28 18:38:10 +0100154 :param name: name (string)
155 :param image: image name (string)
156 :param command: command (string)
157 :param network: networks list({"ip": "10.0.0.254/8"}, {"ip": "11.0.0.254/24"})
peusterm42f08be2016-03-10 21:56:34 +0100158 :param flavor_name: name of the flavor for this compute container
peusterm7f8e8402016-02-28 18:38:10 +0100159 :return:
peusterme4e89d32016-01-07 09:14:54 +0100160 """
peusterm4e98b632016-01-12 14:08:07 +0100161 assert name is not None
peusterm9165ef92016-01-13 13:50:39 +0100162 # no duplications
peustermbd44f4a2016-01-13 14:53:30 +0100163 if name in [c.name for c in self.net.getAllContainers()]:
peusterm9165ef92016-01-13 13:50:39 +0100164 raise Exception("Container with name %s already exists." % name)
peusterm4e98b632016-01-12 14:08:07 +0100165 # set default parameter
166 if image is None:
peusterm0dc3ae02016-04-27 09:33:28 +0200167 image = "ubuntu:trusty"
peusterm4e98b632016-01-12 14:08:07 +0100168 if network is None:
169 network = {} # {"ip": "10.0.0.254/8"}
peusterm7f8e8402016-02-28 18:38:10 +0100170 if isinstance(network, dict):
171 network = [network] # if we have only one network, put it in a list
172 if isinstance(network, list):
173 if len(network) < 1:
174 network.append({})
175
stevenvanrossemb3f34172016-11-16 23:30:57 +0100176 # apply hard-set resource limits=0
177 cpu_percentage = kwargs.get('cpu_percent')
178 if cpu_percentage:
179 cpu_period = self.net.cpu_period
180 cpu_quota = self.net.cpu_period * float(cpu_percentage)
181 else:
182 cpu_quota = None
183 cpu_period = None
184
peusterm7f8e8402016-02-28 18:38:10 +0100185 # create the container
peusterm42f08be2016-03-10 21:56:34 +0100186 d = self.net.addDocker(
187 "%s" % (name),
188 dimage=image,
189 dcmd=command,
190 datacenter=self,
stevenvanrossemb3f34172016-11-16 23:30:57 +0100191 flavor_name=flavor_name,
192 cpu_period = cpu_period,
193 cpu_quota = cpu_quota
peusterm71b3a2f2016-03-19 12:56:11 +0100194 )
peustermd18559d2016-04-16 04:59:23 +0200195
stevenvanrossemb3f34172016-11-16 23:30:57 +0100196
197
peustermd18559d2016-04-16 04:59:23 +0200198 # apply resource limits to container if a resource model is defined
199 if self._resource_model is not None:
peusterma41f7862016-04-27 11:01:24 +0200200 try:
201 self._resource_model.allocate(d)
202 self._resource_model.write_allocation_log(d, self.resource_log_path)
203 except NotEnoughResourcesAvailable as ex:
204 LOG.warning("Allocation of container %r was blocked by resource model." % name)
205 LOG.info(ex.message)
206 # ensure that we remove the container
207 self.net.removeDocker(name)
208 return None
peustermd18559d2016-04-16 04:59:23 +0200209
peusterm7f8e8402016-02-28 18:38:10 +0100210 # connect all given networks
stevenvanrossem14c89052016-04-10 23:49:59 +0200211 # if no --net option is given, network = [{}], so 1 empty dict in the list
212 # this results in 1 default interface with a default ip address
peusterm7f8e8402016-02-28 18:38:10 +0100213 for nw in network:
peusterm761c14d2016-07-19 09:31:19 +0200214 # clean up network configuration (e.g. RTNETLINK does not allow ':' in intf names
215 if nw.get("id") is not None:
216 nw["id"] = self._clean_ifname(nw["id"])
peusterm5877ea22016-05-11 13:44:59 +0200217 # TODO we cannot use TCLink here (see: https://github.com/mpeuster/containernet/issues/3)
stevenvanrossem5b376412016-05-04 15:34:49 +0200218 self.net.addLink(d, self.switch, params1=nw, cls=Link, intfName1=nw.get('id'))
peusterm2ec74e12016-01-13 11:17:53 +0100219 # do bookkeeping
peusterma2ad9ff2016-01-11 17:10:07 +0100220 self.containers[name] = d
peustermfa4bcc72016-01-15 11:08:09 +0100221 return d # we might use UUIDs for naming later on
peustermcbcd4c22015-12-28 11:33:42 +0100222
peusterm7aae6852016-01-12 14:53:18 +0100223 def stopCompute(self, name):
peusterma2ad9ff2016-01-11 17:10:07 +0100224 """
225 Stop and remove a container from this data center.
226 """
peusterm9165ef92016-01-13 13:50:39 +0100227 assert name is not None
228 if name not in self.containers:
229 raise Exception("Container with name %s not found." % name)
peusterm60bf8b82016-04-06 14:12:35 +0200230 LOG.debug("Stopping compute instance %r in data center %r" % (name, str(self)))
peustermd18559d2016-04-16 04:59:23 +0200231
stevenvanrossem461941c2016-05-10 11:41:29 +0200232 # stop the monitored metrics
233 if self.net.monitor_agent is not None:
234 self.net.monitor_agent.stop_metric(name)
235
peusterm42f08be2016-03-10 21:56:34 +0100236 # call resource model and free resources
237 if self._resource_model is not None:
peustermd18559d2016-04-16 04:59:23 +0200238 self._resource_model.free(self.containers[name])
peusterm36c070c2016-04-16 17:39:01 +0200239 self._resource_model.write_free_log(self.containers[name], self.resource_log_path)
peusterm60bf8b82016-04-06 14:12:35 +0200240
peustermd18559d2016-04-16 04:59:23 +0200241 # remove links
242 self.net.removeLink(
243 link=None, node1=self.containers[name], node2=self.switch)
244
245 # remove container
246 self.net.removeDocker("%s" % (name))
247 del self.containers[name]
248
peusterm4e98b632016-01-12 14:08:07 +0100249 return True
250
251 def listCompute(self):
252 """
253 Return a list of all running containers assigned to this
254 data center.
255 """
peusterm5aa8cf22016-01-15 11:12:17 +0100256 return list(self.containers.itervalues())
peustermd313dc12016-02-04 15:36:02 +0100257
258 def getStatus(self):
259 """
260 Return a dict with status information about this DC.
261 """
peusterm53504942016-02-04 16:09:28 +0100262 return {
263 "label": self.label,
264 "internalname": self.name,
265 "switch": self.switch.name,
266 "n_running_containers": len(self.containers),
267 "metadata": self.metadata
268 }
peusterm42f08be2016-03-10 21:56:34 +0100269
270 def assignResourceModel(self, rm):
peusterm279565d2016-03-19 10:36:52 +0100271 """
272 Assign a resource model to this DC.
273 :param rm: a BaseResourceModel object
274 :return:
275 """
peusterm42f08be2016-03-10 21:56:34 +0100276 if self._resource_model is not None:
277 raise Exception("There is already an resource model assigned to this DC.")
278 self._resource_model = rm
279 self.net.rm_registrar.register(self, rm)
peusterm3444ae42016-03-16 20:46:41 +0100280 LOG.info("Assigned RM: %r to DC: %r" % (rm, self))
peusterm42f08be2016-03-10 21:56:34 +0100281
peusterm761c14d2016-07-19 09:31:19 +0200282 @staticmethod
283 def _clean_ifname(name):
284 """
285 Cleans up given string to be a
286 RTNETLINK compatible interface name.
287 :param name: string
288 :return: string
289 """
290 if name is None:
291 return "if0"
292 name = name.replace(":", "-")
293 name = name.replace(" ", "-")
294 name = name.replace(".", "-")
295 name = name.replace("_", "-")
296 return name
297