Coverage for osmclient/sol005/vnfd.py: 13%

207 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2024-06-30 09:54 +0000

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""" 

18OSM vnfd API handling 

19""" 

20 

21from osmclient.common.exceptions import NotFound 

22from osmclient.common.exceptions import ClientException 

23from osmclient.common import utils 

24import json 

25import yaml 

26import magic 

27from os.path import basename 

28import logging 

29import os.path 

30from urllib.parse import quote 

31import tarfile 

32from osm_im.validation import Validation as validation_im 

33from zipfile import ZipFile 

34 

35 

36class Vnfd(object): 

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( 

45 self._apiName, self._apiVersion, self._apiResource 

46 ) 

47 

48 def list(self, filter=None): 

49 self._logger.debug("") 

50 self._client.get_token() 

51 filter_string = "" 

52 if filter: 

53 filter_string = "?{}".format(filter) 

54 _, resp = self._http.get2_cmd("{}{}".format(self._apiBase, filter_string)) 

55 

56 if resp: 

57 return json.loads(resp) 

58 return list() 

59 

60 def get(self, name): 

61 self._logger.debug("") 

62 self._client.get_token() 

63 if utils.validate_uuid4(name): 

64 for vnfd in self.list(): 

65 if name == vnfd["_id"]: 

66 return vnfd 

67 else: 

68 for vnfd in self.list(): 

69 if "product-name" in vnfd and name == vnfd["product-name"]: 

70 return vnfd 

71 elif "name" in vnfd and name == vnfd["name"]: 

72 return vnfd 

73 raise NotFound("vnfd {} not found".format(name)) 

74 

75 def get_individual(self, name): 

76 self._logger.debug("") 

77 vnfd = self.get(name) 

78 # It is redundant, since the previous one already gets the whole vnfpkginfo 

79 # The only difference is that a different primitive is exercised 

80 try: 

81 _, resp = self._http.get2_cmd("{}/{}".format(self._apiBase, vnfd["_id"])) 

82 if resp: 

83 return json.loads(resp) 

84 except NotFound: 

85 raise NotFound("vnfd '{}' not found".format(name)) 

86 raise NotFound("vnfd '{}' not found".format(name)) 

87 

88 def get_thing(self, name, thing, filename): 

89 self._logger.debug("") 

90 vnfd = self.get(name) 

91 headers = self._client._headers 

92 headers["Accept"] = "application/binary" 

93 http_code, resp = self._http.get2_cmd( 

94 "{}/{}/{}".format(self._apiBase, vnfd["_id"], thing) 

95 ) 

96 

97 if resp: 

98 return json.loads(resp) 

99 

100 def get_descriptor(self, name, filename): 

101 self._logger.debug("") 

102 self.get_thing(name, "vnfd", filename) 

103 

104 def get_package(self, name, filename): 

105 self._logger.debug("") 

106 self.get_thing(name, "package_content", filename) 

107 

108 def get_artifact(self, name, artifact, filename): 

109 self._logger.debug("") 

110 self.get_thing(name, "artifacts/{}".format(artifact), filename) 

111 

112 def delete(self, name, force=False): 

113 self._logger.debug("") 

114 self._client.get_token() 

115 vnfd = self.get(name) 

116 querystring = "" 

117 if force: 

118 querystring = "?FORCE=True" 

119 http_code, resp = self._http.delete_cmd( 

120 "{}/{}{}".format(self._apiBase, vnfd["_id"], querystring) 

121 ) 

122 

123 if http_code == 202: 

124 print("Deletion in progress") 

125 elif http_code == 204: 

126 print("Deleted") 

127 else: 

128 msg = resp or "" 

129 raise ClientException("failed to delete vnfd {} - {}".format(name, msg)) 

130 

131 def create( 

132 self, 

133 filename, 

134 overwrite=None, 

135 update_endpoint=None, 

136 skip_charm_build=False, 

137 override_epa=False, 

138 override_nonepa=False, 

139 override_paravirt=False, 

140 ): 

141 self._logger.debug("") 

142 if os.path.isdir(filename): 

143 filename = filename.rstrip("/") 

144 filename = self._client.package_tool.build( 

145 filename, skip_validation=False, skip_charm_build=skip_charm_build 

146 ) 

147 

148 print("Uploading package {}".format(filename)) 

