9eb18cada44cf20e38ca378ee7e31c31ba1ef4e9
[osm/osmclient.git] / osmclient / sol005 / vnfd.py
1 # Copyright 2018 Telefonica
2 #
3 # All Rights Reserved.
4 #
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
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
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
15 # under the License.
16
17 """
18 OSM vnfd API handling
19 """
20
21 from osmclient.common.exceptions import NotFound
22 from osmclient.common.exceptions import ClientException
23 from osmclient.common import utils
24 import json
25 import yaml
26 import magic
27 from os.path import basename
28 import logging
29 import os.path
30 from urllib.parse import quote
31 import tarfile
32 from osm_im.validation import Validation as validation_im
33
34
35 class Vnfd(object):
36
37 def __init__(self, http=None, client=None):
38 self._http = http
39 self._client = client
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)
46
47 def list(self, filter=None):
48 self._logger.debug("")
49 self._client.get_token()
50 filter_string = ''
51 if filter:
52 filter_string = '?{}'.format(filter)
53 _, resp = self._http.get2_cmd('{}{}'.format(self._apiBase, filter_string))
54
55 if resp:
56 return json.loads(resp)
57 return list()
58
59 def get(self, name):
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']:
65 return vnfd
66 else:
67 for vnfd in self.list():
68 if 'product-name' in vnfd and name == vnfd['product-name']:
69 return vnfd
70 raise NotFound("vnfd {} not found".format(name))
71
72 def get_individual(self, name):
73 self._logger.debug("")
74 vnfd = self.get(name)
75 # It is redundant, since the previous one already gets the whole vnfpkginfo
76 # The only difference is that a different primitive is exercised
77 try:
78 _, resp = self._http.get2_cmd('{}/{}'.format(self._apiBase, vnfd['_id']))
79 if resp:
80 return json.loads(resp)
81 except NotFound:
82 raise NotFound("vnfd '{}' not found".format(name))
83 raise NotFound("vnfd '{}' not found".format(name))
84
85 def get_thing(self, name, thing, filename):
86 self._logger.debug("")
87 vnfd = self.get(name)
88 headers = self._client._headers
89 headers['Accept'] = 'application/binary'
90 http_code, resp = self._http.get2_cmd('{}/{}/{}'.format(self._apiBase, vnfd['_id'], thing))
91
92 if resp:
93 return json.loads(resp)
94
95
96 def get_descriptor(self, name, filename):
97 self._logger.debug("")
98 self.get_thing(name, 'vnfd', filename)
99
100 def get_package(self, name, filename):
101 self._logger.debug("")
102 self.get_thing(name, 'package_content', filename)
103
104 def get_artifact(self, name, artifact, filename):
105 self._logger.debug("")
106 self.get_thing(name, 'artifacts/{}'.format(artifact), filename)
107
108 def delete(self, name, force=False):
109 self._logger.debug("")
110 self._client.get_token()
111 vnfd = self.get(name)
112 querystring = ''
113 if force:
114 querystring = '?FORCE=True'
115 http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase,
116 vnfd['_id'], querystring))
117
118 if http_code == 202:
119 print('Deletion in progress')
120 elif http_code == 204:
121 print('Deleted')
122 else:
123 msg = resp or ""
124 raise ClientException("failed to delete vnfd {} - {}".format(name, msg))
125
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)
133
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)
138 else:
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(
144 filename, mime_type)
145 )
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'
152 else:
153 raise ClientException(
154 "Unexpected MIME type for file {}: MIME type {}".format(
155 filename, mime_type)
156 )
157
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")
167 descriptor_list = []
168 for member in tar_object:
169 if member.isreg():
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()
178 tar_object.close()
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)
183
184 vnfd = yaml.safe_load(descriptor_data)
185 vcd_list = []
186 vdu_list = []
187 for k in vnfd:
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', [])
193 break
194
195 for vcd_number, vcd in enumerate(vcd_list):
196 if override_epa:
197 virtual_memory = {
198 "mempage-size": "LARGE",
199 "numa-node-policy": {
200 "node-cnt": 1,
201 "mem-policy": "STRICT"
202 }
203 }
204 virtual_cpu = {
205 "pinning": {
206 "policy": "static",
207 "thread-policy": "PREFER"
208 }
209 }
210
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)
217
218 headers['Query-String-Format'] = 'yaml'
219 if override_nonepa:
220 virtual_memory = {
221 "mempage-size": "",
222 "numa-node-policy": {}
223 }
224 virtual_cpu = {
225 "pinning": {}
226 }
227
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)
234
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)
242
243 special_override_string = special_override_string.rstrip(";")
244
245 headers["Content-File-MD5"] = utils.md5(filename)
246 http_header = ['{}: {}'.format(key, val)
247 for (key, val) in list(headers.items())]
248
249 self._http.set_http_header(http_header)
250 if update_endpoint:
251 http_code, resp = self._http.put_cmd(endpoint=update_endpoint, filename=filename)
252 else:
253 ow_string = ''
254 if special_override_string:
255 if overwrite:
256 overwrite = "{};{}".format(overwrite, special_override_string)
257 else:
258 overwrite = special_override_string
259 if overwrite:
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)
266
267 if http_code in (200, 201, 202):
268 if resp:
269 resp = json.loads(resp)
270 if not resp or 'id' not in resp:
271 raise ClientException('unexpected response from server: {}'.format(resp))
272 print(resp['id'])
273 elif http_code == 204:
274 print('Updated')
275
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)