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