149 self.create( 

150 filename, 

151 overwrite=overwrite, 

152 update_endpoint=update_endpoint, 

153 override_epa=override_epa, 

154 override_nonepa=override_nonepa, 

155 override_paravirt=override_paravirt, 

156 ) 

157 else: 

158 self._client.get_token() 

159 mime_type = magic.from_file(filename, mime=True) 

160 if mime_type is None: 

161 raise ClientException( 

162 "Unexpected MIME type for file {}: MIME type {}".format( 

163 filename, mime_type 

164 ) 

165 ) 

166 headers = self._client._headers 

167 headers["Content-Filename"] = basename(filename) 

168 if mime_type in ["application/yaml", "text/plain", "application/json"]: 

169 headers["Content-Type"] = "text/plain" 

170 elif mime_type in ["application/gzip", "application/x-gzip"]: 

171 headers["Content-Type"] = "application/gzip" 

172 elif mime_type in ["application/zip"]: 

173 headers["Content-Type"] = "application/zip" 

174 else: 

175 raise ClientException( 

176 "Unexpected MIME type for file {}: MIME type {}".format( 

177 filename, mime_type 

178 ) 

179 ) 

180 

181 special_override_string = "" 

182 if override_epa or override_nonepa or override_paravirt: 

183 # If override for EPA, non-EPA or paravirt is required, get the descriptor data 

184 descriptor_data = None 

185 if mime_type in ["application/yaml", "text/plain", "application/json"]: 

186 with open(filename) as df: 

187 descriptor_data = df.read() 

188 elif mime_type in ["application/gzip", "application/x-gzip"]: 

189 tar_object = tarfile.open(filename, "r:gz") 

190 descriptor_list = [] 

191 for member in tar_object: 

192 if member.isreg(): 

193 if "/" not in os.path.dirname( 

194 member.name 

195 ) and member.name.endswith(".yaml"): 

196 descriptor_list.append(member.name) 

197 if len(descriptor_list) > 1: 

198 raise ClientException( 

199 "Found more than one potential descriptor in the tar.gz file" 

200 ) 

201 elif len(descriptor_list) == 0: 

202 raise ClientException( 

203 "No descriptor was found in the tar.gz file" 

204 ) 

205 with tar_object.extractfile(descriptor_list[0]) as df: 

206 descriptor_data = df.read() 

207 tar_object.close() 

208 elif mime_type in ["application/zip"]: 

209 package_zip = ZipFile(filename) 

210 package_files = package_zip.infolist() 

211 

212 descriptors = [] 

213 if ( 

214 "Definitions" in package_files 

215 and "TOSCA-Metadata" in package_files 

216 ): 

217 descriptors = [ 

218 definition 

219 for definition in package_files 

220 if "Definitions/" in definition 

221 and ( 

222 definition.endswith(".yaml") 

223 or definition.endswith(".yml") 

224 ) 

225 ] 

226 else: 

227 descriptors = [ 

228 definition 

229 for definition in package_files 

230 if definition.endswith(".yaml") 

231 or definition.endswith(".yml") 

232 ] 

233 if len(descriptors) < 1: 

234 raise ClientException( 

235 "No descriptor found on this package, OSM was expecting at least 1" 

236 ) 

237 descriptor_data = package_zip.open(descriptors[0]) 

238 package_zip.close() 

239 

240 if not descriptor_data: 

241 raise ClientException("Descriptor could not be read") 

242 desc_type, vnfd = validation_im().yaml_validation(descriptor_data) 

243 validation_im().pyangbind_validation(desc_type, vnfd) 

244 

245 vnfd = yaml.safe_load(descriptor_data) 

246 vcd_list = [] 

247 vdu_list = [] 

248 for k in vnfd: 

249 # Get only the first descriptor in case there are many in the yaml file 

250 # k can be vnfd or etsi-nfv-vnfd:vnfd. This check is skipped 

251 first_vnfd = vnfd.get(k, {}) 

252 vcd_list = first_vnfd.get("virtual-compute-desc", []) 

253 vdu_list = first_vnfd.get("vdu", []) 

254 break 

255 

256 for vcd_number, vcd in enumerate(vcd_list): 

257 if override_epa: 

258 virtual_memory = vcd["virtual-memory"] 

259 virtual_memory["mempage-size"] = "LARGE" 

