Feature 10296 Pip Standardization
[osm/osmclient.git] / osmclient / sol005 / vnfd.py
1 # Copyright 2018 Telefonica
2 #
3 # All Rights Reserved.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 # not use this file except in compliance with the License. You may obtain
7 # 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, WITHOUT
13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 # License for the specific language governing permissions and limitations
15 # under the License.
16
17 """
18 OSM vnfd API handling
19 """
20
21 from osmclient.common.exceptions import NotFound
22 from osmclient.common.exceptions import ClientException
23 from osmclient.common import utils
24 import json
25 import yaml
26 import magic
27 from os.path import basename
28 import logging
29 import os.path
30 from urllib.parse import quote
31 import tarfile
32 from osm_im.validation import Validation as validation_im
33
34
35 class Vnfd(object):
36 def __init__(self, http=None, client=None):
37 self._http = http
38 self._client = client
39 self._logger = logging.getLogger("osmclient")
40 self._apiName = "/vnfpkgm"
41 self._apiVersion = "/v1"
42 self._apiResource = "/vnf_packages"
43 self._apiBase = "{}{}{}".format(
44 self._apiName, self._apiVersion, self._apiResource
45 )
46
47 def list(self, filter=None):
48 self._logger.debug("")
49 self._client.get_token()
50 filter_string = ""
51 if filter:
52 filter_string = "?{}".format(filter)
53 _, resp = self._http.get2_cmd("{}{}".format(self._apiBase, filter_string))
54
55 if resp:
56 return json.loads(resp)
57 return list()
58
59 def get(self, name):
60 self._logger.debug("")
61 self._client.get_token()
62 if utils.validate_uuid4(name):
63 for vnfd in self.list():
64 if name == vnfd["_id"]:
65 return vnfd
66 else:
67 for vnfd in self.list():
68 if "product-name" in vnfd and name == vnfd["product-name"]:
69 return vnfd
70 elif "name" in vnfd and name == vnfd["name"]:
71 return vnfd
72 raise NotFound("vnfd {} not found".format(name))
73
74 def get_individual(self, name):
75 self._logger.debug("")
76 vnfd = self.get(name)
77 # It is redundant, since the previous one already gets the whole vnfpkginfo
78 # The only difference is that a different primitive is exercised
79 try:
80 _, resp = self._http.get2_cmd("{}/{}".format(self._apiBase, vnfd["_id"]))
81 if resp:
82 return json.loads(resp)
83 except NotFound:
84 raise NotFound("vnfd '{}' not found".format(name))
85 raise NotFound("vnfd '{}' not found".format(name))
86
87 def get_thing(self, name, thing, filename):
88 self._logger.debug("")
89 vnfd = self.get(name)
90 headers = self._client._headers
91 headers["Accept"] = "application/binary"
92 http_code, resp = self._http.get2_cmd(
93 "{}/{}/{}".format(self._apiBase, vnfd["_id"], thing)
94 )
95
96 if resp:
97 return json.loads(resp)
98
99 def get_descriptor(self, name, filename):
100 self._logger.debug("")
101 self.get_thing(name, "vnfd", filename)
102
103 def get_package(self, name, filename):
104 self._logger.debug("")
105 self.get_thing(name, "package_content", filename)
106
107 def get_artifact(self, name, artifact, filename):
108 self._logger.debug("")
109 self.get_thing(name, "artifacts/{}".format(artifact), filename)
110
111 def delete(self, name, force=False):
112 self._logger.debug("")
113 self._client.get_token()
114 vnfd = self.get(name)
115 querystring = ""
116 if force:
117 querystring = "?FORCE=True"
118 http_code, resp = self._http.delete_cmd(
119 "{}/{}{}".format(self._apiBase, vnfd["_id"], querystring)
120 )
121
122 if http_code == 202:
123 print("Deletion in progress")
124 elif http_code == 204:
125 print("Deleted")
126 else:
127 msg = resp or ""
128 raise ClientException("failed to delete vnfd {} - {}".format(name, msg))
129
130 def create(
131 self,
132 filename,
133 overwrite=None,
134 update_endpoint=None,
135 skip_charm_build=False,
136 override_epa=False,
137 override_nonepa=False,
138 override_paravirt=False,
139 ):
140 self._logger.debug("")
141 if os.path.isdir(filename):
142 filename = filename.rstrip("/")
143 filename = self._client.package_tool.build(
144 filename, skip_validation=False, skip_charm_build=skip_charm_build
145 )
146
147 print("Uploading package {}".format(filename))
148 self.create(
149 filename,
150 overwrite=overwrite,
151 update_endpoint=update_endpoint,
152 override_epa=override_epa,
153 override_nonepa=override_nonepa,
154 override_paravirt=override_paravirt,
155 )
156 else:
157 self._client.get_token()
158 mime_type = magic.from_file(filename, mime=True)
159 if mime_type is None:
160 raise ClientException(
161 "Unexpected MIME type for file {}: MIME type {}".format(
162 filename, mime_type
163 )
164 )
165 headers = self._client._headers
166 headers["Content-Filename"] = basename(filename)
167 if mime_type in ["application/yaml", "text/plain", "application/json"]:
168 headers["Content-Type"] = "text/plain"
169 elif mime_type in ["application/gzip", "application/x-gzip"]:
170 headers["Content-Type"] = "application/gzip"
171 else:
172 raise ClientException(
173 "Unexpected MIME type for file {}: MIME type {}".format(
174 filename, mime_type
175 )
176 )
177
178 special_override_string = ""
179 if override_epa or override_nonepa or override_paravirt:
180 # If override for EPA, non-EPA or paravirt is required, get the descriptor data
181 descriptor_data = None
182 if mime_type in ["application/yaml", "text/plain", "application/json"]:
183 with open(filename) as df:
184 descriptor_data = df.read()
185 elif mime_type in ["application/gzip", "application/x-gzip"]:
186 tar_object = tarfile.open(filename, "r:gz")
187 descriptor_list = []
188 for member in tar_object:
189 if member.isreg():
190 if "/" not in os.path.dirname(
191 member.name
192 ) and member.name.endswith(".yaml"):
193 descriptor_list.append(member.name)
194 if len(descriptor_list) > 1:
195 raise ClientException(
196 "Found more than one potential descriptor in the tar.gz file"
197 )
198 elif len(descriptor_list) == 0:
199 raise ClientException(
200 "No descriptor was found in the tar.gz file"
201 )
202 with tar_object.extractfile(descriptor_list[0]) as df:
203 descriptor_data = df.read()
204 tar_object.close()
205 if not descriptor_data:
206 raise ClientException("Descriptor could not be read")
207 desc_type, vnfd = validation_im().yaml_validation(descriptor_data)
208 validation_im().pyangbind_validation(desc_type, vnfd)
209
210 vnfd = yaml.safe_load(descriptor_data)
211 vcd_list = []
212 vdu_list = []
213 for k in vnfd:
214 # Get only the first descriptor in case there are many in the yaml file
215 # k can be vnfd or etsi-nfv-vnfd:vnfd. This check is skipped
216 first_vnfd = vnfd.get(k, {})
217 vcd_list = first_vnfd.get("virtual-compute-desc", [])
218 vdu_list = first_vnfd.get("vdu", [])
219 break
220
221 for vcd_number, vcd in enumerate(vcd_list):
222 if override_epa:
223 virtual_memory = vcd["virtual-memory"]
224 virtual_memory["mempage-size"] = "LARGE"
225 virtual_memory["numa-enabled"] = True
226 virtual_memory["numa-node-policy"] = {
227 "node-cnt": 1,
228 "mem-policy": "STRICT",
229 }
230 virtual_cpu = vcd["virtual-cpu"]
231 virtual_cpu["pinning"] = {
232 "policy": "static",
233 "thread-policy": "PREFER",
234 }
235
236 cpu_override_string = (
237 "virtual-compute-desc.{}.virtual-cpu={};".format(
238 vcd_number, quote(yaml.safe_dump(virtual_cpu))
239 )
240 )
241 memory_override_string = (
242 "virtual-compute-desc.{}.virtual-memory={};".format(
243 vcd_number, quote(yaml.safe_dump(virtual_memory))
244 )
245 )
246 special_override_string = "{}{}{}".format(
247 special_override_string,
248 cpu_override_string,
249 memory_override_string,
250 )
251
252 headers["Query-String-Format"] = "yaml"
253 if override_nonepa:
254 virtual_memory = vcd["virtual-memory"]
255 virtual_memory["mempage-size"] = ""
256 virtual_memory["numa-enabled"] = ""
257 virtual_memory["numa-node-policy"] = {}
258 virtual_cpu = vcd["virtual-cpu"]
259 virtual_cpu["pinning"] = {}
260
261 cpu_override_string = (
262 "virtual-compute-desc.{}.virtual-cpu={};".format(
263 vcd_number, quote(yaml.safe_dump(virtual_cpu))
264 )
265 )
266 memory_override_string = (
267 "virtual-compute-desc.{}.virtual-memory={};".format(
268 vcd_number, quote(yaml.safe_dump(virtual_memory))
269 )
270 )
271 special_override_string = "{}{}{}".format(
272 special_override_string,
273 cpu_override_string,
274 memory_override_string,
275 )
276
277 if override_paravirt:
278 for vdu_number, vdu in enumerate(vdu_list):
279 for cpd_number, cpd in enumerate(vdu["int-cpd"]):
280 for vnir_number, vnir in enumerate(
281 cpd["virtual-network-interface-requirement"]
282 ):
283 special_override_string = (
284 "{}vdu.{}.int-cpd.{}.virtual-network-interface-"
285 "requirement.{}.virtual-interface.type="
286 "PARAVIRT;".format(
287 special_override_string,
288 vdu_number,
289 cpd_number,
290 vnir_number,
291 )
292 )
293
294 special_override_string = special_override_string.rstrip(";")
295
296 headers["Content-File-MD5"] = utils.md5(filename)
297 http_header = [
298 "{}: {}".format(key, val) for (key, val) in list(headers.items())
299 ]
300
301 self._http.set_http_header(http_header)
302 if update_endpoint:
303 http_code, resp = self._http.put_cmd(
304 endpoint=update_endpoint, filename=filename
305 )
306 else:
307 ow_string = ""
308 if special_override_string:
309 if overwrite:
310 overwrite = "{};{}".format(overwrite, special_override_string)
311 else:
312 overwrite = special_override_string
313
314 if overwrite:
315 ow_string = "?{}".format(overwrite)
316 self._apiResource = "/vnf_packages_content"
317 self._apiBase = "{}{}{}".format(
318 self._apiName, self._apiVersion, self._apiResource
319 )
320 endpoint = "{}{}".format(self._apiBase, ow_string)
321 http_code, resp = self._http.post_cmd(
322 endpoint=endpoint, filename=filename
323 )
324
325 if http_code in (200, 201, 202):
326 if resp:
327 resp = json.loads(resp)
328 if not resp or "id" not in resp:
329 raise ClientException(
330 "unexpected response from server: {}".format(resp)
331 )
332 print(resp["id"])
333 elif http_code == 204:
334 print("Updated")
335
336 def update(self, name, filename):
337 self._logger.debug("")
338 self._client.get_token()
339 vnfd = self.get(name)
340 endpoint = "{}/{}/package_content".format(self._apiBase, vnfd["_id"])
341 self.create(filename=filename, update_endpoint=endpoint)