Merges branch sol006 into master
[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 def get_descriptor(self, name, filename):
98 self._logger.debug("")
99 self.get_thing(name, 'vnfd', filename)
100
101 def get_package(self, name, filename):
102 self._logger.debug("")
103 self.get_thing(name, 'package_content', filename)
104
105 def get_artifact(self, name, artifact, filename):
106 self._logger.debug("")
107 self.get_thing(name, 'artifacts/{}'.format(artifact), filename)
108
109 def delete(self, name, force=False):
110 self._logger.debug("")
111 self._client.get_token()
112 vnfd = self.get(name)
113 querystring = ''
114 if force:
115 querystring = '?FORCE=True'
116 http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase,
117 vnfd['_id'], querystring))
118
119 if http_code == 202:
120 print('Deletion in progress')
121 elif http_code == 204:
122 print('Deleted')
123 else:
124 msg = resp or ""
125 raise ClientException("failed to delete vnfd {} - {}".format(name, msg))
126
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)
134
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)
139 else:
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(
145 filename, mime_type)
146 )
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'
153 else:
154 raise ClientException(
155 "Unexpected MIME type for file {}: MIME type {}".format(
156 filename, mime_type)
157 )
158
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")
168 descriptor_list = []
169 for member in tar_object:
170 if member.isreg():
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()
179 tar_object.close()
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)
184
185 vnfd = yaml.safe_load(descriptor_data)
186 vcd_list = []
187 vdu_list = []
188 for k in vnfd:
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', [])
194 break
195
196 for vcd_number, vcd in enumerate(vcd_list):
197 if override_epa:
198 virtual_memory = vcd["virtual-memory"]
199 virtual_memory["mempage-size"] = "LARGE"
200 virtual_memory["numa-enabled"] = True
201 virtual_memory["numa-node-policy"] = {
202 "node-cnt": 1,
203 "mem-policy": "STRICT"
204 }
205 virtual_cpu = vcd["virtual-cpu"]
206 virtual_cpu["pinning"] = {
207 "policy": "static",
208 "thread-policy": "PREFER"
209 }
210
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)
217
218 headers['Query-String-Format'] = 'yaml'
219 if override_nonepa:
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"] = {}
226
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)
233
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)
241
242 special_override_string = special_override_string.rstrip(";")
243
244 headers["Content-File-MD5"] = utils.md5(filename)
245 http_header = ['{}: {}'.format(key, val)
246 for (key, val) in list(headers.items())]
247
248 self._http.set_http_header(http_header)
249 if update_endpoint:
250 http_code, resp = self._http.put_cmd(endpoint=update_endpoint, filename=filename)
251 else:
252 ow_string = ''
253 if special_override_string:
254 if overwrite:
255 overwrite = "{};{}".format(overwrite, special_override_string)
256 else:
257 overwrite = special_override_string
258
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)