260 virtual_memory["numa-enabled"] = True 

261 virtual_memory["numa-node-policy"] = { 

262 "node-cnt": 1, 

263 "mem-policy": "STRICT", 

264 } 

265 virtual_cpu = vcd["virtual-cpu"] 

266 virtual_cpu["pinning"] = { 

267 "policy": "static", 

268 "thread-policy": "PREFER", 

269 } 

270 

271 cpu_override_string = ( 

272 "virtual-compute-desc.{}.virtual-cpu={};".format( 

273 vcd_number, quote(yaml.safe_dump(virtual_cpu)) 

274 ) 

275 ) 

276 memory_override_string = ( 

277 "virtual-compute-desc.{}.virtual-memory={};".format( 

278 vcd_number, quote(yaml.safe_dump(virtual_memory)) 

279 ) 

280 ) 

281 special_override_string = "{}{}{}".format( 

282 special_override_string, 

283 cpu_override_string, 

284 memory_override_string, 

285 ) 

286 

287 headers["Query-String-Format"] = "yaml" 

288 if override_nonepa: 

289 virtual_memory = vcd["virtual-memory"] 

290 virtual_memory["mempage-size"] = "" 

291 virtual_memory["numa-enabled"] = "" 

292 virtual_memory["numa-node-policy"] = {} 

293 virtual_cpu = vcd["virtual-cpu"] 

294 virtual_cpu["pinning"] = {} 

295 

296 cpu_override_string = ( 

297 "virtual-compute-desc.{}.virtual-cpu={};".format( 

298 vcd_number, quote(yaml.safe_dump(virtual_cpu)) 

299 ) 

300 ) 

301 memory_override_string = ( 

302 "virtual-compute-desc.{}.virtual-memory={};".format( 

303 vcd_number, quote(yaml.safe_dump(virtual_memory)) 

304 ) 

305 ) 

306 special_override_string = "{}{}{}".format( 

307 special_override_string, 

308 cpu_override_string, 

309 memory_override_string, 

310 ) 

311 

312 if override_paravirt: 

313 for vdu_number, vdu in enumerate(vdu_list): 

314 for cpd_number, cpd in enumerate(vdu["int-cpd"]): 

315 for vnir_number, vnir in enumerate( 

316 cpd["virtual-network-interface-requirement"] 

317 ): 

318 special_override_string = ( 

319 "{}vdu.{}.int-cpd.{}.virtual-network-interface-" 

320 "requirement.{}.virtual-interface.type=" 

321 "PARAVIRT;".format( 

322 special_override_string, 

323 vdu_number, 

324 cpd_number, 

325 vnir_number, 

326 ) 

327 ) 

328 

329 special_override_string = special_override_string.rstrip(";") 

330 

331 headers["Content-File-MD5"] = utils.md5(filename) 

332 self._http.set_http_header(headers) 

333 if update_endpoint: 

334 http_code, resp = self._http.put_cmd( 

335 endpoint=update_endpoint, filename=filename 

336 ) 

337 else: 

338 ow_string = "" 

339 if special_override_string: 

340 if overwrite: 

341 overwrite = "{};{}".format(overwrite, special_override_string) 

342 else: 

343 overwrite = special_override_string 

344 

345 if overwrite: 

346 ow_string = "?{}".format(overwrite) 

347 self._apiResource = "/vnf_packages_content" 

348 self._apiBase = "{}{}{}".format( 

349 self._apiName, self._apiVersion, self._apiResource 

350 ) 

351 endpoint = "{}{}".format(self._apiBase, ow_string) 

352 http_code, resp = self._http.post_cmd( 

353 endpoint=endpoint, filename=filename 

354 ) 

355 

356 if http_code in (200, 201, 202): 

357 if resp: 

358 resp = json.loads(resp) 

359 if not resp or "id" not in resp: 

360 raise ClientException( 

361 "unexpected response from server: {}".format(resp) 

362 ) 

363 print(resp["id"]) 

364 elif http_code == 204: 

365 print("Updated") 

366 

367 def update(self, name, filename): 

368 self._logger.debug("") 

369 self._client.get_token() 

370 vnfd = self.get(name) 

371 endpoint = "{}/{}/package_content".format(self._apiBase, vnfd["_id"]) 

372 self.create(filename=filename, update_endpoint=endpoint)