1 # Copyright 2018 Telefonica
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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
21 from osmclient
.common
.exceptions
import NotFound
22 from osmclient
.common
.exceptions
import ClientException
23 from osmclient
.common
import utils
27 from os
.path
import basename
30 from urllib
.parse
import quote
32 from osm_im
.validation
import Validation
as validation_im
33 from zipfile
import ZipFile
37 def __init__(self
, http
=None, client
=None):
40 self
._logger
= logging
.getLogger("osmclient")
41 self
._apiName
= "/vnfpkgm"
42 self
._apiVersion
= "/v1"
43 self
._apiResource
= "/vnf_packages"
44 self
._apiBase
= "{}{}{}".format(
45 self
._apiName
, self
._apiVersion
, self
._apiResource
48 def list(self
, filter=None):
49 self
._logger
.debug("")
50 self
._client
.get_token()
53 filter_string
= "?{}".format(filter)
54 _
, resp
= self
._http
.get2_cmd("{}{}".format(self
._apiBase
, filter_string
))
57 return json
.loads(resp
)
61 self
._logger
.debug("")
62 self
._client
.get_token()
63 if utils
.validate_uuid4(name
):
64 for vnfd
in self
.list():
65 if name
== vnfd
["_id"]:
68 for vnfd
in self
.list():
69 if "product-name" in vnfd
and name
== vnfd
["product-name"]:
71 elif "name" in vnfd
and name
== vnfd
["name"]:
73 raise NotFound("vnfd {} not found".format(name
))
75 def get_individual(self
, name
):
76 self
._logger
.debug("")
78 # It is redundant, since the previous one already gets the whole vnfpkginfo
79 # The only difference is that a different primitive is exercised
81 _
, resp
= self
._http
.get2_cmd("{}/{}".format(self
._apiBase
, vnfd
["_id"]))
83 return json
.loads(resp
)
85 raise NotFound("vnfd '{}' not found".format(name
))
86 raise NotFound("vnfd '{}' not found".format(name
))
88 def get_thing(self
, name
, thing
, filename
):
89 self
._logger
.debug("")
91 headers
= self
._client
._headers
92 headers
["Accept"] = "application/binary"
93 http_code
, resp
= self
._http
.get2_cmd(
94 "{}/{}/{}".format(self
._apiBase
, vnfd
["_id"], thing
)
98 return json
.loads(resp
)
100 def get_descriptor(self
, name
, filename
):
101 self
._logger
.debug("")
102 self
.get_thing(name
, "vnfd", filename
)
104 def get_package(self
, name
, filename
):
105 self
._logger
.debug("")
106 self
.get_thing(name
, "package_content", filename
)
108 def get_artifact(self
, name
, artifact
, filename
):
109 self
._logger
.debug("")
110 self
.get_thing(name
, "artifacts/{}".format(artifact
), filename
)
112 def delete(self
, name
, force
=False):
113 self
._logger
.debug("")
114 self
._client
.get_token()
115 vnfd
= self
.get(name
)
118 querystring
= "?FORCE=True"
119 http_code
, resp
= self
._http
.delete_cmd(
120 "{}/{}{}".format(self
._apiBase
, vnfd
["_id"], querystring
)
124 print("Deletion in progress")
125 elif http_code
== 204:
129 raise ClientException("failed to delete vnfd {} - {}".format(name
, msg
))
135 update_endpoint
=None,
136 skip_charm_build
=False,
138 override_nonepa
=False,
139 override_paravirt
=False,
141 self
._logger
.debug("")
142 if os
.path
.isdir(filename
):
143 filename
= filename
.rstrip("/")
144 filename
= self
._client
.package_tool
.build(
145 filename
, skip_validation
=False, skip_charm_build
=skip_charm_build
148 print("Uploading package {}".format(filename
))
152 update_endpoint
=update_endpoint
,
153 override_epa
=override_epa
,
154 override_nonepa
=override_nonepa
,
155 override_paravirt
=override_paravirt
,
158 self
._client
.get_token()
159 mime_type
= magic
.from_file(filename
, mime
=True)
160 if mime_type
is None:
161 raise ClientException(
162 "Unexpected MIME type for file {}: MIME type {}".format(
166 headers
= self
._client
._headers
167 headers
["Content-Filename"] = basename(filename
)
168 if mime_type
in ["application/yaml", "text/plain", "application/json"]:
169 headers
["Content-Type"] = "text/plain"
170 elif mime_type
in ["application/gzip", "application/x-gzip"]:
171 headers
["Content-Type"] = "application/gzip"
172 elif mime_type
in ["application/zip"]:
173 headers
["Content-Type"] = "application/zip"
175 raise ClientException(
176 "Unexpected MIME type for file {}: MIME type {}".format(
181 special_override_string
= ""
182 if override_epa
or override_nonepa
or override_paravirt
:
183 # If override for EPA, non-EPA or paravirt is required, get the descriptor data
184 descriptor_data
= None
185 if mime_type
in ["application/yaml", "text/plain", "application/json"]:
186 with
open(filename
) as df
:
187 descriptor_data
= df
.read()
188 elif mime_type
in ["application/gzip", "application/x-gzip"]:
189 tar_object
= tarfile
.open(filename
, "r:gz")
191 for member
in tar_object
:
193 if "/" not in os
.path
.dirname(
195 ) and member
.name
.endswith(".yaml"):
196 descriptor_list
.append(member
.name
)
197 if len(descriptor_list
) > 1:
198 raise ClientException(
199 "Found more than one potential descriptor in the tar.gz file"
201 elif len(descriptor_list
) == 0:
202 raise ClientException(
203 "No descriptor was found in the tar.gz file"
205 with tar_object
.extractfile(descriptor_list
[0]) as df
:
206 descriptor_data
= df
.read()
208 elif mime_type
in ["application/zip"]:
209 package_zip
= ZipFile(filename
)
210 package_files
= package_zip
.infolist()
214 "Definitions" in package_files
215 and "TOSCA-Metadata" in package_files
219 for definition
in package_files
220 if "Definitions/" in definition
222 definition
.endswith(".yaml")
223 or definition
.endswith(".yml")
229 for definition
in package_files
230 if definition
.endswith(".yaml")
231 or definition
.endswith(".yml")
233 if len(descriptors
) < 1:
234 raise ClientException(
235 "No descriptor found on this package, OSM was expecting at least 1"
237 descriptor_data
= package_zip
.open(descriptors
[0])
240 if not descriptor_data
:
241 raise ClientException("Descriptor could not be read")
242 desc_type
, vnfd
= validation_im().yaml_validation(descriptor_data
)
243 validation_im().pyangbind_validation(desc_type
, vnfd
)
245 vnfd
= yaml
.safe_load(descriptor_data
)
249 # Get only the first descriptor in case there are many in the yaml file
250 # k can be vnfd or etsi-nfv-vnfd:vnfd. This check is skipped
251 first_vnfd
= vnfd
.get(k
, {})
252 vcd_list
= first_vnfd
.get("virtual-compute-desc", [])
253 vdu_list
= first_vnfd
.get("vdu", [])
256 for vcd_number
, vcd
in enumerate(vcd_list
):
258 virtual_memory
= vcd
["virtual-memory"]
259 virtual_memory
["mempage-size"] = "LARGE"
260 virtual_memory
["numa-enabled"] = True
261 virtual_memory
["numa-node-policy"] = {
263 "mem-policy": "STRICT",
265 virtual_cpu
= vcd
["virtual-cpu"]
266 virtual_cpu
["pinning"] = {
268 "thread-policy": "PREFER",
271 cpu_override_string
= (
272 "virtual-compute-desc.{}.virtual-cpu={};".format(
273 vcd_number
, quote(yaml
.safe_dump(virtual_cpu
))
276 memory_override_string
= (
277 "virtual-compute-desc.{}.virtual-memory={};".format(
278 vcd_number
, quote(yaml
.safe_dump(virtual_memory
))
281 special_override_string
= "{}{}{}".format(
282 special_override_string
,
284 memory_override_string
,
287 headers
["Query-String-Format"] = "yaml"
289 virtual_memory
= vcd
["virtual-memory"]
290 virtual_memory
["mempage-size"] = ""
291 virtual_memory
["numa-enabled"] = ""
292 virtual_memory
["numa-node-policy"] = {}
293 virtual_cpu
= vcd
["virtual-cpu"]
294 virtual_cpu
["pinning"] = {}
296 cpu_override_string
= (
297 "virtual-compute-desc.{}.virtual-cpu={};".format(
298 vcd_number
, quote(yaml
.safe_dump(virtual_cpu
))
301 memory_override_string
= (
302 "virtual-compute-desc.{}.virtual-memory={};".format(
303 vcd_number
, quote(yaml
.safe_dump(virtual_memory
))
306 special_override_string
= "{}{}{}".format(
307 special_override_string
,
309 memory_override_string
,
312 if override_paravirt
:
313 for vdu_number
, vdu
in enumerate(vdu_list
):
314 for cpd_number
, cpd
in enumerate(vdu
["int-cpd"]):
315 for vnir_number
, vnir
in enumerate(
316 cpd
["virtual-network-interface-requirement"]
318 special_override_string
= (
319 "{}vdu.{}.int-cpd.{}.virtual-network-interface-"
320 "requirement.{}.virtual-interface.type="
322 special_override_string
,
329 special_override_string
= special_override_string
.rstrip(";")
331 headers
["Content-File-MD5"] = utils
.md5(filename
)
332 self
._http
.set_http_header(headers
)
334 http_code
, resp
= self
._http
.put_cmd(
335 endpoint
=update_endpoint
, filename
=filename
339 if special_override_string
:
341 overwrite
= "{};{}".format(overwrite
, special_override_string
)
343 overwrite
= special_override_string
346 ow_string
= "?{}".format(overwrite
)
347 self
._apiResource
= "/vnf_packages_content"
348 self
._apiBase
= "{}{}{}".format(
349 self
._apiName
, self
._apiVersion
, self
._apiResource
351 endpoint
= "{}{}".format(self
._apiBase
, ow_string
)
352 http_code
, resp
= self
._http
.post_cmd(
353 endpoint
=endpoint
, filename
=filename
356 if http_code
in (200, 201, 202):
358 resp
= json
.loads(resp
)
359 if not resp
or "id" not in resp
:
360 raise ClientException(
361 "unexpected response from server: {}".format(resp
)
364 elif http_code
== 204:
367 def update(self
, name
, filename
):
368 self
._logger
.debug("")
369 self
._client
.get_token()
370 vnfd
= self
.get(name
)
371 endpoint
= "{}/{}/package_content".format(self
._apiBase
, vnfd
["_id"])
372 self
.create(filename
=filename
, update_endpoint
=endpoint
)