update getStatus print to include all resources
[osm/vim-emu.git] / src / emuvim / dcemulator / node.py
1 """
2 Copyright (c) 2015 SONATA-NFV and Paderborn University
3 ALL RIGHTS RESERVED.
4
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
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
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.
16
17 Neither the name of the SONATA-NFV [, ANY ADDITIONAL AFFILIATION]
18 nor the names of its contributors may be used to endorse or promote
19 products derived from this software without specific prior written
20 permission.
21
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).
27 """
28 from mininet.node import Docker
29 from mininet.link import Link
30 from emuvim.dcemulator.resourcemodel import NotEnoughResourcesAvailable
31 import logging
32 import time
33 import json
34
35 LOG = logging.getLogger("dcemulator.node")
36 LOG.setLevel(logging.DEBUG)
37
38
39 DCDPID_BASE = 1000 # start of switch dpid's used for data center switches
40
41 class EmulatorCompute(Docker):
42 """
43 Emulator specific compute node class.
44 Inherits from Containernet's Docker host class.
45 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):
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)
57
58 def getNetworkStatus(self):
59 """
60 Helper method to receive information about the virtual networks
61 this compute instance is connected to.
62 """
63 # 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
74
75 def getStatus(self):
76 """
77 Helper method to receive information about this compute instance.
78 """
79 status = {}
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["datacenter"] = (None if self.datacenter is None
95 else self.datacenter.label)
96 return status
97
98
99 class Datacenter(object):
100 """
101 Represents a logical data center to which compute resources
102 (Docker containers) can be added at runtime.
103
104 Will also implement resource bookkeeping in later versions.
105 """
106
107 DC_COUNTER = 1
108
109 def __init__(self, label, metadata={}, resource_log_path=None):
110 self.net = None # DCNetwork to which we belong
111 # 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
115 # use this for user defined names that can be longer than self.name
116 self.label = label
117 # dict to store arbitrary metadata (e.g. latitude and longitude)
118 self.metadata = metadata
119 # path to which resource information should be logged (e.g. for experiments). None = no logging
120 self.resource_log_path = resource_log_path
121 # 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
127
128 def __repr__(self):
129 return self.label
130
131 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
141 TODO: This will be changed in the future to support multiple networks
142 per data center
143 """
144 self.switch = self.net.addSwitch(
145 "%s.s1" % self.name, dpid=hex(self._get_next_dc_dpid())[2:])
146 LOG.debug("created data center switch: %s" % str(self.switch))
147
148 def start(self):
149 pass
150
151 def startCompute(self, name, image=None, command=None, network=None, flavor_name="tiny", **params):
152 """
153 Create a new container as compute resource and connect it to this
154 data center.
155 :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"})
159 :param flavor_name: name of the flavor for this compute container
160 :return:
161 """
162 assert name is not None
163 # no duplications
164 if name in [c.name for c in self.net.getAllContainers()]:
165 raise Exception("Container with name %s already exists." % name)
166 # set default parameter
167 if image is None:
168 image = "ubuntu:trusty"
169 if network is None:
170 network = {} # {"ip": "10.0.0.254/8"}
171 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
177 # apply hard-set resource limits=0
178 cpu_percentage = params.get('cpu_percent')
179 if cpu_percentage:
180 params['cpu_period'] = self.net.cpu_period
181 params['cpu_quota'] = self.net.cpu_period * float(cpu_percentage)
182
183 # create the container
184 d = self.net.addDocker(
185 "%s" % (name),
186 dimage=image,
187 dcmd=command,
188 datacenter=self,
189 flavor_name=flavor_name,
190 environment = {'VNF_NAME':name},
191 **params
192 )
193
194
195
196 # apply resource limits to container if a resource model is defined
197 if self._resource_model is not None:
198 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
207
208 # connect all given networks
209 # 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
211 for nw in network:
212 # 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"])
215 # TODO we cannot use TCLink here (see: https://github.com/mpeuster/containernet/issues/3)
216 self.net.addLink(d, self.switch, params1=nw, cls=Link, intfName1=nw.get('id'))
217 # do bookkeeping
218 self.containers[name] = d
219 return d # we might use UUIDs for naming later on
220
221 def stopCompute(self, name):
222 """
223 Stop and remove a container from this data center.
224 """
225 assert name is not None
226 if name not in self.containers:
227 raise Exception("Container with name %s not found." % name)
228 LOG.debug("Stopping compute instance %r in data center %r" % (name, str(self)))
229
230 # stop the monitored metrics
231 if self.net.monitor_agent is not None:
232 self.net.monitor_agent.stop_metric(name)
233
234 # call resource model and free resources
235 if self._resource_model is not None:
236 self._resource_model.free(self.containers[name])
237 self._resource_model.write_free_log(self.containers[name], self.resource_log_path)
238
239 # 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
247 return True
248
249 def listCompute(self):
250 """
251 Return a list of all running containers assigned to this
252 data center.
253 """
254 return list(self.containers.itervalues())
255
256 def getStatus(self):
257 """
258 Return a dict with status information about this DC.
259 """
260 return {
261 "label": self.label,
262 "internalname": self.name,
263 "switch": self.switch.name,
264 "n_running_containers": len(self.containers),
265 "metadata": self.metadata
266 }
267
268 def assignResourceModel(self, rm):
269 """
270 Assign a resource model to this DC.
271 :param rm: a BaseResourceModel object
272 :return:
273 """
274 if self._resource_model is not None:
275 raise Exception("There is already an resource model assigned to this DC.")
276 self._resource_model = rm
277 self.net.rm_registrar.register(self, rm)
278 LOG.info("Assigned RM: %r to DC: %r" % (rm, self))
279
280 @staticmethod
281 def _clean_ifname(name):
282 """
283 Cleans up given string to be a
284 RTNETLINK compatible interface name.
285 :param name: string
286 :return: string
287 """
288 if name is None:
289 return "if0"
290 name = name.replace(":", "-")
291 name = name.replace(" ", "-")
292 name = name.replace(".", "-")
293 name = name.replace("_", "-")
294 return name
295