blob: 10f821e897554de63f8ef9f6fc7c9de1edb62ad8 [file] [log] [blame]
Thomas Vachuskaad37e372017-08-03 12:07:01 -07001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.yang.impl;
18
19import com.google.common.io.ByteStreams;
20import com.google.common.io.Files;
21import org.apache.felix.scr.annotations.Activate;
22import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Deactivate;
24import org.apache.felix.scr.annotations.Service;
Ray Milkey74c98a32018-07-26 10:05:49 -070025import org.onlab.util.FilePathValidator;
Thomas Vachuskaad37e372017-08-03 12:07:01 -070026import org.onosproject.yang.YangLiveCompilerService;
27import org.onosproject.yang.compiler.tool.DefaultYangCompilationParam;
28import org.onosproject.yang.compiler.tool.YangCompilerManager;
29import org.slf4j.Logger;
30import org.slf4j.LoggerFactory;
31
32import java.io.ByteArrayInputStream;
33import java.io.File;
34import java.io.FileInputStream;
35import java.io.FileOutputStream;
36import java.io.IOException;
37import java.io.InputStream;
38import java.nio.charset.StandardCharsets;
39import java.nio.file.FileVisitResult;
40import java.nio.file.Path;
41import java.nio.file.Paths;
42import java.nio.file.SimpleFileVisitor;
43import java.nio.file.attribute.BasicFileAttributes;
44import java.util.zip.ZipEntry;
45import java.util.zip.ZipInputStream;
46
47import static com.google.common.io.ByteStreams.toByteArray;
48import static com.google.common.io.Files.createParentDirs;
49import static com.google.common.io.Files.write;
50import static java.nio.file.Files.walkFileTree;
51
52/**
53 * Represents implementation of YANG live compiler manager.
54 */
55@Service
56@Component(immediate = true)
57public class YangLiveCompilerManager implements YangLiveCompilerService {
58
59 private final Logger log = LoggerFactory.getLogger(getClass());
60
61 private static final String ZIP_MAGIC = "PK";
62
63 @Activate
64 public void activate() {
65 log.info("Started");
66 }
67
68 @Deactivate
69 public void deactivate() {
70 log.info("Stopped");
71 }
72
73 @Override
74 public InputStream compileYangFiles(String modelId,
75 InputStream yangSources) throws IOException {
76 // Generate temporary directory where the work will happen.
77 File root = Files.createTempDir();
78 log.info("Compiling YANG model to {}", root);
79
80 // Unpack the input stream
81 File yangRoot = unpackYangSources(root, yangSources);
82
83 // Run the YANG compilation phase
84 File javaRoot = runYangCompiler(root, yangRoot, modelId);
85
86 // Run the Java compilation phase
87 File classRoot = runJavaCompiler(root, javaRoot, modelId);
88
89 // Run the JAR assembly phase
90 File jarFile = runJarAssembly(root, classRoot, modelId);
91
92 // Return the final JAR file as input stream
93 return new FileInputStream(jarFile);
94 }
95
96 // Unpacks the given input stream into the YANG root subdirectory of the specified root directory.
97 private File unpackYangSources(File root, InputStream yangSources) throws IOException {
98 File yangRoot = new File(root, "yang/");
99 if (yangRoot.mkdirs()) {
100 // Unpack the yang sources into the newly created directory
101 byte[] cache = toByteArray(yangSources);
102 InputStream bis = new ByteArrayInputStream(cache);
103 if (isZipArchive(cache)) {
104 extractZipArchive(yangRoot, bis);
105 } else {
106 extractYangFile(yangRoot, bis);
107 }
108 return yangRoot;
109 }
110 throw new IOException("Unable to create yang source root");
111 }
112
113 // Extracts the YANG source stream into the specified directory.
114 private void extractYangFile(File dir, InputStream stream) throws IOException {
115 ByteStreams.copy(stream, new FileOutputStream(new File(dir, "model.yang")));
116 }
117
118 // Extracts the ZIP stream into the specified directory.
119 private void extractZipArchive(File dir, InputStream stream) throws IOException {
120 ZipInputStream zis = new ZipInputStream(stream);
121 ZipEntry entry;
122 while ((entry = zis.getNextEntry()) != null) {
Ray Milkey74c98a32018-07-26 10:05:49 -0700123 if (FilePathValidator.validateZipEntry(entry, dir)) {
Ray Milkey351d4562018-07-25 12:31:48 -0700124 if (!entry.isDirectory()) {
125 byte[] data = toByteArray(zis);
126 zis.closeEntry();
127 File file = new File(dir, entry.getName());
128 createParentDirs(file);
129 write(data, file);
130 }
131 } else {
132 throw new IOException("Zip archive is attempting to create a file outside of its root");
Thomas Vachuskaad37e372017-08-03 12:07:01 -0700133 }
134 }
135 zis.close();
136 }
137
138 // Runs the YANG compiler on the YANG sources in the specified directory.
139 private File runYangCompiler(File root, File yangRoot, String modelId) throws IOException {
140 File javaRoot = new File(root, "java/");
141 if (javaRoot.mkdirs()) {
142 // Prepare the compilation parameter
143 DefaultYangCompilationParam.Builder param = DefaultYangCompilationParam.builder()
144 .setCodeGenDir(new File(javaRoot, "src").toPath())
145 .setMetadataGenDir(new File(javaRoot, "schema").toPath())
146 .setModelId(modelId);
147
148 // TODO: How to convey YANG dependencies? "/dependencies" directory?
149
150 // Iterate over all files and add all YANG sources.
151 walkFileTree(Paths.get(yangRoot.getAbsolutePath()),
152 new SimpleFileVisitor<Path>() {
153 @Override
154 public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
155 throws IOException {
156 if (attributes.isRegularFile() && file.toString().endsWith(".yang")) {
157 param.addYangFile(file);
158 }
159 return FileVisitResult.CONTINUE;
160 }
161 });
162
163 // Run the YANG compiler and collect the results
164 new YangCompilerManager().compileYangFiles(param.build());
165 return javaRoot;
166 }
167 throw new IOException("Unable to create Java results root");
168 }
169
170 // Runs the Java compilation on the Java sources generated by YANG compiler.
171 private File runJavaCompiler(File root, File javaRoot, String modelId) throws IOException {
172 File classRoot = new File(root, "classes/");
173 if (classRoot.mkdirs()) {
174 File compilerScript = writeResource("onos-yang-javac", root);
175 writeResource("YangModelRegistrator.java", root);
176 execute(new String[]{
177 "bash",
178 compilerScript.getAbsolutePath(),
179 javaRoot.getAbsolutePath(),
180 classRoot.getAbsolutePath(),
181 modelId
182 });
183 return classRoot;
184 }
185 throw new IOException("Unable to create class results root");
186 }
187
188 // Run the JAR assembly on the classes root and include any YANG sources as well.
189 private File runJarAssembly(File root, File classRoot, String modelId) throws IOException {
190 File jarFile = new File(root, "model.jar");
191 File jarScript = writeResource("onos-yang-jar", root);
192 writeResource("app.xml", root);
193 writeResource("features.xml", root);
194 writeResource("YangModelRegistrator.xml", root);
195 execute(new String[]{
196 "bash",
197 jarScript.getAbsolutePath(),
198 classRoot.getAbsolutePath(),
199 jarFile.getAbsolutePath(),
200 modelId
201 });
202 return jarFile;
203 }
204
205 // Writes the specified resource as a file in the given directory.
206 private File writeResource(String resourceName, File dir) throws IOException {
207 File script = new File(dir, resourceName);
208 write(toByteArray(getClass().getResourceAsStream("/" + resourceName)), script);
209 return script;
210 }
211
212 // Indicates whether the stream encoded in the given bytes is a ZIP archive.
213 private boolean isZipArchive(byte[] bytes) {
214 return substring(bytes, ZIP_MAGIC.length()).equals(ZIP_MAGIC);
215 }
216
217 // Returns the substring of maximum possible length from the specified bytes.
218 private String substring(byte[] bytes, int length) {
219 return new String(bytes, 0, Math.min(bytes.length, length), StandardCharsets.UTF_8);
220 }
221
222 // Executes the given command arguments as a system command.
223 private void execute(String[] command) throws IOException {
224 try {
225 Process process = Runtime.getRuntime().exec(command);
226 byte[] output = toByteArray(process.getInputStream());
227 byte[] error = toByteArray(process.getErrorStream());
228 int code = process.waitFor();
229 if (code != 0) {
230 log.info("Command failed: status={}, output={}, error={}",
231 code, new String(output), new String(error));
232 }
233 } catch (InterruptedException e) {
234 log.error("Interrupted executing command {}", command, e);
Ray Milkey5c7d4882018-02-05 14:50:39 -0800235 Thread.currentThread().interrupt();
Thomas Vachuskaad37e372017-08-03 12:07:01 -0700236 }
237 }
238}