8ccc899f88cbfc5765a7416325ff8c16cea7f0e6
[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
65 def add_image(self, nsd_id, image, chksum=None):
66 if image.name not in self.images:
67 self.images[image.name] = image
68
69 def add_vld(self, nsd_id, vld, pkg=None):
70 if not 'vlds' in self.nsds[nsd_id]:
71 self.nsds[nsd_id]['vlds'] = []
72 self.nsds[nsd_id]['vlds'].append(vld)
73 if pkg:
74 self.nsds[nsd_id]['pkgs'].append(pkg)
75
76 def add_vnfd(self, nsd_id, vnfd, pkg=None):
77 if not 'vnfds' in self.nsds[nsd_id]:
78 self.nsds[nsd_id]['vnfds'] = []
79 self.nsds[nsd_id]['vnfds'].append(vnfd)
80 if pkg:
81 self.nsds[nsd_id]['pkgs'].append(pkg)
82
83 def add_nsd(self, nsd, pkg=None):
84 nsd_id = str(uuid.uuid4())
85 self.nsds[nsd_id] = {'nsd': nsd}
86 self.nsds[nsd_id]['pkgs'] = []
87 if pkg:
88 self.nsds[nsd_id]['pkgs'].append(pkg)
89 return nsd_id
90
91 def create_csar(self, nsd_id, dest=None):
92 if dest is None:
93 dest = tempfile.mkdtemp()
94
95 # Convert YANG to dict
96 yangs = {}
97 yangs['vnfd'] = []
98 for vnfd in self.nsds[nsd_id]['vnfds']:
99 yangs['vnfd'].append(vnfd.as_dict())
100 self.log.debug("Translate VNFD: {}".format(vnfd.as_dict()))
101 yangs['nsd'] = []
102 yangs['nsd'].append(self.nsds[nsd_id]['nsd'].as_dict())
103 self.log.debug("Translate NSD : {}".format(yangs['nsd']))
104
105 # Translate YANG model to TOSCA template
106 translator = YangTranslator(self.log,
107 yangs=yangs,
108 packages=self.nsds[nsd_id]['pkgs'])
109 output = translator.translate()
110 self.csars.extend(translator.write_output(output,
111 output_dir=dest,
112 archive=True))
113 self.log.debug("Created CSAR archive {}".format(self.csars[-1]))
114
115 def create_archive(self, archive_name, dest=None):
116 if not len(self.nsds):
117 self.log.error("Did not find any NSDs to export")
118 return
119
120 if dest is None:
121 dest = tempfile.mkdtemp()
122
123 prevdir = os.getcwd()
124
125 if not os.path.exists(dest):
126 os.makedirs(dest)
127
128 try:
129 # Convert each NSD to a TOSCA template
130 for nsd_id in self.nsds:
131 # Not passing the dest dir to prevent clash in case
132 # multiple export of the same desc happens
133 self.create_csar(nsd_id)
134
135 except Exception as e:
136 msg = "Exception converting NSD {}: {}".format(nsd_id, e)
137 self.log.exception(e)
138 raise YangTranslateNsdError(msg)
139
140 os.chdir(dest)
141
142 try:
143 if archive_name.endswith(".zip"):
144 archive_name = archive_name[:-4]
145
146 archive_path = os.path.join(dest, archive_name)
147
148 # Construct a zip of the csar archives
149 zip_name = '{}.zip'.format(archive_path)
150
151 if len(self.csars) == 1:
152 # Only 1 TOSCA template, just rename csar if required
153 if self.csars[0] != zip_name:
154 mv_cmd = "mv {} {}".format(self.csars[0], zip_name)
155 subprocess.check_call(mv_cmd, shell=True, stdout=subprocess.DEVNULL)
156 # Remove the temporary directory created
157 shutil.rmtree(os.path.dirname(self.csars[0]))
158
159 else:
160 with zipfile.ZipFile('{}.partial'.format(zip_name), 'w') as zf:
161 for csar in self.csars:
162 # Move file to the current dest dir
163 if os.path.dirname(csar) != dest:
164 file_mv = "mv {} {}".format(csar, dest)
165 subprocess.check_call(file_mv,
166 shell=True,
167 stdout=subprocess.DEVNULL)
168 # Remove the temporary directory created
169 shutil.rmtree(os.path.dirname(csar))
170
171 csar_f = os.basename(csar)
172 # Now add to the archive
173 zf.write(csar_f)
174 # Remove the csar file
175 os.remove(csar_f)
176
177 # Rename archive to final name
178 mv_cmd = "mv {0}.partial {0}".format(zip_name)
179 subprocess.check_call(mv_cmd, shell=True, stdout=subprocess.DEVNULL)
180
181 return zip_name
182
183 except Exception as e:
184 msg = "Creating CSAR archive failed: {0}".format(e)
185 self.log.exception(e)
186 raise YangTranslateError(msg)
187
188 finally:
189 os.chdir(prevdir)
190
191 class ImportTosca(object):
192
193 def __init__(self, log, in_file, out_dir=None):
194 if log is None:
195 self.log = logging.getLogger("rw-mano-log")
196 else:
197 self.log = log
198 self.log = log
199 self.in_file = in_file
200 self.out_dir = out_dir
201
202 def translate(self):
203 # Check if the input file is a zip file
204 if not zipfile.is_zipfile(self.in_file):
205 err_msg = "{} is not a zip file.".format(self.in_file)
206 self.log.error(err_msg)
207 raise InvalidToscaPackageError(err_msg)
208
209 try:
210 # Store the current working directory
211 prevdir = os.getcwd()
212
213 # See if we need to create a output directory
214 out_dir = self.out_dir
215 if out_dir is None:
216 out_dir = tempfile.mkdtemp()
217
218 # Call the TOSCA translator
219 self.log.debug("Calling tosca-translator for {}".
220 format(self.in_file))
221 return TranslatorShell(self.log).translate(self.in_file,
222 out_dir,
223 archive=True)
224
225 except Exception as e:
226 self.log.exception(e)
227 raise ToscaTranslateError("Error translating TOSCA package {}: {}".
228 format(self.in_file, e))
229
230 finally:
231 os.chdir(prevdir)
232
233 @staticmethod
234 def is_tosca_package(in_file):
235 if zipfile.is_zipfile(in_file):
236 return True
237 else:
238 return False
239
240