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
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(self
._apiName
,
45 self
._apiVersion
, self
._apiResource
)
47 def list(self
, filter=None):
48 self
._logger
.debug("")
49 self
._client
.get_token()
52 filter_string
= '?{}'.format(filter)
53 _
, resp
= self
._http
.get2_cmd('{}{}'.format(self
._apiBase
, filter_string
))
56 return json
.loads(resp
)
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']:
67 for vnfd
in self
.list():
68 if 'product-name' in vnfd
and name
== vnfd
['product-name']:
70 elif 'name' in vnfd
and name
== vnfd
['name']:
72 raise NotFound("vnfd {} not found".format(name
))
74 def get_individual(self
, name
):
75 self
._logger
.debug("")
77 # It is redundant, since the previous one already gets the whole vnfpkginfo
78 # The only difference is that a different primitive is exercised
80 _
, resp
= self
._http
.get2_cmd('{}/{}'.format(self
._apiBase
, vnfd
['_id']))
82 return json
.loads(resp
)
84 raise NotFound("vnfd '{}' not found".format(name
))
85 raise NotFound("vnfd '{}' not found".format(name
))
87 def get_thing(self
, name
, thing
, filename
):
88 self
._logger
.debug("")
90 headers
= self
._client
._headers
91 headers
['Accept'] = 'application/binary'
92 http_code
, resp
= self
._http
.get2_cmd('{}/{}/{}'.format(self
._apiBase
, vnfd
['_id'], thing
))
95 return json
.loads(resp
)
98 def get_descriptor(self
, name
, filename
):
99 self
._logger
.debug("")
100 self
.get_thing(name
, 'vnfd', filename
)
102 def get_package(self
, name
, filename
):
103 self
._logger
.debug("")
104 self
.get_thing(name
, 'package_content', filename
)
106 def get_artifact(self
, name
, artifact
, filename
):
107 self
._logger
.debug("")
108 self
.get_thing(name
, 'artifacts/{}'.format(artifact
), filename
)
110 def delete(self
, name
, force
=False):
111 self
._logger
.debug("")
112 self
._client
.get_token()
113 vnfd
= self
.get(name
)
116 querystring
= '?FORCE=True'
117 http_code
, resp
= self
._http
.delete_cmd('{}/{}{}'.format(self
._apiBase
,
118 vnfd
['_id'], querystring
))
121 print('Deletion in progress')
122 elif http_code
== 204:
126 raise ClientException("failed to delete vnfd {} - {}".format(name
, msg
))
128 def create(self
, filename
, overwrite
=None, update_endpoint
=None, skip_charm_build
=False,
129 override_epa
=False, override_nonepa
=False, override_paravirt
=False):
130 self
._logger
.debug("")
131 if os
.path
.isdir(filename
):
132 filename
= filename
.rstrip('/')
133 filename
= self
._client
.package_tool
.build(filename
, skip_validation
=False,
134 skip_charm_build
=skip_charm_build
)
136 print('Uploading package {}'.format(filename
))
137 self
.create(filename
, overwrite
=overwrite
, update_endpoint
=update_endpoint
,
138 override_epa
=override_epa
, override_nonepa
=override_nonepa
,
139 override_paravirt
=override_paravirt
)
141 self
._client
.get_token()
142 mime_type
= magic
.from_file(filename
, mime
=True)
143 if mime_type
is None:
144 raise ClientException(
145 "Unexpected MIME type for file {}: MIME type {}".format(
148 headers
= self
._client
._headers
149 headers
['Content-Filename'] = basename(filename
)
150 if mime_type
in ['application/yaml', 'text/plain', 'application/json']:
151 headers
['Content-Type'] = 'text/plain'
152 elif mime_type
in ['application/gzip', 'application/x-gzip']:
153 headers
['Content-Type'] = 'application/gzip'
155 raise ClientException(
156 "Unexpected MIME type for file {}: MIME type {}".format(
160 special_override_string
= ''
161 if override_epa
or override_nonepa
or override_paravirt
:
162 # If override for EPA, non-EPA or paravirt is required, get the descriptor data
163 descriptor_data
= None
164 if mime_type
in ['application/yaml', 'text/plain', 'application/json']:
165 with
open(filename
) as df
:
166 descriptor_data
= df
.read()
167 elif mime_type
in ['application/gzip', 'application/x-gzip']:
168 tar_object
= tarfile
.open(filename
, "r:gz")
170 for member
in tar_object
:
172 if '/' not in os
.path
.dirname(member
.name
) and member
.name
.endswith('.yaml'):
173 descriptor_list
.append(member
.name
)
174 if len(descriptor_list
) > 1:
175 raise ClientException('Found more than one potential descriptor in the tar.gz file')
176 elif len(descriptor_list
) == 0:
177 raise ClientException('No descriptor was found in the tar.gz file')
178 with tar_object
.extractfile(descriptor_list
[0]) as df
:
179 descriptor_data
= df
.read()
181 if not descriptor_data
:
182 raise ClientException('Descriptor could not be read')
183 desc_type
, vnfd
= validation_im().yaml_validation(descriptor_data
)
184 validation_im().pyangbind_validation(desc_type
, vnfd
)
186 vnfd
= yaml
.safe_load(descriptor_data
)
190 # Get only the first descriptor in case there are many in the yaml file
191 # k can be vnfd or etsi-nfv-vnfd:vnfd. This check is skipped
192 first_vnfd
= vnfd
.get(k
, {})
193 vcd_list
= first_vnfd
.get('virtual-compute-desc', [])
194 vdu_list
= first_vnfd
.get('vdu', [])
197 for vcd_number
, vcd
in enumerate(vcd_list
):
199 virtual_memory
= vcd
["virtual-memory"]
200 virtual_memory
["mempage-size"] = "LARGE"
201 virtual_memory
["numa-enabled"] = True
202 virtual_memory
["numa-node-policy"] = {
204 "mem-policy": "STRICT"
206 virtual_cpu
= vcd
["virtual-cpu"]
207 virtual_cpu
["pinning"] = {
209 "thread-policy": "PREFER"
212 cpu_override_string
= "virtual-compute-desc.{}.virtual-cpu={};"\
213 .format(vcd_number
, quote(yaml
.safe_dump(virtual_cpu
)))
214 memory_override_string
= "virtual-compute-desc.{}.virtual-memory={};"\
215 .format(vcd_number
, quote(yaml
.safe_dump(virtual_memory
)))
216 special_override_string
= "{}{}{}".format(special_override_string
,
217 cpu_override_string
, memory_override_string
)
219 headers
['Query-String-Format'] = 'yaml'
221 virtual_memory
= vcd
["virtual-memory"]
222 virtual_memory
["mempage-size"] = ""
223 virtual_memory
["numa-enabled"] = ""
224 virtual_memory
["numa-node-policy"] = {}
225 virtual_cpu
= vcd
["virtual-cpu"]
226 virtual_cpu
["pinning"] = {}
228 cpu_override_string
= "virtual-compute-desc.{}.virtual-cpu={};"\
229 .format(vcd_number
, quote(yaml
.safe_dump(virtual_cpu
)))
230 memory_override_string
= "virtual-compute-desc.{}.virtual-memory={};"\
231 .format(vcd_number
, quote(yaml
.safe_dump(virtual_memory
)))
232 special_override_string
= "{}{}{}".format(special_override_string
,
233 cpu_override_string
, memory_override_string
)
235 if override_paravirt
:
236 for vdu_number
, vdu
in enumerate(vdu_list
):
237 for cpd_number
, cpd
in enumerate(vdu
["int-cpd"]):
238 for vnir_number
, vnir
in enumerate(cpd
['virtual-network-interface-requirement']):
239 special_override_string
= "{}vdu.{}.int-cpd.{}.virtual-network-interface-" \
240 "requirement.{}.virtual-interface.type=PARAVIRT;"\
241 .format(special_override_string
, vdu_number
, cpd_number
, vnir_number
)
243 special_override_string
= special_override_string
.rstrip(";")
245 headers
["Content-File-MD5"] = utils
.md5(filename
)
246 http_header
= ['{}: {}'.format(key
, val
)
247 for (key
, val
) in list(headers
.items())]
249 self
._http
.set_http_header(http_header
)
251 http_code
, resp
= self
._http
.put_cmd(endpoint
=update_endpoint
, filename
=filename
)
254 if special_override_string
:
256 overwrite
= "{};{}".format(overwrite
, special_override_string
)
258 overwrite
= special_override_string
260 ow_string
= '?{}'.format(overwrite
)
261 self
._apiResource
= '/vnf_packages_content'
262 self
._apiBase
= '{}{}{}'.format(self
._apiName
,
263 self
._apiVersion
, self
._apiResource
)
264 endpoint
= '{}{}'.format(self
._apiBase
, ow_string
)
265 http_code
, resp
= self
._http
.post_cmd(endpoint
=endpoint
, filename
=filename
)
267 if http_code
in (200, 201, 202):
269 resp
= json
.loads(resp
)
270 if not resp
or 'id' not in resp
:
271 raise ClientException('unexpected response from server: {}'.format(resp
))
273 elif http_code
== 204:
276 def update(self
, name
, filename
):
277 self
._logger
.debug("")
278 self
._client
.get_token()
279 vnfd
= self
.get(name
)
280 endpoint
= '{}/{}/package_content'.format(self
._apiBase
, vnfd
['_id'])
281 self
.create(filename
=filename
, update_endpoint
=endpoint
)