* YANG to TOSCA translator
[osm/SO.git] / rwlaunchpad / plugins / rwlaunchpadtasklet / rift / tasklets / rwlaunchpad / tosca.py
1 # Copyright 2016 RIFT.io Inc
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15
16 import logging
17 import os
18 import shutil
19 import subprocess
20 import tempfile
21 import uuid
22 import zipfile
23
24 from rift.mano.tosca_translator.shell import TranslatorShell
25 from rift.mano.yang_translator.rwmano.yang_translator import YangTranslator
26
27
28 class ToscaPackageError(Exception):
29 pass
30
31
32 class ToscaPackageReadError(Exception):
33 pass
34
35
36 class InvalidToscaPackageError(ToscaPackageError):
37 pass
38
39
40 class ToscaTranslateError(ToscaPackageError):
41 pass
42
43
44 class YangTranslateError(Exception):
45 pass
46
47
48 class ToscaArchiveCreateError(YangTranslateError):
49 pass
50
51
52 class YangTranslateNsdError(YangTranslateError):
53 pass
54
55
56 class ExportTosca(object):
57 def __init__(self, log=None):
58 if log is None:
59 self.log = logging.getLogger("rw-mano-log")
60 else:
61 self.log = log
62 self.nsds = {}
63 self.csars = list()
64 self.vnfds = {}
65
66 def add_image(self, nsd_id, image, chksum=None):
67 if image.name not in self.images:
68 self.images[image.name] = image
69
70 def add_vld(self, nsd_id, vld, pkg=None):
71 if not 'vlds' in self.nsds[nsd_id]:
72 self.nsds[nsd_id]['vlds'] = []
73 self.nsds[nsd_id]['vlds'].append(vld)
74 if pkg:
75 self.nsds[nsd_id]['pkgs'].append(pkg)
76
77 def add_single_vnfd(self, vnfd_id, vnfd, pkg=None):
78 if vnfd is not None:
79 self.vnfds['vnfds'] = []
80 self.vnfds['pkgs'] = []
81 self.vnfds['vnfds'].append(vnfd)
82 if pkg:
83 self.vnfds['pkgs'].append(pkg)
84
85 def add_vnfd(self, nsd_id, vnfd, pkg=None):
86 if not 'vnfds' in self.nsds[nsd_id]:
87 self.nsds[nsd_id]['vnfds'] = []
88 self.nsds[nsd_id]['vnfds'].append(vnfd)
89 if pkg:
90 self.nsds[nsd_id]['pkgs'].append(pkg)
91
92 def add_nsd(self, nsd, pkg=None):
93 nsd_id = str(uuid.uuid4())
94 self.nsds[nsd_id] = {'nsd': nsd}
95 self.nsds[nsd_id]['pkgs'] = []
96 if pkg:
97 self.nsds[nsd_id]['pkgs'].append(pkg)
98 return nsd_id
99
100 def create_csar(self, nsd_id, dest=None):
101 if dest is None:
102 dest = tempfile.mkdtemp()
103
104 # Convert YANG to dict
105 yangs = {}
106 yangs['vnfd'] = []
107 for vnfd in self.nsds[nsd_id]['vnfds']:
108 yangs['vnfd'].append(vnfd.as_dict())
109 self.log.debug("Translate VNFD: {}".format(vnfd.as_dict()))
110 yangs['nsd'] = []
111 yangs['nsd'].append(self.nsds[nsd_id]['nsd'].as_dict())
112 self.log.debug("Translate NSD : {}".format(yangs['nsd']))
113
114 # Translate YANG model to TOSCA template
115 translator = YangTranslator(self.log,
116 yangs=yangs,
117 packages=self.nsds[nsd_id]['pkgs'])
118 output = translator.translate()
119 self.csars.extend(translator.write_output(output,
120 output_dir=dest,
121 archive=True))
122 self.log.debug("Created CSAR archive {}".format(self.csars[-1]))
123
124 def create_vnfd_csar(self, dest=None):
125 if dest is None:
126 dest = tempfile.mkdtemp()
127 yangs = {}
128 yangs['vnfd'] = []
129 for vnfd in self.vnfds['vnfds']:
130 yangs['vnfd'].append(vnfd.as_dict())
131 translator = YangTranslator(self.log,
132 yangs=yangs,
133 packages=self.vnfds['pkgs'])
134 output = translator.translate()
135 self.csars.extend(translator.write_output(output,
136 output_dir=dest,
137 archive=True))
138 self.log.debug("Created CSAR archive {}".format(self.csars[-1]))
139
140
141 def create_archive(self, archive_name, dest=None):
142 if not len(self.nsds) and len(self.vnfds) == 0:
143 self.log.error("Did not find any NSDs to export")
144 return
145
146 if dest is None:
147 dest = tempfile.mkdtemp()
148
149 prevdir = os.getcwd()
150
151 if not os.path.exists(dest):
152 os.makedirs(dest)
153
154 try:
155 # Convert each NSD to a TOSCA template
156 if len(self.nsds) > 0:
157 for nsd_id in self.nsds:
158 # Not passing the dest dir to prevent clash in case
159 # multiple export of the same desc happens
160 self.create_csar(nsd_id)
161 elif len(self.vnfds) > 0:
162 self.create_vnfd_csar()
163
164 except Exception as e:
165 msg = "Exception converting NSD/VNFD {}".format(e)
166 self.log.exception(e)
167 raise YangTranslateNsdError(msg)
168
169 os.chdir(dest)
170
171 try:
172 if archive_name.endswith(".zip"):
173 archive_name = archive_name[:-4]
174
175 archive_path = os.path.join(dest, archive_name)
176
177 # Construct a zip of the csar archives
178 zip_name = '{}.zip'.format(archive_path)
179
180 if len(self.csars) == 1:
181 # Only 1 TOSCA template, just rename csar if required
182 if self.csars[0] != zip_name:
183 mv_cmd = "mv {} {}".format(self.csars[0], zip_name)
184 subprocess.check_call(mv_cmd, shell=True, stdout=subprocess.DEVNULL)
185 # Remove the temporary directory created
186 shutil.rmtree(os.path.dirname(self.csars[0]))
187
188 else:
189 with zipfile.ZipFile('{}.partial'.format(zip_name), 'w') as zf:
190 for csar in self.csars:
191 # Move file to the current dest dir
192 if os.path.dirname(csar) != dest:
193 file_mv = "mv {} {}".format(csar, dest)
194 subprocess.check_call(file_mv,
195 shell=True,
196 stdout=subprocess.DEVNULL)
197 # Remove the temporary directory created
198 shutil.rmtree(os.path.dirname(csar))
199
200 csar_f = os.basename(csar)
201 # Now add to the archive
202 zf.write(csar_f)
203 # Remove the csar file
204 os.remove(csar_f)
205
206 # Rename archive to final name
207 mv_cmd = "mv {0}.partial {0}".format(zip_name)
208 subprocess.check_call(mv_cmd, shell=True, stdout=subprocess.DEVNULL)
209
210 return zip_name
211
212 except Exception as e:
213 msg = "Creating CSAR archive failed: {0}".format(e)
214 self.log.exception(e)
215 raise YangTranslateError(msg)
216
217 finally:
218 os.chdir(prevdir)
219
220 class ImportTosca(object):
221
222 def __init__(self, log, in_file, out_dir=None):
223 if log is None:
224 self.log = logging.getLogger("rw-mano-log")
225 else:
226 self.log = log
227 self.log = log
228 self.in_file = in_file
229 self.out_dir = out_dir
230
231 def translate(self):
232 # Check if the input file is a zip file
233 if not zipfile.is_zipfile(self.in_file):
234 err_msg = "{} is not a zip file.".format(self.in_file)
235 self.log.error(err_msg)
236 raise InvalidToscaPackageError(err_msg)
237
238 try:
239 # Store the current working directory
240 prevdir = os.getcwd()
241
242 # See if we need to create a output directory
243 out_dir = self.out_dir
244 if out_dir is None:
245 out_dir = tempfile.mkdtemp()
246
247 # Call the TOSCA translator
248 self.log.debug("Calling tosca-translator for {}".
249 format(self.in_file))
250 return TranslatorShell(self.log).translate(self.in_file,
251 out_dir,
252 archive=True)
253
254 except Exception as e:
255 self.log.exception(e)
256 raise ToscaTranslateError("Error translating TOSCA package {}: {}".
257 format(self.in_file, e))
258
259 finally:
260 os.chdir(prevdir)
261
262 @staticmethod
263 def is_tosca_package(in_file):
264 if zipfile.is_zipfile(in_file):
265 return True
266 else:
267 return False
268
269