1 # Copyright (c) 2015 SONATA-NFV and Paderborn University
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
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
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).
27 from flask_restful
import Resource
28 from flask
import request
34 CORS_HEADER
= {'Access-Control-Allow-Origin': '*'}
36 # the dcs dict is set in the rest_api_endpoint.py upon datacenter init
40 class Compute(Resource
):
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),...
48 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
54 def put(self
, dc_label
, compute_name
, resource
=None, value
=None):
56 # deploy new container
57 # check if json data is a dict
61 elif not isinstance(data
, dict):
62 data
= json
.loads(request
.json
)
64 network
= data
.get("network")
65 nw_list
= self
._parse
_network
(network
)
66 image
= data
.get("image")
67 command
= data
.get("docker_command")
70 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
76 c
= dcs
.get(dc_label
).startCompute(
77 compute_name
, image
=image
, command
=command
, network
=nw_list
)
78 # (if available) trigger emu. entry point given in Dockerfile
80 config
= c
.dcinfo
.get("Config", dict())
81 env
= config
.get("Env", list())
82 legacy_command_execution
= False
84 var
, cmd
= map(str.strip
, map(str, env_var
.split('=', 1)))
85 logging
.debug("%r = %r" % (var
, cmd
))
86 if var
== "SON_EMU_CMD" or var
== "VIM_EMU_CMD":
87 logging
.info("Executing script in '{}': {}={}"
88 .format(compute_name
, var
, cmd
))
89 # execute command in new thread to ensure that API is
91 t
= threading
.Thread(target
=c
.cmdPrint
, args
=(cmd
,))
94 legacy_command_execution
= True
96 if not legacy_command_execution
:
98 except Exception as ex
:
99 logging
.warning("Couldn't run Docker entry point VIM_EMU_CMD")
100 logging
.exception("Exception:")
101 # return docker inspect dict
102 return c
.getStatus(), 200, CORS_HEADER
103 except Exception as ex
:
104 logging
.exception("API error.")
105 return ex
.message
, 500, CORS_HEADER
107 def get(self
, dc_label
, compute_name
):
109 logging
.debug("API CALL: compute status")
112 return dcs
.get(dc_label
).containers
.get(
113 compute_name
).getStatus(), 200, CORS_HEADER
114 except Exception as ex
:
115 logging
.exception("API error.")
116 return ex
.message
, 500, CORS_HEADER
118 def delete(self
, dc_label
, compute_name
):
119 logging
.debug("API CALL: compute stop")
121 return dcs
.get(dc_label
).stopCompute(
122 compute_name
), 200, CORS_HEADER
123 except Exception as ex
:
124 logging
.exception("API error.")
125 return ex
.message
, 500, CORS_HEADER
127 def _parse_network(self
, network_str
):
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"}, ...]
135 # TODO make this more robust with regex check
136 if network_str
is None:
139 networks
= network_str
[1:-1].split('),(')
141 nw_dict
= dict(tuple(e
.split('=')) for e
in nw
.split(','))
142 nw_list
.append(nw_dict
)
147 class ComputeList(Resource
):
150 def get(self
, dc_label
=None):
151 logging
.debug("API CALL: compute list")
153 if dc_label
is None or dc_label
== 'None':
154 # return list with all compute nodes in all DCs
156 for dc
in dcs
.itervalues():
157 all_containers
+= dc
.listCompute()
158 container_list
= [(c
.name
, c
.getStatus())
159 for c
in all_containers
]
160 return container_list
, 200, CORS_HEADER
162 # return list of compute nodes for specified DC
163 container_list
= [(c
.name
, c
.getStatus())
164 for c
in dcs
.get(dc_label
).listCompute()]
165 return container_list
, 200, CORS_HEADER
166 except Exception as ex
:
167 logging
.exception("API error.")
168 return ex
.message
, 500, CORS_HEADER
171 class ComputeResources(Resource
):
173 Update the container's resources using the docker.update function
174 re-using the same parameters:
177 cpu_period, cpu_quota, cpu_shares
185 see https://docs.docker.com/engine/reference/commandline/update/
186 or API docs: https://docker-py.readthedocs.io/en/stable/api.html#module-docker.api.container
187 :param dc_label: name of the DC
188 :param compute_name: compute container name
190 :return: docker inspect dict of deployed docker
194 def put(self
, dc_label
, compute_name
):
195 logging
.debug("REST CALL: update container resources")
198 c
= self
._update
_resources
(dc_label
, compute_name
)
199 return c
.getStatus(), 200, CORS_HEADER
200 except Exception as ex
:
201 logging
.exception("API error.")
202 return ex
.message
, 500, CORS_HEADER
204 def _update_resources(self
, dc_label
, compute_name
):
207 params
= request
.args
212 "REST CALL: update container resources {0}".format(params
))
213 # check if container exists
214 d
= dcs
.get(dc_label
).net
.getNodeByName(compute_name
)
216 # general request of cpu percentage
217 # create a mutable copy
218 params
= params
.to_dict()
219 if 'cpu_bw' in params
:
220 cpu_period
= int(dcs
.get(dc_label
).net
.cpu_period
)
221 value
= params
.get('cpu_bw')
222 cpu_quota
= int(cpu_period
* float(value
))
223 # put default values back
224 if float(value
) <= 0:
227 params
['cpu_period'] = cpu_period
228 params
['cpu_quota'] = cpu_quota
229 # d.updateCpuLimit(cpu_period=cpu_period, cpu_quota=cpu_quota)
231 # only pass allowed keys to docker
232 allowed_keys
= ['blkio_weight', 'cpu_period', 'cpu_quota', 'cpu_shares', 'cpuset_cpus',
233 'cpuset_mems', 'mem_limit', 'mem_reservation', 'memswap_limit',
234 'kernel_memory', 'restart_policy']
235 filtered_params
= {key
: params
[key
]
236 for key
in allowed_keys
if key
in params
}
238 d
.update_resources(**filtered_params
)
243 class DatacenterList(Resource
):
247 logging
.debug("API CALL: datacenter list")
249 return [d
.getStatus() for d
in dcs
.itervalues()], 200, CORS_HEADER
250 except Exception as ex
:
251 logging
.exception("API error.")
252 return ex
.message
, 500, CORS_HEADER
255 class DatacenterStatus(Resource
):
258 def get(self
, dc_label
):
259 logging
.debug("API CALL: datacenter status")
261 return dcs
.get(dc_label
).getStatus(), 200, CORS_HEADER
262 except Exception as ex
:
263 logging
.exception("API error.")
264 return ex
.message
, 500, CORS_HEADER