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
)
97 def get_descriptor(self
, name
, filename
):
98 self
._logger
.debug("")
99 self
.get_thing(name
, 'vnfd', filename
)
101 def get_package(self
, name
, filename
):
102 self
._logger
.debug("")
103 self
.get_thing(name
, 'package_content', filename
)
105 def get_artifact(self
, name
, artifact
, filename
):
106 self
._logger
.debug("")
107 self
.get_thing(name
, 'artifacts/{}'.format(artifact
), filename
)
109 def delete(self
, name
, force
=False):
110 self
._logger
.debug("")
111 self
._client
.get_token()
112 vnfd
= self
.get(name
)
115 querystring
= '?FORCE=True'
116 http_code
, resp
= self
._http
.delete_cmd('{}/{}{}'.format(self
._apiBase
,
117 vnfd
['_id'], querystring
))
120 print('Deletion in progress')
121 elif http_code
== 204:
125 raise ClientException("failed to delete vnfd {} - {}".format(name
, msg
))
127 def create(self
, filename
, overwrite
=None, update_endpoint
=None, skip_charm_build
=False,
128 override_epa
=False, override_nonepa
=False, override_paravirt
=False):
129 self
._logger
.debug("")
130 if os
.path
.isdir(filename
):
131 filename
= filename
.rstrip('/')
132 filename
= self
._client
.package_tool
.build(filename
, skip_validation
=False,
133 skip_charm_build
=skip_charm_build
)
135 print('Uploading package {}'.format(filename
))
136 self
.create(filename
, overwrite
=overwrite
, update_endpoint
=update_endpoint
,
137 override_epa
=override_epa
, override_nonepa
=override_nonepa
,
138 override_paravirt
=override_paravirt
)
140 self
._client
.get_token()
141 mime_type
= magic
.from_file(filename
, mime
=True)
142 if mime_type
is None:
143 raise ClientException(
144 "Unexpected MIME type for file {}: MIME type {}".format(
147 headers
= self
._client
._headers
148 headers
['Content-Filename'] = basename(filename
)
149 if mime_type
in ['application/yaml', 'text/plain', 'application/json']:
150 headers
['Content-Type'] = 'text/plain'
151 elif mime_type
in ['application/gzip', 'application/x-gzip']:
152 headers
['Content-Type'] = 'application/gzip'
154 raise ClientException(
155 "Unexpected MIME type for file {}: MIME type {}".format(
159 special_override_string
= ''
160 if override_epa
or override_nonepa
or override_paravirt
:
161 # If override for EPA, non-EPA or paravirt is required, get the descriptor data
162 descriptor_data
= None
163 if mime_type
in ['application/yaml', 'text/plain', 'application/json']:
164 with
open(filename
) as df
:
165 descriptor_data
= df
.read()
166 elif mime_type
in ['application/gzip', 'application/x-gzip']:
167 tar_object
= tarfile
.open(filename
, "r:gz")
169 for member
in tar_object
:
171 if '/' not in os
.path
.dirname(member
.name
) and member
.name
.endswith('.yaml'):
172 descriptor_list
.append(member
.name
)
173 if len(descriptor_list
) > 1:
174 raise ClientException('Found more than one potential descriptor in the tar.gz file')
175 elif len(descriptor_list
) == 0:
176 raise ClientException('No descriptor was found in the tar.gz file')
177 with tar_object
.extractfile(descriptor_list
[0]) as df
:
178 descriptor_data
= df
.read()
180 if not descriptor_data
:
181 raise ClientException('Descriptor could not be read')
182 desc_type
, vnfd
= validation_im().yaml_validation(descriptor_data
)
183 validation_im().pyangbind_validation(desc_type
, vnfd
)
185 vnfd
= yaml
.safe_load(descriptor_data
)
189 # Get only the first descriptor in case there are many in the yaml file
190 # k can be vnfd or etsi-nfv-vnfd:vnfd. This check is skipped
191 first_vnfd
= vnfd
.get(k
, {})
192 vcd_list
= first_vnfd
.get('virtual-compute-desc', [])
193 vdu_list
= first_vnfd
.get('vdu', [])
196 for vcd_number
, vcd
in enumerate(vcd_list
):
198 virtual_memory
= vcd
["virtual-memory"]
199 virtual_memory
["mempage-size"] = "LARGE"
200 virtual_memory
["numa-enabled"] = True
201 virtual_memory
["numa-node-policy"] = {
203 "mem-policy": "STRICT"
205 virtual_cpu
= vcd
["virtual-cpu"]
206 virtual_cpu
["pinning"] = {
208 "thread-policy": "PREFER"
211 cpu_override_string
= "virtual-compute-desc.{}.virtual-cpu={};"\
212 .format(vcd_number
, quote(yaml
.safe_dump(virtual_cpu
)))
213 memory_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'
220 virtual_memory
= vcd
["virtual-memory"]
221 virtual_memory
["mempage-size"] = ""
222 virtual_memory
["numa-enabled"] = ""
223 virtual_memory
["numa-node-policy"] = {}
224 virtual_cpu
= vcd
["virtual-cpu"]
225 virtual_cpu
["pinning"] = {}
227 cpu_override_string
= "virtual-compute-desc.{}.virtual-cpu={};"\
228 .format(vcd_number
, quote(yaml
.safe_dump(virtual_cpu
)))
229 memory_override_string
= "virtual-compute-desc.{}.virtual-memory={};"\
230 .format(vcd_number
, quote(yaml
.safe_dump(virtual_memory
)))
231 special_override_string
= "{}{}{}".format(special_override_string
,
232 cpu_override_string
, memory_override_string
)
234 if override_paravirt
:
235 for vdu_number
, vdu
in enumerate(vdu_list
):
236 for cpd_number
, cpd
in enumerate(vdu
["int-cpd"]):
237 for vnir_number
, vnir
in enumerate(cpd
['virtual-network-interface-requirement']):
238 special_override_string
= "{}vdu.{}.int-cpd.{}.virtual-network-interface-" \
239 "requirement.{}.virtual-interface.type=PARAVIRT;"\
240 .format(special_override_string
, vdu_number
, cpd_number
, vnir_number
)
242 special_override_string
= special_override_string
.rstrip(";")
244 headers
["Content-File-MD5"] = utils
.md5(filename
)
245 http_header
= ['{}: {}'.format(key
, val
)
246 for (key
, val
) in list(headers
.items())]
248 self
._http
.set_http_header(http_header
)
250 http_code
, resp
= self
._http
.put_cmd(endpoint
=update_endpoint
, filename
=filename
)
253 if special_override_string
:
255 overwrite
= "{};{}".format(overwrite
, special_override_string
)
257 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
)