blob: 1ad50d2d30f3b05d2e496fdf36b478c9fedfaf63 [file] [log] [blame]
/*
* Copyright 2019-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.drivers.gnoi;
import com.google.protobuf.ByteString;
import gnoi.system.SystemOuterClass;
import gnoi.system.SystemOuterClass.SetPackageRequest;
import gnoi.system.SystemOuterClass.SetPackageResponse;
import gnoi.system.SystemOuterClass.SwitchControlProcessorRequest;
import gnoi.types.Types;
import org.onosproject.gnoi.api.GnoiClient;
import org.onosproject.gnoi.api.GnoiController;
import org.onosproject.grpc.utils.AbstractGrpcHandlerBehaviour;
import org.onosproject.net.behaviour.SoftwareUpgrade;
import org.apache.commons.io.FilenameUtils;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.UUID;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.xml.bind.DatatypeConverter;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Implementation that upgrades the software on a device.
*/
public class GnoiSoftwareUpgradeImpl
extends AbstractGrpcHandlerBehaviour<GnoiClient, GnoiController>
implements SoftwareUpgrade {
private static final String HASHING_METHOD = "MD5";
private static final int MAX_CHUNK_SIZE = 64_000;
private static final Path DEFAULT_PACKAGE_PATH = Paths.get("/tmp");
private static final int STREAM_TIMEOUT_SECONDS = 10;
public GnoiSoftwareUpgradeImpl() {
super(GnoiController.class);
}
@Override
public CompletableFuture<String> uploadPackage(String sourcePath, String dst) {
if (!setupBehaviour("uploadPackage()")) {
return CompletableFuture.completedFuture(null);
}
checkNotNull(sourcePath, "Source file not specified.");
final CompletableFuture<String> future = new CompletableFuture<>();
final File deb = Paths.get(sourcePath).toFile();
final String destinationPath;
final SetPackageRequest.Builder requestBuilder = SetPackageRequest.newBuilder();
final SystemOuterClass.Package.Builder pkgBuilder = SystemOuterClass.Package.newBuilder();
final Types.HashType.Builder hashBuilder = Types.HashType.newBuilder();
final SynchronousQueue<SetPackageRequest> stream = new SynchronousQueue<>();
final CompletableFuture<SetPackageResponse> futureResponse = client.setPackage(stream);
if (dst == null) {
destinationPath = getTempPath(sourcePath);
} else {
destinationPath = dst;
}
futureResponse.whenComplete((response, exception) -> {
if (exception == null) {
future.complete(destinationPath);
} else {
future.complete(null);
}
});
// Handle reading file, creating requests, etc...
CompletableFuture.runAsync(() -> {
try {
if (!deb.isFile()) {
log.error("File {} does not exist", sourcePath);
future.complete(null);
return;
}
// Set general package info (filename, version, etc...).
pkgBuilder.setFilename(destinationPath);
requestBuilder.setPackage(pkgBuilder.build());
boolean requestSent = stream.offer(requestBuilder.build(), STREAM_TIMEOUT_SECONDS, TimeUnit.SECONDS);
if (!requestSent) {
future.complete(null);
return;
}
final MessageDigest md = MessageDigest.getInstance(HASHING_METHOD);
final FileInputStream buffer = new FileInputStream(deb);
byte[] contents = new byte[MAX_CHUNK_SIZE];
int read = 0;
// Read file in 64k chunks.
while ((read = buffer.read(contents, 0, MAX_CHUNK_SIZE)) != -1) {
// Calculate File hash.
md.update(contents, 0, read);
// Form next request.
requestBuilder.setContents(ByteString.copyFrom(contents, 0, read));
// Add file chunk to stream.
requestSent = stream.offer(requestBuilder.build(), STREAM_TIMEOUT_SECONDS, TimeUnit.SECONDS);
if (!requestSent) {
future.complete(null);
return;
}
}
// Convert hash to lowercase string.
String hash = DatatypeConverter
.printHexBinary(md.digest())
.toLowerCase();
hashBuilder
.setMethodValue(Types.HashType.HashMethod.MD5.getNumber())
.setHash(ByteString.copyFrom(hash.getBytes()));
// Form last request with file hash.
requestBuilder.setHash(hashBuilder.build());
// Add file chunk to stream.
requestSent = stream.offer(requestBuilder.build(), STREAM_TIMEOUT_SECONDS, TimeUnit.SECONDS);
if (!requestSent) {
future.complete(null);
return;
}
} catch (IOException e) {
log.error("Error while reading file {}", sourcePath, e);
future.complete(null);
return;
} catch (InterruptedException e) {
log.error("Interrupted while sending package", e);
future.complete(null);
return;
} catch (SecurityException e) {
log.error("File {} cannot be accessed", sourcePath, e);
future.complete(null);
return;
} catch (NoSuchAlgorithmException e) {
log.error("Invalid hashing algorithm {}", HASHING_METHOD, e);
future.complete(null);
return;
}
});
return future;
}
@Override
public CompletableFuture<Response> swapAgent(String packagePath) {
if (!setupBehaviour("swapAgent()")) {
return CompletableFuture.completedFuture(new Response());
}
checkNotNull(packagePath, "File path not specified.");
final CompletableFuture<Response> future = new CompletableFuture<>();
final Types.Path.Builder routeProcessor = Types.Path.newBuilder();
final SwitchControlProcessorRequest.Builder requestMsg = SwitchControlProcessorRequest.newBuilder();
Paths.get(packagePath)
.iterator()
.forEachRemaining(part -> {
routeProcessor.addElem(
Types.PathElem.newBuilder()
.setName(part.toString())
.build());
});
requestMsg.setControlProcessor(routeProcessor.build());
client.switchControlProcessor(requestMsg.build())
.whenComplete((response, exception) -> {
if (exception != null) {
future.complete(new Response());
} else {
future.complete(new Response(response.getUptime(), response.getVersion()));
}
});
return future;
}
private String getTempPath(String source) {
String baseName = FilenameUtils.getBaseName(source);
String extension = FilenameUtils.getExtension(source);
if (extension.length() != 0) {
extension = "." + extension;
}
String filename = baseName + "_" + UUID.randomUUID().toString() + extension;
return DEFAULT_PACKAGE_PATH.resolve(filename).toString();
}
}