blob: b29d6343368f6d88f68c3422ff823f388219b6f5 [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
peusterm3444ae42016-03-16 20:46:41 +010035LOG = logging.getLogger("dcemulator")
36LOG.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
41
peusterm7aae6852016-01-12 14:53:18 +010042class EmulatorCompute(Docker):
43 """
44 Emulator specific compute node class.
peusterm5877ea22016-05-11 13:44:59 +020045 Inherits from Containernet's Docker host class.
peusterm7aae6852016-01-12 14:53:18 +010046 Represents a single container connected to a (logical)
47 data center.
48 We can add emulator specific helper functions to it.
49 """
50
51 def __init__(
52 self, name, dimage, **kwargs):
peusterm42f08be2016-03-10 21:56:34 +010053 self.datacenter = kwargs.get("datacenter") # pointer to current DC
54 self.flavor_name = kwargs.get("flavor_name")
peusterm3444ae42016-03-16 20:46:41 +010055 LOG.debug("Starting compute instance %r in data center %r" % (name, str(self.datacenter)))
peusterm7aae6852016-01-12 14:53:18 +010056 # call original Docker.__init__
57 Docker.__init__(self, name, dimage, **kwargs)
58
59 def getNetworkStatus(self):
60 """
61 Helper method to receive information about the virtual networks
62 this compute instance is connected to.
63 """
peusterm056fd452016-01-12 15:32:25 +010064 # format list of tuples (name, Ip, MAC, isUp, status)
stevenvanrossem307aa1f2016-05-06 10:35:15 +020065 return [{'intf_name':str(i), 'ip':i.IP(), 'mac':i.MAC(), 'up':i.isUp(), 'status':i.status()}
peusterm056fd452016-01-12 15:32:25 +010066 for i in self.intfList()]
peusterm7aae6852016-01-12 14:53:18 +010067
68 def getStatus(self):
69 """
70 Helper method to receive information about this compute instance.
71 """
peusterm056fd452016-01-12 15:32:25 +010072 status = {}
73 status["name"] = self.name
74 status["network"] = self.getNetworkStatus()
stevenvanrosseme66ef122016-05-03 11:22:54 +020075 status["docker_network"] = self.dcinfo['NetworkSettings']['IPAddress']
peusterm056fd452016-01-12 15:32:25 +010076 status["image"] = self.dimage
peusterm36c070c2016-04-16 17:39:01 +020077 status["flavor_name"] = self.flavor_name
peusterm056fd452016-01-12 15:32:25 +010078 status["cpu_quota"] = self.cpu_quota
79 status["cpu_period"] = self.cpu_period
80 status["cpu_shares"] = self.cpu_shares
81 status["cpuset"] = self.cpuset
82 status["mem_limit"] = self.mem_limit
83 status["memswap_limit"] = self.memswap_limit
84 status["state"] = self.dcli.inspect_container(self.dc)["State"]
85 status["id"] = self.dcli.inspect_container(self.dc)["Id"]
peusterm2ec74e12016-01-13 11:17:53 +010086 status["datacenter"] = (None if self.datacenter is None
peusterma47db032016-02-04 14:55:29 +010087 else self.datacenter.label)
peusterm056fd452016-01-12 15:32:25 +010088 return status
peusterm7aae6852016-01-12 14:53:18 +010089
90
peustermcbcd4c22015-12-28 11:33:42 +010091class Datacenter(object):
92 """
93 Represents a logical data center to which compute resources
94 (Docker containers) can be added at runtime.
peusterme4e89d32016-01-07 09:14:54 +010095
96 Will also implement resource bookkeeping in later versions.
peustermcbcd4c22015-12-28 11:33:42 +010097 """
98
peusterma47db032016-02-04 14:55:29 +010099 DC_COUNTER = 1
100
peusterm60bf8b82016-04-06 14:12:35 +0200101 def __init__(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100102 self.net = None # DCNetwork to which we belong
peusterma47db032016-02-04 14:55:29 +0100103 # each node (DC) has a short internal name used by Mininet
104 # this is caused by Mininets naming limitations for swtiches etc.
105 self.name = "dc%d" % Datacenter.DC_COUNTER
106 Datacenter.DC_COUNTER += 1
peusterm53504942016-02-04 16:09:28 +0100107 # use this for user defined names that can be longer than self.name
108 self.label = label
109 # dict to store arbitrary metadata (e.g. latitude and longitude)
110 self.metadata = metadata
peusterm60bf8b82016-04-06 14:12:35 +0200111 # path to which resource information should be logged (e.g. for experiments). None = no logging
112 self.resource_log_path = resource_log_path
peusterm42f08be2016-03-10 21:56:34 +0100113 # first prototype assumes one "bigswitch" per DC
114 self.switch = None
115 # keep track of running containers
116 self.containers = {}
117 # pointer to assigned resource model
118 self._resource_model = None
peustermcbcd4c22015-12-28 11:33:42 +0100119
peusterme26487b2016-03-08 14:00:21 +0100120 def __repr__(self):
121 return self.label
122
peustermcbcd4c22015-12-28 11:33:42 +0100123 def _get_next_dc_dpid(self):
124 global DCDPID_BASE
125 DCDPID_BASE += 1
126 return DCDPID_BASE
127
128 def create(self):
129 """
130 Each data center is represented by a single switch to which
131 compute resources can be connected at run time.
132
peusterm9c252b62016-01-06 16:59:53 +0100133 TODO: This will be changed in the future to support multiple networks
peustermcbcd4c22015-12-28 11:33:42 +0100134 per data center
135 """
peusterm293cbc32016-01-13 17:05:28 +0100136 self.switch = self.net.addSwitch(
peustermcbcd4c22015-12-28 11:33:42 +0100137 "%s.s1" % self.name, dpid=hex(self._get_next_dc_dpid())[2:])
peusterm3444ae42016-03-16 20:46:41 +0100138 LOG.debug("created data center switch: %s" % str(self.switch))
peustermcbcd4c22015-12-28 11:33:42 +0100139
140 def start(self):
141 pass
142
peusterm42f08be2016-03-10 21:56:34 +0100143 def startCompute(self, name, image=None, command=None, network=None, flavor_name="tiny"):
peusterme4e89d32016-01-07 09:14:54 +0100144 """
145 Create a new container as compute resource and connect it to this
146 data center.
peusterm7f8e8402016-02-28 18:38:10 +0100147 :param name: name (string)
148 :param image: image name (string)
149 :param command: command (string)
150 :param network: networks list({"ip": "10.0.0.254/8"}, {"ip": "11.0.0.254/24"})
peusterm42f08be2016-03-10 21:56:34 +0100151 :param flavor_name: name of the flavor for this compute container
peusterm7f8e8402016-02-28 18:38:10 +0100152 :return:
peusterme4e89d32016-01-07 09:14:54 +0100153 """
peusterm4e98b632016-01-12 14:08:07 +0100154 assert name is not None
peusterm9165ef92016-01-13 13:50:39 +0100155 # no duplications
peustermbd44f4a2016-01-13 14:53:30 +0100156 if name in [c.name for c in self.net.getAllContainers()]:
peusterm9165ef92016-01-13 13:50:39 +0100157 raise Exception("Container with name %s already exists." % name)
peusterm4e98b632016-01-12 14:08:07 +0100158 # set default parameter
159 if image is None:
peusterm0dc3ae02016-04-27 09:33:28 +0200160 image = "ubuntu:trusty"
peusterm4e98b632016-01-12 14:08:07 +0100161 if network is None:
162 network = {} # {"ip": "10.0.0.254/8"}
peusterm7f8e8402016-02-28 18:38:10 +0100163 if isinstance(network, dict):
164 network = [network] # if we have only one network, put it in a list
165 if isinstance(network, list):
166 if len(network) < 1:
167 network.append({})
168
169 # create the container
peusterm42f08be2016-03-10 21:56:34 +0100170 d = self.net.addDocker(
171 "%s" % (name),
172 dimage=image,
173 dcmd=command,
174 datacenter=self,
peustermd18559d2016-04-16 04:59:23 +0200175 flavor_name=flavor_name
peusterm71b3a2f2016-03-19 12:56:11 +0100176 )
peustermd18559d2016-04-16 04:59:23 +0200177
178 # apply resource limits to container if a resource model is defined
179 if self._resource_model is not None:
peusterma41f7862016-04-27 11:01:24 +0200180 try:
181 self._resource_model.allocate(d)
182 self._resource_model.write_allocation_log(d, self.resource_log_path)
183 except NotEnoughResourcesAvailable as ex:
184 LOG.warning("Allocation of container %r was blocked by resource model." % name)
185 LOG.info(ex.message)
186 # ensure that we remove the container
187 self.net.removeDocker(name)
188 return None
peustermd18559d2016-04-16 04:59:23 +0200189
peusterm7f8e8402016-02-28 18:38:10 +0100190 # connect all given networks
stevenvanrossem14c89052016-04-10 23:49:59 +0200191 # if no --net option is given, network = [{}], so 1 empty dict in the list
192 # this results in 1 default interface with a default ip address
peusterm7f8e8402016-02-28 18:38:10 +0100193 for nw in network:
peusterm5877ea22016-05-11 13:44:59 +0200194 # TODO we cannot use TCLink here (see: https://github.com/mpeuster/containernet/issues/3)
stevenvanrossem5b376412016-05-04 15:34:49 +0200195 self.net.addLink(d, self.switch, params1=nw, cls=Link, intfName1=nw.get('id'))
peusterm2ec74e12016-01-13 11:17:53 +0100196 # do bookkeeping
peusterma2ad9ff2016-01-11 17:10:07 +0100197 self.containers[name] = d
peustermfa4bcc72016-01-15 11:08:09 +0100198 return d # we might use UUIDs for naming later on
peustermcbcd4c22015-12-28 11:33:42 +0100199
peusterm7aae6852016-01-12 14:53:18 +0100200 def stopCompute(self, name):
peusterma2ad9ff2016-01-11 17:10:07 +0100201 """
202 Stop and remove a container from this data center.
203 """
peusterm9165ef92016-01-13 13:50:39 +0100204 assert name is not None
205 if name not in self.containers:
206 raise Exception("Container with name %s not found." % name)
peusterm60bf8b82016-04-06 14:12:35 +0200207 LOG.debug("Stopping compute instance %r in data center %r" % (name, str(self)))
peustermd18559d2016-04-16 04:59:23 +0200208
stevenvanrossem461941c2016-05-10 11:41:29 +0200209 # stop the monitored metrics
210 if self.net.monitor_agent is not None:
211 self.net.monitor_agent.stop_metric(name)
212
peusterm42f08be2016-03-10 21:56:34 +0100213 # call resource model and free resources
214 if self._resource_model is not None:
peustermd18559d2016-04-16 04:59:23 +0200215 self._resource_model.free(self.containers[name])
peusterm36c070c2016-04-16 17:39:01 +0200216 self._resource_model.write_free_log(self.containers[name], self.resource_log_path)
peusterm60bf8b82016-04-06 14:12:35 +0200217
peustermd18559d2016-04-16 04:59:23 +0200218 # remove links
219 self.net.removeLink(
220 link=None, node1=self.containers[name], node2=self.switch)
221
222 # remove container
223 self.net.removeDocker("%s" % (name))
224 del self.containers[name]
225
peusterm4e98b632016-01-12 14:08:07 +0100226 return True
227
228 def listCompute(self):
229 """
230 Return a list of all running containers assigned to this
231 data center.
232 """
peusterm5aa8cf22016-01-15 11:12:17 +0100233 return list(self.containers.itervalues())
peustermd313dc12016-02-04 15:36:02 +0100234
235 def getStatus(self):
236 """
237 Return a dict with status information about this DC.
238 """
peusterm53504942016-02-04 16:09:28 +0100239 return {
240 "label": self.label,
241 "internalname": self.name,
242 "switch": self.switch.name,
243 "n_running_containers": len(self.containers),
244 "metadata": self.metadata
245 }
peusterm42f08be2016-03-10 21:56:34 +0100246
247 def assignResourceModel(self, rm):
peusterm279565d2016-03-19 10:36:52 +0100248 """
249 Assign a resource model to this DC.
250 :param rm: a BaseResourceModel object
251 :return:
252 """
peusterm42f08be2016-03-10 21:56:34 +0100253 if self._resource_model is not None:
254 raise Exception("There is already an resource model assigned to this DC.")
255 self._resource_model = rm
256 self.net.rm_registrar.register(self, rm)
peusterm3444ae42016-03-16 20:46:41 +0100257 LOG.info("Assigned RM: %r to DC: %r" % (rm, self))
peusterm42f08be2016-03-10 21:56:34 +0100258