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
))
55 return json
.loads(resp
)
59 self
._logger
.debug("")
60 self
._client
.get_token()
61 if utils
.validate_uuid4(name
):
62 for vnfd
in self
.list():
63 if name
== vnfd
['_id']:
66 for vnfd
in self
.list():
67 if 'name' in vnfd
and name
== vnfd
['name']:
69 raise NotFound("vnfd {} not found".format(name
))
71 def get_individual(self
, name
):
72 self
._logger
.debug("")
74 # It is redundant, since the previous one already gets the whole vnfpkginfo
75 # The only difference is that a different primitive is exercised
77 _
, resp
= self
._http
.get2_cmd('{}/{}'.format(self
._apiBase
, vnfd
['_id']))
79 return json
.loads(resp
)
81 raise NotFound("vnfd '{}' not found".format(name
))
82 raise NotFound("vnfd '{}' not found".format(name
))
84 def get_thing(self
, name
, thing
, filename
):
85 self
._logger
.debug("")
87 headers
= self
._client
._headers
88 headers
['Accept'] = 'application/binary'
89 http_code
, resp
= self
._http
.get2_cmd('{}/{}/{}'.format(self
._apiBase
, vnfd
['_id'], thing
))
92 return json
.loads(resp
)
94 def get_descriptor(self
, name
, filename
):
95 self
._logger
.debug("")
96 self
.get_thing(name
, 'vnfd', filename
)
98 def get_package(self
, name
, filename
):
99 self
._logger
.debug("")
100 self
.get_thing(name
, 'package_content', filename
)
102 def get_artifact(self
, name
, artifact
, filename
):
103 self
._logger
.debug("")
104 self
.get_thing(name
, 'artifacts/{}'.format(artifact
), filename
)
106 def delete(self
, name
, force
=False):
107 self
._logger
.debug("")
108 self
._client
.get_token()
109 vnfd
= self
.get(name
)
112 querystring
= '?FORCE=True'
113 http_code
, resp
= self
._http
.delete_cmd('{}/{}{}'.format(self
._apiBase
,
114 vnfd
['_id'], querystring
))
117 print('Deletion in progress')
118 elif http_code
== 204:
122 raise ClientException("failed to delete vnfd {} - {}".format(name
, msg
))
124 def create(self
, filename
, overwrite
=None, update_endpoint
=None, skip_charm_build
=False,
125 override_epa
=False, override_nonepa
=False, override_paravirt
=False):
126 self
._logger
.debug("")
127 if os
.path
.isdir(filename
):
128 filename
= filename
.rstrip('/')
129 filename
= self
._client
.package_tool
.build(filename
, skip_validation
=False,
130 skip_charm_build
=skip_charm_build
)
131 print('Uploading package {}'.format(filename
))
132 self
.create(filename
, overwrite
=overwrite
, update_endpoint
=update_endpoint
,
133 override_epa
=override_epa
, override_nonepa
=override_nonepa
,
134 override_paravirt
=override_paravirt
)
136 self
._client
.get_token()
137 mime_type
= magic
.from_file(filename
, mime
=True)
138 if mime_type
is None:
139 raise ClientException(
140 "Unexpected MIME type for file {}: MIME type {}".format(
143 headers
= self
._client
._headers
144 headers
['Content-Filename'] = basename(filename
)
145 if mime_type
in ['application/yaml', 'text/plain', 'application/json']:
146 headers
['Content-Type'] = 'text/plain'
147 elif mime_type
in ['application/gzip', 'application/x-gzip']:
148 headers
['Content-Type'] = 'application/gzip'
150 raise ClientException(
151 "Unexpected MIME type for file {}: MIME type {}".format(
154 special_ow_string
= ''
155 if override_epa
or override_nonepa
or override_paravirt
:
156 # If override for EPA, non-EPA or paravirt is required, get the descriptor data
157 descriptor_data
= None
158 if mime_type
in ['application/yaml', 'text/plain', 'application/json']:
159 with
open(filename
) as df
:
160 descriptor_data
= df
.read()
161 elif mime_type
in ['application/gzip', 'application/x-gzip']:
162 tar_object
= tarfile
.open(filename
, "r:gz")
164 for member
in tar_object
:
166 if '/' not in os
.path
.dirname(member
.name
) and member
.name
.endswith('.yaml'):
167 descriptor_list
.append(member
.name
)
168 if len(descriptor_list
) > 1:
169 raise ClientException('Found more than one potential descriptor in the tar.gz file')
170 elif len(descriptor_list
) == 0:
171 raise ClientException('No descriptor was found in the tar.gz file')
172 with tar_object
.extractfile(descriptor_list
[0]) as df
:
173 descriptor_data
= df
.read()
175 if not descriptor_data
:
176 raise ClientException('Descriptor could not be read')
177 desc_type
, vnfd
= validation_im().yaml_validation(descriptor_data
)
178 validation_im().pyangbind_validation(desc_type
, vnfd
)
179 vnfd
= yaml
.safe_load(descriptor_data
)
182 # Get only the first descriptor in case there are many in the yaml file
183 # k can be vnfd:vnfd-catalog or vnfd-catalog. This check is skipped
184 first_vnfd
= vnfd
[k
]['vnfd'][0]
185 vdu_list
= first_vnfd
.get('vdu', [])
187 for vdu_number
, vdu
in enumerate(vdu_list
):
190 guest_epa
["mempage-size"] = "LARGE"
191 guest_epa
["cpu-pinning-policy"] = "DEDICATED"
192 guest_epa
["cpu-thread-pinning-policy"] = "PREFER"
193 guest_epa
["numa-node-policy"] = {}
194 guest_epa
["numa-node-policy"]["node-cnt"] = 1
195 guest_epa
["numa-node-policy"]["mem-policy"] = "STRICT"
196 special_ow_string
= "{}vdu.{}.guest-epa={};".format(special_ow_string
, vdu_number
,
197 quote(yaml
.safe_dump(guest_epa
)))
198 headers
['Query-String-Format'] = 'yaml'
200 special_ow_string
= "{}vdu.{}.guest-epa=;".format(special_ow_string
, vdu_number
)
201 if override_paravirt
:
202 for iface_number
in range(len(vdu
['interface'])):
203 special_ow_string
= "{}vdu.{}.interface.{}.virtual-interface.type=PARAVIRT;".format(
204 special_ow_string
, vdu_number
, iface_number
)
205 special_ow_string
= special_ow_string
.rstrip(";")
207 headers
["Content-File-MD5"] = utils
.md5(filename
)
208 http_header
= ['{}: {}'.format(key
, val
)
209 for (key
, val
) in list(headers
.items())]
210 self
._http
.set_http_header(http_header
)
212 http_code
, resp
= self
._http
.put_cmd(endpoint
=update_endpoint
, filename
=filename
)
215 if special_ow_string
:
217 overwrite
= "{};{}".format(overwrite
, special_ow_string
)
219 overwrite
= special_ow_string
221 ow_string
= '?{}'.format(overwrite
)
222 self
._apiResource
= '/vnf_packages_content'
223 self
._apiBase
= '{}{}{}'.format(self
._apiName
,
224 self
._apiVersion
, self
._apiResource
)
225 endpoint
= '{}{}'.format(self
._apiBase
, ow_string
)
226 http_code
, resp
= self
._http
.post_cmd(endpoint
=endpoint
, filename
=filename
)
228 if http_code
in (200, 201, 202):
230 resp
= json
.loads(resp
)
231 if not resp
or 'id' not in resp
:
232 raise ClientException('unexpected response from server: {}'.format(resp
))
234 elif http_code
== 204:
237 def update(self
, name
, filename
):
238 self
._logger
.debug("")
239 self
._client
.get_token()
240 vnfd
= self
.get(name
)
241 endpoint
= '{}/{}/package_content'.format(self
._apiBase
, vnfd
['_id'])
242 self
.create(filename
=filename
, update_endpoint
=endpoint
)