blob: 7a12c68b377ddcfe0ef89848db9ffd2fe4036d87 [file] [log] [blame]
peusterm72f09882018-05-15 17:10:27 +02001# Copyright (c) 2015 SONATA-NFV and Paderborn University
2# ALL RIGHTS RESERVED.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16# Neither the name of the SONATA-NFV, Paderborn University
17# nor the names of its contributors may be used to endorse or promote
18# products derived from this software without specific prior written
19# permission.
20#
21# This work has been performed in the framework of the SONATA project,
22# funded by the European Commission under Grant number 671517 through
23# the Horizon 2020 and 5G-PPP programmes. The authors would like to
24# acknowledge the contributions of their colleagues of the SONATA
25# partner consortium (www.sonata-nfv.eu).
hadik3r237d3f52016-06-27 17:57:49 +020026import logging
stevenvanrossem73efd192016-06-29 01:44:07 +020027from flask_restful import Resource
28from flask import request
hadik3r237d3f52016-06-27 17:57:49 +020029import json
peusterm2aecf1d2017-11-29 12:02:42 +010030import threading
hadik3r237d3f52016-06-27 17:57:49 +020031
peusterm5b428742017-06-16 10:08:11 +020032logging.basicConfig()
hadik3r237d3f52016-06-27 17:57:49 +020033
peustermdfc14602017-01-13 08:22:45 +010034CORS_HEADER = {'Access-Control-Allow-Origin': '*'}
35
stevenvanrossemc63c5492017-05-08 16:10:13 +020036# the dcs dict is set in the rest_api_endpoint.py upon datacenter init
hadik3r237d3f52016-06-27 17:57:49 +020037dcs = {}
38
hadik3ra9dd9012016-08-09 10:51:13 +020039
40class Compute(Resource):
hadik3r237d3f52016-06-27 17:57:49 +020041 """
42 Start a new compute instance: A docker container (note: zerorpc does not support keyword arguments)
43 :param dc_label: name of the DC
44 :param compute_name: compute container name
45 :param image: image name
46 :param command: command to execute
47 :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 +020048 example networks list({"id":"input","ip": "10.0.0.254/8"}, {"id":"output","ip": "11.0.0.254/24"})
49 :return: docker inspect dict of deployed docker
hadik3r237d3f52016-06-27 17:57:49 +020050 """
stevenvanrossemc63c5492017-05-08 16:10:13 +020051
hadik3r237d3f52016-06-27 17:57:49 +020052 global dcs
53
stevenvanrossemb3f34172016-11-16 23:30:57 +010054 def put(self, dc_label, compute_name, resource=None, value=None):
hadik3ra9dd9012016-08-09 10:51:13 +020055
stevenvanrossemb3f34172016-11-16 23:30:57 +010056 # deploy new container
hadik3ra9dd9012016-08-09 10:51:13 +020057 # check if json data is a dict
58 data = request.json
59 if data is None:
60 data = {}
peusterm72f09882018-05-15 17:10:27 +020061 elif not isinstance(data, dict):
hadik3ra9dd9012016-08-09 10:51:13 +020062 data = json.loads(request.json)
63
64 network = data.get("network")
65 nw_list = self._parse_network(network)
66 image = data.get("image")
67 command = data.get("docker_command")
68
hadik3r237d3f52016-06-27 17:57:49 +020069 try:
peusterm2aecf1d2017-11-29 12:02:42 +010070 if compute_name is None or compute_name == "None":
71 logging.error("No compute name defined in request.")
72 return "No compute name defined in request.", 500, CORS_HEADER
73 if dc_label is None or dcs.get(dc_label) is None:
74 logging.error("No datacenter defined in request.")
75 return "No datacenter defined in request.", 500, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +020076 c = dcs.get(dc_label).startCompute(
hadik3ra9dd9012016-08-09 10:51:13 +020077 compute_name, image=image, command=command, network=nw_list)
peusterm2aecf1d2017-11-29 12:02:42 +010078 # (if available) trigger emu. entry point given in Dockerfile
79 try:
80 config = c.dcinfo.get("Config", dict())
81 env = config.get("Env", list())
schillinge3bb8b252019-03-14 22:46:14 +010082 legacy_command_execution = False
peusterm2aecf1d2017-11-29 12:02:42 +010083 for env_var in env:
84 var, cmd = map(str.strip, map(str, env_var.split('=', 1)))
peusterm72f09882018-05-15 17:10:27 +020085 logging.debug("%r = %r" % (var, cmd))
86 if var == "SON_EMU_CMD" or var == "VIM_EMU_CMD":
peusterm5b8ac4f2019-02-12 19:58:24 +010087 logging.info("Executing script in '{}': {}={}"
88 .format(compute_name, var, cmd))
peusterm72f09882018-05-15 17:10:27 +020089 # execute command in new thread to ensure that API is
90 # not blocked by VNF
peusterm2aecf1d2017-11-29 12:02:42 +010091 t = threading.Thread(target=c.cmdPrint, args=(cmd,))
92 t.daemon = True
93 t.start()
schillinge3bb8b252019-03-14 22:46:14 +010094 legacy_command_execution = True
95 break
96 if not legacy_command_execution:
97 c.start()
peusterm2aecf1d2017-11-29 12:02:42 +010098 except Exception as ex:
99 logging.warning("Couldn't run Docker entry point VIM_EMU_CMD")
100 logging.exception("Exception:")
hadik3r237d3f52016-06-27 17:57:49 +0200101 # return docker inspect dict
peustermdfc14602017-01-13 08:22:45 +0100102 return c.getStatus(), 200, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200103 except Exception as ex:
104 logging.exception("API error.")
peustermdfc14602017-01-13 08:22:45 +0100105 return ex.message, 500, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200106
hadik3ra9dd9012016-08-09 10:51:13 +0200107 def get(self, dc_label, compute_name):
108
109 logging.debug("API CALL: compute status")
110
111 try:
peusterm72f09882018-05-15 17:10:27 +0200112 return dcs.get(dc_label).containers.get(
113 compute_name).getStatus(), 200, CORS_HEADER
hadik3ra9dd9012016-08-09 10:51:13 +0200114 except Exception as ex:
115 logging.exception("API error.")
peustermdfc14602017-01-13 08:22:45 +0100116 return ex.message, 500, CORS_HEADER
hadik3ra9dd9012016-08-09 10:51:13 +0200117
118 def delete(self, dc_label, compute_name):
119 logging.debug("API CALL: compute stop")
120 try:
peusterm72f09882018-05-15 17:10:27 +0200121 return dcs.get(dc_label).stopCompute(
122 compute_name), 200, CORS_HEADER
hadik3ra9dd9012016-08-09 10:51:13 +0200123 except Exception as ex:
124 logging.exception("API error.")
peustermdfc14602017-01-13 08:22:45 +0100125 return ex.message, 500, CORS_HEADER
hadik3ra9dd9012016-08-09 10:51:13 +0200126
stevenvanrossemff6b4042016-07-14 20:51:37 +0200127 def _parse_network(self, network_str):
128 '''
129 parse the options for all network interfaces of the vnf
130 :param network_str: (id=x,ip=x.x.x.x/x), ...
131 :return: list of dicts [{"id":x,"ip":"x.x.x.x/x"}, ...]
132 '''
133 nw_list = list()
134
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200135 # TODO make this more robust with regex check
hadik3ra9dd9012016-08-09 10:51:13 +0200136 if network_str is None:
stevenvanrossemff6b4042016-07-14 20:51:37 +0200137 return nw_list
138
139 networks = network_str[1:-1].split('),(')
140 for nw in networks:
141 nw_dict = dict(tuple(e.split('=')) for e in nw.split(','))
142 nw_list.append(nw_dict)
143
144 return nw_list
145
hadik3r237d3f52016-06-27 17:57:49 +0200146
147class ComputeList(Resource):
hadik3r237d3f52016-06-27 17:57:49 +0200148 global dcs
149
stevenvanrossem65819b82016-08-05 18:21:47 +0200150 def get(self, dc_label=None):
hadik3r237d3f52016-06-27 17:57:49 +0200151 logging.debug("API CALL: compute list")
152 try:
stevenvanrossem34566442016-08-10 00:13:24 +0200153 if dc_label is None or dc_label == 'None':
hadik3r237d3f52016-06-27 17:57:49 +0200154 # return list with all compute nodes in all DCs
155 all_containers = []
stevenvanrossem17b6e882017-05-04 16:51:34 +0200156 all_extSAPs = []
hadik3r237d3f52016-06-27 17:57:49 +0200157 for dc in dcs.itervalues():
158 all_containers += dc.listCompute()
stevenvanrossem17b6e882017-05-04 16:51:34 +0200159 all_extSAPs += dc.listExtSAPs()
160
peusterm72f09882018-05-15 17:10:27 +0200161 extSAP_list = [(sap.name, sap.getStatus())
162 for sap in all_extSAPs]
163 container_list = [(c.name, c.getStatus())
164 for c in all_containers]
stevenvanrossem17b6e882017-05-04 16:51:34 +0200165 total_list = container_list + extSAP_list
166 return total_list, 200, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200167 else:
168 # return list of compute nodes for specified DC
peusterm72f09882018-05-15 17:10:27 +0200169 container_list = [(c.name, c.getStatus())
170 for c in dcs.get(dc_label).listCompute()]
171 extSAP_list = [(sap.name, sap.getStatus())
172 for sap in dcs.get(dc_label).listExtSAPs()]
stevenvanrossem17b6e882017-05-04 16:51:34 +0200173 total_list = container_list + extSAP_list
174 return total_list, 200, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200175 except Exception as ex:
176 logging.exception("API error.")
peustermdfc14602017-01-13 08:22:45 +0100177 return ex.message, 500, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200178
peusterm72f09882018-05-15 17:10:27 +0200179
stevenvanrossem7953f2f2017-02-10 12:56:15 +0100180class ComputeResources(Resource):
181 """
182 Update the container's resources using the docker.update function
183 re-using the same parameters:
184 url params:
185 blkio_weight
186 cpu_period, cpu_quota, cpu_shares
187 cpuset_cpus
188 cpuset_mems
189 mem_limit
190 mem_reservation
191 memswap_limit
192 kernel_memory
193 restart_policy
194 see https://docs.docker.com/engine/reference/commandline/update/
195 or API docs: https://docker-py.readthedocs.io/en/stable/api.html#module-docker.api.container
196 :param dc_label: name of the DC
197 :param compute_name: compute container name
198
199 :return: docker inspect dict of deployed docker
200 """
201 global dcs
202
203 def put(self, dc_label, compute_name):
204 logging.debug("REST CALL: update container resources")
205
206 try:
207 c = self._update_resources(dc_label, compute_name)
208 return c.getStatus(), 200, CORS_HEADER
209 except Exception as ex:
210 logging.exception("API error.")
211 return ex.message, 500, CORS_HEADER
212
213 def _update_resources(self, dc_label, compute_name):
214
215 # get URL parameters
216 params = request.args
217 # then no data
218 if params is None:
219 params = {}
peusterm72f09882018-05-15 17:10:27 +0200220 logging.debug(
221 "REST CALL: update container resources {0}".format(params))
222 # check if container exists
stevenvanrossem7953f2f2017-02-10 12:56:15 +0100223 d = dcs.get(dc_label).net.getNodeByName(compute_name)
224
225 # general request of cpu percentage
226 # create a mutable copy
227 params = params.to_dict()
stevenvanrossem33d76892017-02-13 00:13:37 +0100228 if 'cpu_bw' in params:
stevenvanrossem7953f2f2017-02-10 12:56:15 +0100229 cpu_period = int(dcs.get(dc_label).net.cpu_period)
stevenvanrossem33d76892017-02-13 00:13:37 +0100230 value = params.get('cpu_bw')
stevenvanrossem7953f2f2017-02-10 12:56:15 +0100231 cpu_quota = int(cpu_period * float(value))
peusterm72f09882018-05-15 17:10:27 +0200232 # put default values back
stevenvanrossem7953f2f2017-02-10 12:56:15 +0100233 if float(value) <= 0:
234 cpu_period = 100000
235 cpu_quota = -1
236 params['cpu_period'] = cpu_period
237 params['cpu_quota'] = cpu_quota
peusterm72f09882018-05-15 17:10:27 +0200238 # d.updateCpuLimit(cpu_period=cpu_period, cpu_quota=cpu_quota)
stevenvanrossem7953f2f2017-02-10 12:56:15 +0100239
240 # only pass allowed keys to docker
241 allowed_keys = ['blkio_weight', 'cpu_period', 'cpu_quota', 'cpu_shares', 'cpuset_cpus',
242 'cpuset_mems', 'mem_limit', 'mem_reservation', 'memswap_limit',
243 'kernel_memory', 'restart_policy']
peusterm72f09882018-05-15 17:10:27 +0200244 filtered_params = {key: params[key]
245 for key in allowed_keys if key in params}
stevenvanrossem7953f2f2017-02-10 12:56:15 +0100246
247 d.update_resources(**filtered_params)
248
249 return d
hadik3r237d3f52016-06-27 17:57:49 +0200250
peusterm72f09882018-05-15 17:10:27 +0200251
hadik3r237d3f52016-06-27 17:57:49 +0200252class DatacenterList(Resource):
hadik3r237d3f52016-06-27 17:57:49 +0200253 global dcs
254
255 def get(self):
256 logging.debug("API CALL: datacenter list")
257 try:
peustermdfc14602017-01-13 08:22:45 +0100258 return [d.getStatus() for d in dcs.itervalues()], 200, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200259 except Exception as ex:
260 logging.exception("API error.")
peustermdfc14602017-01-13 08:22:45 +0100261 return ex.message, 500, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200262
hadik3r237d3f52016-06-27 17:57:49 +0200263
hadik3ra9dd9012016-08-09 10:51:13 +0200264class DatacenterStatus(Resource):
hadik3r237d3f52016-06-27 17:57:49 +0200265 global dcs
266
267 def get(self, dc_label):
268 logging.debug("API CALL: datacenter status")
269 try:
peustermdfc14602017-01-13 08:22:45 +0100270 return dcs.get(dc_label).getStatus(), 200, CORS_HEADER
hadik3r237d3f52016-06-27 17:57:49 +0200271 except Exception as ex:
272 logging.exception("API error.")
peustermdfc14602017-01-13 08:22:45 +0100273 return ex.message, 500, CORS_HEADER