Backwards compatibility with old descriptors; updated override flags in vnfd/nsd...
[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 elif 'name' in vnfd and name == vnfd['name']:
71 return vnfd
72 raise NotFound("vnfd {} not found".format(name))
73
74 def get_individual(self, name):
75 self._logger.debug("")
76 vnfd = self.get(name)
77 # It is redundant, since the previous one already gets the whole vnfpkginfo
78 # The only difference is that a different primitive is exercised
79 try:
80 _, resp = self._http.get2_cmd('{}/{}'.format(self._apiBase, vnfd['_id']))
81 if resp:
82 return json.loads(resp)
83 except NotFound:
84 raise NotFound("vnfd '{}' not found".format(name))
85 raise NotFound("vnfd '{}' not found".format(name))
86
87 def get_thing(self, name, thing, filename):
88 self._logger.debug("")
89 vnfd = self.get(name)
90 headers = self._client._headers
91 headers['Accept'] = 'application/binary'
92 http_code, resp = self._http.get2_cmd('{}/{}/{}'.format(self._apiBase, vnfd['_id'], thing))
93
94 if resp:
95 return json.loads(resp)
96
97
98 def get_descriptor(self, name, filename):
99 self._logger.debug("")
100 self.get_thing(name, 'vnfd', filename)
101
102 def get_package(self, name, filename):
103 self._logger.debug("")
104 self.get_thing(name, 'package_content', filename)
105
106 def get_artifact(self, name, artifact, filename):
107 self._logger.debug("")
108 self.get_thing(name, 'artifacts/{}'.format(artifact), filename)
109
110 def delete(self, name, force=False):
111 self._logger.debug("")
112 self._client.get_token()
113 vnfd = self.get(name)
114 querystring = ''
115 if force:
116 querystring = '?FORCE=True'
117 http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase,
118 vnfd['_id'], querystring))
119
120 if http_code == 202:
121 print('Deletion in progress')
122 elif http_code == 204:
123 print('Deleted')
124 else:
125 msg = resp or ""
126 raise ClientException("failed to delete vnfd {} - {}".format(name, msg))
127
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)
135
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)
140 else:
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(
146 filename, mime_type)
147 )
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'
154 else:
155 raise ClientException(
156 "Unexpected MIME type for file {}: MIME type {}".format(
157 filename, mime_type)
158 )
159
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")
169 descriptor_list = []
170 for member in tar_object:
171 if member.isreg():
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()
180 tar_object.close()
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)
185
186 vnfd = yaml.safe_load(descriptor_data)
187 vcd_list = []
188 vdu_list = []
189 for k in vnfd:
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', [])
195 break
196
197 for vcd_number, vcd in enumerate(vcd_list):
198 if override_epa:
199 virtual_memory = vcd["virtual-memory"]
200 virtual_memory["mempage-size"] = "LARGE"
201 virtual_memory["numa-enabled"] = True
202 virtual_memory["numa-node-policy"] = {
203 "node-cnt": 1,
204 "mem-policy": "STRICT"
205 }
206 virtual_cpu = vcd["virtual-cpu"]
207 virtual_cpu["pinning"] = {
208 "policy": "static",
209 "thread-policy": "PREFER"
210 }
211
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)
218
219 headers['Query-String-Format'] = 'yaml'
220 if override_nonepa:
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"] = {}
227
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)
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)