blob: 78d96da2ece7607796ffa70176f7060b2490ebcd [file] [log] [blame]
peusterm79ef6ae2016-07-08 13:53:57 +02001"""
2Copyright (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
peustermd7cbd212017-09-07 08:55:14 +020017Neither the name of the SONATA-NFV, Paderborn University
peusterm79ef6ae2016-07-08 13:53:57 +020018nor 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).
27"""
hadik3r237d3f52016-06-27 17:57:49 +020028import logging
stevenvanrossem73efd192016-06-29 01:44:07 +020029from flask_restful import Resource
30from flask import request
hadik3r237d3f52016-06-27 17:57:49 +020031import json
peusterm2aecf1d2017-11-29 12:02:42 +010032import threading
stevenvanrossem7953f2f2017-02-10 12:56:15 +010033from copy import deepcopy
hadik3r237d3f52016-06-27 17:57:49 +020034
peusterm5b428742017-06-16 10:08:11 +020035logging.basicConfig()
hadik3r237d3f52016-06-27 17:57:49 +020036
peustermdfc14602017-01-13 08:22:45 +010037CORS_HEADER = {'Access-Control-Allow-Origin': '*'}
38
stevenvanrossemc63c5492017-05-08 16:10:13 +020039# the dcs dict is set in the rest_api_endpoint.py upon datacenter init
hadik3r237d3f52016-06-27 17:57:49 +020040dcs = {}
41
hadik3ra9dd9012016-08-09 10:51:13 +020042
43class Compute(Resource):
hadik3r237d3f52016-06-27 17:57:49 +020044 """
45 Start a new compute instance: A docker container (note: zerorpc does not support keyword arguments)
46 :param dc_label: name of the DC
47 :param compute_name: compute container name
48 :param image: image name
49 :param command: command to execute
50 :param network: list of all interface of the vnf, with their parameters (id=id1,ip=x.x.x.x/x),...
stevenvanrossem73efd192016-06-29 01:44:07 +020051 example networks list({"id":"input","ip": "10.0.0.254/8"}, {"id":"output","ip": "11.0.0.254/24"})
52 :return: docker inspect dict of deployed docker
hadik3r237d3f52016-06-27 17:57:49 +020053 """
stevenvanrossemc63c5492017-05-08 16:10:13 +020054
hadik3r237d3f52016-06-27 17:57:49 +020055 global dcs
56
stevenvanrossemb3f34172016-11-16 23:30:57 +010057 def put(self, dc_label, compute_name, resource=None, value=None):
hadik3ra9dd9012016-08-09 10:51:13 +020058
stevenvanrossemb3f34172016-11-16 23:30:57 +010059 # deploy new container
hadik3ra9dd9012016-08-09 10:51:13 +020060 # check if json data is a dict
61 data = request.json
62 if data is None:
63 data = {}
64 elif type(data) is not dict:
65 data = json.loads(request.json)
66
67 network = data.get("network")
68 nw_list = self._parse_network(network)
69 image = data.get("image")
70 command = data.get("docker_command")
71
hadik3r237d3f52016-06-27 17:57:49 +020072 try:
peusterm2aecf1d2017-11-29 12:02:42 +010073 if compute_name is None or compute_name == "None":
74 logging.error("No compute name defined in request.")
75 return "No compute name defined in request.", 500, CORS_HEADER
76 if dc_label is None or dcs.get(dc_label) is None:
77 logging.error("No datacenter defined in request.")
78 return "No datacenter defined in request.", 500, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +020079 c = dcs.get(dc_label).startCompute(
hadik3ra9dd9012016-08-09 10:51:13 +020080 compute_name, image=image, command=command, network=nw_list)
peusterm2aecf1d2017-11-29 12:02:42 +010081 # (if available) trigger emu. entry point given in Dockerfile
82 try:
83 config = c.dcinfo.get("Config", dict())
84 env = config.get("Env", list())
85 for env_var in env:
86 var, cmd = map(str.strip, map(str, env_var.split('=', 1)))
87 logging.debug("%r = %r" % (var , cmd))
88 if var=="SON_EMU_CMD" or var=="VIM_EMU_CMD":
89 logging.info("Executing entry point script in %r: %r" % (c.name, cmd))
90 # execute command in new thread to ensure that API is not blocked by VNF
91 t = threading.Thread(target=c.cmdPrint, args=(cmd,))
92 t.daemon = True
93 t.start()
94 except Exception as ex:
95 logging.warning("Couldn't run Docker entry point VIM_EMU_CMD")
96 logging.exception("Exception:")
hadik3r237d3f52016-06-27 17:57:49 +020097 # return docker inspect dict
peustermdfc14602017-01-13 08:22:45 +010098 return c.getStatus(), 200, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +020099 except Exception as ex:
100 logging.exception("API error.")
peustermdfc14602017-01-13 08:22:45 +0100101 return ex.message, 500, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200102
hadik3ra9dd9012016-08-09 10:51:13 +0200103 def get(self, dc_label, compute_name):
104
105 logging.debug("API CALL: compute status")
106
107 try:
peustermdfc14602017-01-13 08:22:45 +0100108 return dcs.get(dc_label).containers.get(compute_name).getStatus(), 200, CORS_HEADER
hadik3ra9dd9012016-08-09 10:51:13 +0200109 except Exception as ex:
110 logging.exception("API error.")
peustermdfc14602017-01-13 08:22:45 +0100111 return ex.message, 500, CORS_HEADER
hadik3ra9dd9012016-08-09 10:51:13 +0200112
113 def delete(self, dc_label, compute_name):
114 logging.debug("API CALL: compute stop")
115 try:
peustermdfc14602017-01-13 08:22:45 +0100116 return dcs.get(dc_label).stopCompute(compute_name), 200, CORS_HEADER
hadik3ra9dd9012016-08-09 10:51:13 +0200117 except Exception as ex:
118 logging.exception("API error.")
peustermdfc14602017-01-13 08:22:45 +0100119 return ex.message, 500, CORS_HEADER
hadik3ra9dd9012016-08-09 10:51:13 +0200120
stevenvanrossemff6b4042016-07-14 20:51:37 +0200121 def _parse_network(self, network_str):
122 '''
123 parse the options for all network interfaces of the vnf
124 :param network_str: (id=x,ip=x.x.x.x/x), ...
125 :return: list of dicts [{"id":x,"ip":"x.x.x.x/x"}, ...]
126 '''
127 nw_list = list()
128
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200129 # TODO make this more robust with regex check
hadik3ra9dd9012016-08-09 10:51:13 +0200130 if network_str is None:
stevenvanrossemff6b4042016-07-14 20:51:37 +0200131 return nw_list
132
133 networks = network_str[1:-1].split('),(')
134 for nw in networks:
135 nw_dict = dict(tuple(e.split('=')) for e in nw.split(','))
136 nw_list.append(nw_dict)
137
138 return nw_list
139
hadik3r237d3f52016-06-27 17:57:49 +0200140
141class ComputeList(Resource):
hadik3r237d3f52016-06-27 17:57:49 +0200142 global dcs
143
stevenvanrossem65819b82016-08-05 18:21:47 +0200144 def get(self, dc_label=None):
hadik3r237d3f52016-06-27 17:57:49 +0200145 logging.debug("API CALL: compute list")
146 try:
stevenvanrossem34566442016-08-10 00:13:24 +0200147 if dc_label is None or dc_label == 'None':
hadik3r237d3f52016-06-27 17:57:49 +0200148 # return list with all compute nodes in all DCs
149 all_containers = []
stevenvanrossem17b6e882017-05-04 16:51:34 +0200150 all_extSAPs = []
hadik3r237d3f52016-06-27 17:57:49 +0200151 for dc in dcs.itervalues():
152 all_containers += dc.listCompute()
stevenvanrossem17b6e882017-05-04 16:51:34 +0200153 all_extSAPs += dc.listExtSAPs()
154
155 extSAP_list = [(sap.name, sap.getStatus()) for sap in all_extSAPs]
156 container_list = [(c.name, c.getStatus()) for c in all_containers]
157 total_list = container_list + extSAP_list
158 return total_list, 200, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200159 else:
160 # return list of compute nodes for specified DC
stevenvanrossem17b6e882017-05-04 16:51:34 +0200161 container_list = [(c.name, c.getStatus()) for c in dcs.get(dc_label).listCompute()]
162 extSAP_list = [(sap.name, sap.getStatus()) for sap in dcs.get(dc_label).listExtSAPs()]
163 total_list = container_list + extSAP_list
164 return total_list, 200, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200165 except Exception as ex:
166 logging.exception("API error.")
peustermdfc14602017-01-13 08:22:45 +0100167 return ex.message, 500, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200168
stevenvanrossem7953f2f2017-02-10 12:56:15 +0100169class ComputeResources(Resource):
170 """
171 Update the container's resources using the docker.update function
172 re-using the same parameters:
173 url params:
174 blkio_weight
175 cpu_period, cpu_quota, cpu_shares
176 cpuset_cpus
177 cpuset_mems
178 mem_limit
179 mem_reservation
180 memswap_limit
181 kernel_memory
182 restart_policy
183 see https://docs.docker.com/engine/reference/commandline/update/
184 or API docs: https://docker-py.readthedocs.io/en/stable/api.html#module-docker.api.container
185 :param dc_label: name of the DC
186 :param compute_name: compute container name
187
188 :return: docker inspect dict of deployed docker
189 """
190 global dcs
191
192 def put(self, dc_label, compute_name):
193 logging.debug("REST CALL: update container resources")
194
195 try:
196 c = self._update_resources(dc_label, compute_name)
197 return c.getStatus(), 200, CORS_HEADER
198 except Exception as ex:
199 logging.exception("API error.")
200 return ex.message, 500, CORS_HEADER
201
202 def _update_resources(self, dc_label, compute_name):
203
204 # get URL parameters
205 params = request.args
206 # then no data
207 if params is None:
208 params = {}
stevenvanrossem33d76892017-02-13 00:13:37 +0100209 logging.debug("REST CALL: update container resources {0}".format(params))
stevenvanrossem7953f2f2017-02-10 12:56:15 +0100210 #check if container exists
211 d = dcs.get(dc_label).net.getNodeByName(compute_name)
212
213 # general request of cpu percentage
214 # create a mutable copy
215 params = params.to_dict()
stevenvanrossem33d76892017-02-13 00:13:37 +0100216 if 'cpu_bw' in params:
stevenvanrossem7953f2f2017-02-10 12:56:15 +0100217 cpu_period = int(dcs.get(dc_label).net.cpu_period)
stevenvanrossem33d76892017-02-13 00:13:37 +0100218 value = params.get('cpu_bw')
stevenvanrossem7953f2f2017-02-10 12:56:15 +0100219 cpu_quota = int(cpu_period * float(value))
220 #put default values back
221 if float(value) <= 0:
222 cpu_period = 100000
223 cpu_quota = -1
224 params['cpu_period'] = cpu_period
225 params['cpu_quota'] = cpu_quota
226 #d.updateCpuLimit(cpu_period=cpu_period, cpu_quota=cpu_quota)
227
228 # only pass allowed keys to docker
229 allowed_keys = ['blkio_weight', 'cpu_period', 'cpu_quota', 'cpu_shares', 'cpuset_cpus',
230 'cpuset_mems', 'mem_limit', 'mem_reservation', 'memswap_limit',
231 'kernel_memory', 'restart_policy']
232 filtered_params = {key:params[key] for key in allowed_keys if key in params}
233
234 d.update_resources(**filtered_params)
235
236 return d
hadik3r237d3f52016-06-27 17:57:49 +0200237
hadik3r237d3f52016-06-27 17:57:49 +0200238class DatacenterList(Resource):
hadik3r237d3f52016-06-27 17:57:49 +0200239 global dcs
240
241 def get(self):
242 logging.debug("API CALL: datacenter list")
243 try:
peustermdfc14602017-01-13 08:22:45 +0100244 return [d.getStatus() for d in dcs.itervalues()], 200, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200245 except Exception as ex:
246 logging.exception("API error.")
peustermdfc14602017-01-13 08:22:45 +0100247 return ex.message, 500, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200248
hadik3r237d3f52016-06-27 17:57:49 +0200249
hadik3ra9dd9012016-08-09 10:51:13 +0200250class DatacenterStatus(Resource):
hadik3r237d3f52016-06-27 17:57:49 +0200251 global dcs
252
253 def get(self, dc_label):
254 logging.debug("API CALL: datacenter status")
255 try:
peustermdfc14602017-01-13 08:22:45 +0100256 return dcs.get(dc_label).getStatus(), 200, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200257 except Exception as ex:
258 logging.exception("API error.")
peustermdfc14602017-01-13 08:22:45 +0100259 return ex.message, 500, CORS_HEADER