Skip to content

Commit

Permalink
Update service runtime [ECR-3588] (#1131)
Browse files Browse the repository at this point in the history
Update the ServiceRuntime to the newer Runtime specification
from the core (as of exonum/exonum@fd3b037):

* Rename #configureService to #initializeService to not conflict
with service re-configuration, which is a separate and 
upcoming feature.

* Update #initialize and #deploy to work with serialized messages
instead of protobuf.Any.

* Add ServiceRuntime#isArtifactDeployed
  • Loading branch information
dmitry-timofeev authored Oct 2, 2019
1 parent 3789142 commit 4b56ff0
Show file tree
Hide file tree
Showing 14 changed files with 104 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,29 @@

package com.exonum.binding.core.runtime;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.exonum.binding.common.serialization.Serializer;
import com.exonum.binding.common.serialization.StandardSerializers;
import com.exonum.binding.core.service.Configuration;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import java.util.Objects;
import com.google.protobuf.MessageLite;
import java.util.Arrays;

final class ServiceConfiguration implements Configuration {

private final Any configuration;
private final byte[] configuration;

ServiceConfiguration(Any configuration) {
this.configuration = checkNotNull(configuration);
ServiceConfiguration(byte[] configuration) {
this.configuration = configuration.clone();
}

@Override
public <MessageT extends Message> MessageT getAsMessage(Class<MessageT> parametersType) {
checkArgument(configuration.is(parametersType), "Actual type of the configuration message "
+ "specified by the network administrators (%s) does not match requested (%s)",
configuration.getTypeUrl(), parametersType.getTypeName());

try {
return configuration.unpack(parametersType);
} catch (InvalidProtocolBufferException e) {
String message = String.format("Cannot unpack Any into %s message",
parametersType.getTypeName());
throw new IllegalArgumentException(message, e);
}
public <MessageT extends MessageLite> MessageT getAsMessage(Class<MessageT> parametersType) {
Serializer<MessageT> serializer = StandardSerializers.protobuf(parametersType);
return serializer.fromBytes(configuration);
}

@Override
public String toString() {
return "ServiceConfiguration{" + configuration + '}';
return "ServiceConfiguration{" + Arrays.toString(configuration) + '}';
}

@Override
Expand All @@ -62,11 +50,11 @@ public boolean equals(Object o) {
return false;
}
ServiceConfiguration that = (ServiceConfiguration) o;
return Objects.equals(configuration, that.configuration);
return Arrays.equals(configuration, that.configuration);
}

@Override
public int hashCode() {
return Objects.hash(configuration);
return Arrays.hashCode(configuration);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
Expand Down Expand Up @@ -153,6 +152,17 @@ public void deployArtifact(ServiceArtifactId id, String filename)
}
}

/**
* Returns true if an artifact with the given id is currently deployed in this runtime.
* @param id a service artifact identifier
*/
public boolean isArtifactDeployed(ServiceArtifactId id) {
synchronized (lock) {
return serviceLoader.findService(id)
.isPresent();
}
}

/**
* Creates a new service instance with the given specification. This method registers
* the service API.
Expand Down Expand Up @@ -200,18 +210,19 @@ private void registerService(ServiceWrapper service) {
}

/**
* Configures the service instance.
* Performs an initial configuration of the service instance.
*
* @param id the id of the started service
* @param view a database view to apply configuration
* @param configuration service instance configuration parameters
* @param configuration service instance configuration parameters as a serialized protobuf
* message
*/
public void configureService(Integer id, Fork view, Any configuration) {
public void initializeService(Integer id, Fork view, byte[] configuration) {
synchronized (lock) {
ServiceWrapper service = getServiceById(id);
try {
Configuration config = new ServiceConfiguration(configuration);
service.configure(view, config);
service.initialize(view, config);
} catch (Exception e) {
String name = service.getName();
logger.error("Service {} configuration with parameters {} failed",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@

package com.exonum.binding.core.runtime;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.io.BaseEncoding.base16;

import com.exonum.binding.common.crypto.PublicKey;
import com.exonum.binding.common.hash.HashCode;
import com.exonum.binding.core.proxy.Cleaner;
Expand All @@ -33,7 +30,6 @@
import com.exonum.binding.core.storage.database.Snapshot;
import com.exonum.binding.core.transaction.TransactionContext;
import com.exonum.binding.core.transaction.TransactionExecutionException;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import java.util.OptionalInt;
import org.apache.logging.log4j.LogManager;
Expand Down Expand Up @@ -61,9 +57,9 @@ class ServiceRuntimeAdapter {
* Deploys the Java service artifact.
*
* @param id the service artifact id in format "groupId:artifactId:version"
* @param deploySpec the deploy specification as a serialized {@link com.google.protobuf.Any}
* @param deploySpec the deploy specification as a serialized
* {@link com.exonum.binding.core.runtime.ServiceRuntimeProtos.DeployArguments}
* protobuf message
* with {@link com.exonum.binding.core.runtime.ServiceRuntimeProtos.DeployArguments}
* @throws IllegalArgumentException if the deploy specification or id are not valid
* @throws ServiceLoadingException if the runtime failed to load the service or it is not correct
* @see ServiceRuntime#deployArtifact(ServiceArtifactId, String)
Expand All @@ -75,15 +71,22 @@ void deployArtifact(String id, byte[] deploySpec) throws ServiceLoadingException
serviceRuntime.deployArtifact(ServiceArtifactId.parseFrom(id), artifactFilename);
}

/**
* Returns true if the artifact with the given id is deployed in this runtime; false — otherwise.
* @param id the service artifact id in format "groupId:artifactId:version"
*/
boolean isArtifactDeployed(String id) {
ServiceArtifactId artifactId = ServiceArtifactId.parseFrom(id);
return serviceRuntime.isArtifactDeployed(artifactId);
}

private static DeployArguments unpackDeployArgs(String id, byte[] deploySpec) {
try {
Any anyDeploySpec = Any.parseFrom(deploySpec);
checkArgument(anyDeploySpec.is(DeployArguments.class), "Deploy specification "
+ "for %s contains message of unexpected type inside Any (%s), must be %s",
id, anyDeploySpec.getTypeUrl(), DeployArguments.getDescriptor().getFullName());
return anyDeploySpec.unpack(DeployArguments.class);
return DeployArguments.parseFrom(deploySpec);
} catch (InvalidProtocolBufferException e) {
throw new IllegalArgumentException("Invalid deploy specification for " + id, e);
String message = "Invalid deploy specification for " + id;
logger.error(message, e);
throw new IllegalArgumentException(message, e);
}
}

Expand All @@ -104,37 +107,25 @@ void createService(String name, int id, String artifactId) {
}

/**
* Configures a started service instance.
* Performs an initial configuration of a started service instance.
*
* @param id the id of the service
* @param forkHandle a handle to a native fork object
* @param configuration the service configuration parameters as serialized protobuf Any
* @param configuration the service configuration parameters as a serialized protobuf message
* @throws CloseFailuresException if there was a failure in destroying some native peers
* @see ServiceRuntime#configureService(Integer, Fork, Any)
* @see ServiceRuntime#initializeService(Integer, Fork, byte[])
*/
void configureService(int id, long forkHandle, byte[] configuration)
void initializeService(int id, long forkHandle, byte[] configuration)
throws CloseFailuresException {
try (Cleaner cleaner = new Cleaner()) {
Fork fork = viewFactory.createFork(forkHandle, cleaner);
Any parsedConfig = decodeConfiguration(configuration);

serviceRuntime.configureService(id, fork, parsedConfig);
serviceRuntime.initializeService(id, fork, configuration);
} catch (CloseFailuresException e) {
handleCloseFailure(e);
}
}

private static Any decodeConfiguration(byte[] configuration) {
try {
return Any.parseFrom(configuration);
} catch (InvalidProtocolBufferException e) {
String message = String.format("Could not parse the config (%s) as Any",
base16().encode(configuration));
logger.error(message, e);
throw new IllegalArgumentException(message, e);
}
}

/**
* Stops a service instance with the given id.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ int getId() {
return instanceSpec.getId();
}

void configure(Fork view, Configuration configuration) {
service.configure(view, configuration);
void initialize(Fork view, Configuration configuration) {
service.initialize(view, configuration);
}

void executeTransaction(int txId, byte[] arguments, TransactionContext context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@
package com.exonum.binding.core.service;

import com.exonum.binding.core.storage.database.Fork;
import com.google.protobuf.Message;
import com.google.protobuf.MessageLite;

/**
* Configuration parameters of Exonum service. Network administrators agree on and pass
* the configuration parameters as a service-specific protobuf message in the transactions
* which add that service instance to the network. After Exonum starts the service, it
* {@linkplain Service#configure(Fork, Configuration) passes the configuration parameters}
* {@linkplain Service#initialize(Fork, Configuration) passes the configuration parameters}
* to the newly created service instance.
*
* <p>Service reconfiguration is not currently supported, but will be added soon.
*
* @see Service#configure(Fork, Configuration)
* @see Service#initialize(Fork, Configuration)
*/
public interface Configuration {

Expand All @@ -43,6 +43,6 @@ public interface Configuration {
* an error in the service itself
* @see com.exonum.binding.common.serialization.StandardSerializers#protobuf(Class)
*/
<MessageT extends Message> MessageT getAsMessage(Class<MessageT> parametersType);
<MessageT extends MessageLite> MessageT getAsMessage(Class<MessageT> parametersType);

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
public interface Service {

/**
* Configures the service instance. This method is called <em>once</em> after the service
* instance is added to the blockchain and allows initializing some persistent data
* of the service.
* Performs an initial configuration of the service instance. This method is called <em>once</em>
* after the service instance is added to the blockchain and allows initializing
* some persistent data of the service.
*
* <p>As Exonum passes the configuration parameters only once and does not persist them for
* later access, this service method must make any needed changes to the database based
Expand All @@ -48,9 +48,9 @@ public interface Service {
* @param configuration the service configuration parameters
* @throws IllegalArgumentException if the configuration parameters are not valid (e.g.,
* malformed, or do not meet the preconditions). Exonum will stop the service if
* its configuration fails
* its initialization fails
*/
default void configure(Fork fork, Configuration configuration) {
default void initialize(Fork fork, Configuration configuration) {
// No configuration
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,23 @@

package com.exonum.binding.core.runtime;

import static com.exonum.binding.test.Bytes.bytes;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

import com.exonum.binding.core.storage.indices.TestProtoMessages.Id;
import com.exonum.binding.core.storage.indices.TestProtoMessages.Point;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import nl.jqno.equalsverifier.EqualsVerifier;
import org.junit.jupiter.api.Test;

class ServiceConfigurationTest {

@Test
void getAsMessage() {
// Pack the config in Any as core does
Id config = id("1");
Any configInTx = Any.pack(config);
Id config = anyId();
byte[] serializedConfig = config.toByteArray();

ServiceConfiguration serviceConfiguration = new ServiceConfiguration(configInTx);
ServiceConfiguration serviceConfiguration = new ServiceConfiguration(serializedConfig);

// Decode the config
Id unpackedConfig = serviceConfiguration.getAsMessage(Id.class);
Expand All @@ -42,31 +41,28 @@ void getAsMessage() {
}

@Test
void getAsMessageWrongType() {
// Pack the config in Any as core does
Id config = id("1");
Any configInTx = Any.pack(config);
void getAsMessageNotMessage() {
// Not a valid serialized Id
byte[] serializedConfig = bytes(1, 2, 3, 4);

ServiceConfiguration serviceConfiguration = new ServiceConfiguration(configInTx);
ServiceConfiguration serviceConfiguration = new ServiceConfiguration(serializedConfig);

// Try to decode the config
Exception e = assertThrows(IllegalArgumentException.class,
() -> serviceConfiguration.getAsMessage(Point.class));
() -> serviceConfiguration.getAsMessage(Id.class));

assertThat(e).hasMessageContainingAll(Id.getDescriptor().getName(),
Point.getDescriptor().getName());
assertThat(e).hasCauseInstanceOf(InvalidProtocolBufferException.class);
}

@Test
void verifyEquals() {
EqualsVerifier.forClass(ServiceConfiguration.class)
.withPrefabValues(Any.class, Any.pack(id("Red")), Any.pack(id("Black")))
.verify();
}

private static Id id(String id) {
private static Id anyId() {
return Id.newBuilder()
.setId(id)
.setId("12ab")
.build();
}
}
Loading

0 comments on commit 4b56ff0

Please sign in to comment.