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 raise NotFound("vnfd {} not found".format(name
))
72 def get_individual(self
, name
):
73 self
._logger
.debug("")
75 # It is redundant, since the previous one already gets the whole vnfpkginfo
76 # The only difference is that a different primitive is exercised
78 _
, resp
= self
._http
.get2_cmd('{}/{}'.format(self
._apiBase
, vnfd
['_id']))
80 return json
.loads(resp
)
82 raise NotFound("vnfd '{}' not found".format(name
))
83 raise NotFound("vnfd '{}' not found".format(name
))
85 def get_thing(self
, name
, thing
, filename
):
86 self
._logger
.debug("")
88 headers
= self
._client
._headers
89 headers
['Accept'] = 'application/binary'
90 http_code
, resp
= self
._http
.get2_cmd('{}/{}/{}'.format(self
._apiBase
, vnfd
['_id'], thing
))
93 return json
.loads(resp
)
96 def get_descriptor(self
, name
, filename
):
97 self
._logger
.debug("")
98 self
.get_thing(name
, 'vnfd', filename
)
100 def get_package(self
, name
, filename
):
101 self
._logger
.debug("")
102 self
.get_thing(name
, 'package_content', filename
)
104 def get_artifact(self
, name
, artifact
, filename
):
105 self
._logger
.debug("")
106 self
.get_thing(name
, 'artifacts/{}'.format(artifact
), filename
)
108 def delete(self
, name
, force
=False):
109 self
._logger
.debug("")
110 self
._client
.get_token()
111 vnfd
= self
.get(name
)
114 querystring
= '?FORCE=True'
115 http_code
, resp
= self
._http
.delete_cmd('{}/{}{}'.format(self
._apiBase
,
116 vnfd
['_id'], querystring
))
119 print('Deletion in progress')
120 elif http_code
== 204:
124 raise ClientException("failed to delete vnfd {} - {}".format(name
, msg
))
126 def create(self
, filename
, overwrite
=None, update_endpoint
=None, skip_charm_build
=False,
127 override_epa
=False, override_nonepa
=False, override_paravirt
=False):
128 self
._logger
.debug("")
129 if os
.path
.isdir(filename
):
130 filename
= filename
.rstrip('/')
131 filename
= self
._client
.package_tool
.build(filename
, skip_validation
=False,
132 skip_charm_build
=skip_charm_build
)
134 print('Uploading package {}'.format(filename
))
135 self
.create(filename
, overwrite
=overwrite
, update_endpoint
=update_endpoint
,
136 override_epa
=override_epa
, override_nonepa
=override_nonepa
,
137 override_paravirt
=override_paravirt
)
139 self
._client
.get_token()
140 mime_type
= magic
.from_file(filename
, mime
=True)
141 if mime_type
is None:
142 raise ClientException(
143 "Unexpected MIME type for file {}: MIME type {}".format(
146 headers
= self
._client
._headers
147 headers
['Content-Filename'] = basename(filename
)
148 if mime_type
in ['application/yaml', 'text/plain', 'application/json']:
149 headers
['Content-Type'] = 'text/plain'
150 elif mime_type
in ['application/gzip', 'application/x-gzip']:
151 headers
['Content-Type'] = 'application/gzip'
153 raise ClientException(
154 "Unexpected MIME type for file {}: MIME type {}".format(
158 special_override_string
= ''
159 if override_epa
or override_nonepa
or override_paravirt
:
160 # If override for EPA, non-EPA or paravirt is required, get the descriptor data
161 descriptor_data
= None
162 if mime_type
in ['application/yaml', 'text/plain', 'application/json']:
163 with
open(filename
) as df
:
164 descriptor_data
= df
.read()
165 elif mime_type
in ['application/gzip', 'application/x-gzip']:
166 tar_object
= tarfile
.open(filename
, "r:gz")
168 for member
in tar_object
:
170 if '/' not in os
.path
.dirname(member
.name
) and member
.name
.endswith('.yaml'):
171 descriptor_list
.append(member
.name
)
172 if len(descriptor_list
) > 1:
173 raise ClientException('Found more than one potential descriptor in the tar.gz file')
174 elif len(descriptor_list
) == 0:
175 raise ClientException('No descriptor was found in the tar.gz file')
176 with tar_object
.extractfile(descriptor_list
[0]) as df
:
177 descriptor_data
= df
.read()
179 if not descriptor_data
:
180 raise ClientException('Descriptor could not be read')
181 desc_type
, vnfd
= validation_im().yaml_validation(descriptor_data
)
182 validation_im().pyangbind_validation(desc_type
, vnfd
)
184 vnfd
= yaml
.safe_load(descriptor_data
)
188 # Get only the first descriptor in case there are many in the yaml file
189 # k can be vnfd or etsi-nfv-vnfd:vnfd. This check is skipped
190 first_vnfd
= vnfd
.get(k
, {})
191 vcd_list
= first_vnfd
.get('virtual-compute-desc', [])
192 vdu_list
= first_vnfd
.get('vdu', [])
195 for vcd_number
, vcd
in enumerate(vcd_list
):
198 "mempage-size": "LARGE",
199 "numa-node-policy": {
201 "mem-policy": "STRICT"
206 "policy": "DEDICATED",
207 "thread-policy": "PREFER"
211 memory_override_string
= "virtual-compute-desc.{}.virtual-cpu={};"\
212 .format(vcd_number
, quote(yaml
.safe_dump(virtual_cpu
)))
213 cpu_override_string
= "virtual-compute-desc.{}.virtual-memory={};"\
214 .format(vcd_number
, quote(yaml
.safe_dump(virtual_memory
)))
215 special_override_string
= "{}{}{}".format(special_override_string
,
216 cpu_override_string
, memory_override_string
)
218 headers
['Query-String-Format'] = 'yaml'
222 "numa-node-policy": {}
228 memory_override_string
= "virtual-compute-desc.{}.virtual-cpu={};"\
229 .format(vcd_number
, quote(yaml
.safe_dump(virtual_cpu
)))
230 cpu_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
)