diff options
25 files changed, 662 insertions, 1312 deletions
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/Main.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/Main.java index e562ef7..fb9eead 100644 --- a/agent/src/main/java/moe/yuuta/dn42peering/agent/Main.java +++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/Main.java @@ -5,11 +5,11 @@ import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; import io.vertx.core.json.JsonObject; import moe.yuuta.dn42peering.agent.grpc.RPCVerticle; -import moe.yuuta.dn42peering.agent.provision.ProvisionVerticle; import javax.annotation.Nonnull; import java.io.FileInputStream; import java.io.InputStream; +import java.util.Arrays; public class Main { public static void main(@Nonnull String... args) throws Throwable { @@ -31,10 +31,9 @@ public class Main { .setConfig(config) .setInstances(Runtime.getRuntime().availableProcessors() * 2); Logger logger = LoggerFactory.getLogger("Main"); - CompositeFuture.all( - Future.<String>future(f -> vertx.deployVerticle(ProvisionVerticle.class.getName(), options, f)), + CompositeFuture.all(Arrays.asList( Future.<String>future(f -> vertx.deployVerticle(RPCVerticle.class.getName(), options, f)) - ).onComplete(res -> { + )).onComplete(res -> { if (res.succeeded()) { logger.info("The server started."); } else { diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/grpc/AgentServiceImpl.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/grpc/AgentServiceImpl.java index 75b8184..c1ad43d 100644 --- a/agent/src/main/java/moe/yuuta/dn42peering/agent/grpc/AgentServiceImpl.java +++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/grpc/AgentServiceImpl.java @@ -1,116 +1,50 @@ package moe.yuuta.dn42peering.agent.grpc; +import io.vertx.core.CompositeFuture; import io.vertx.core.Future; import io.vertx.core.Vertx; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; -import moe.yuuta.dn42peering.agent.proto.*; -import moe.yuuta.dn42peering.agent.provision.IProvisionService; +import moe.yuuta.dn42peering.agent.proto.DeployResult; +import moe.yuuta.dn42peering.agent.proto.NodeConfig; +import moe.yuuta.dn42peering.agent.proto.VertxAgentGrpc; +import moe.yuuta.dn42peering.agent.provision.BGPProvisioner; +import moe.yuuta.dn42peering.agent.provision.Change; +import moe.yuuta.dn42peering.agent.provision.WireGuardProvisioner; import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; class AgentServiceImpl extends VertxAgentGrpc.AgentVertxImplBase { private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName()); private final Vertx vertx; - private final IProvisionService provisionService; AgentServiceImpl(@Nonnull Vertx vertx) { this.vertx = vertx; - this.provisionService = IProvisionService.create(vertx); } @Override - public Future<BGPReply> provisionBGP(BGPRequest request) { - return Future.<Void>future(f -> provisionService.provisionBGP( - request.getNode().getIpv4(), - request.getNode().getIpv6(), - request.getNode().getIpv6NonLL(), - (int)request.getId(), - request.getIpv4(), - request.getIpv6().isEmpty() ? null : request.getIpv6(), - request.getDevice(), - request.getMpbgp(), - request.getAsn(), - f)) - .compose(_v -> Future.succeededFuture(BGPReply.newBuilder().build())) - .onFailure(err -> logger.error(String.format("Cannot provision BGP for %d", request.getId()), - err)); - } - - @Override - public Future<BGPReply> reloadBGP(BGPRequest request) { - return Future.<Void>future(f -> provisionService.reloadBGP( - request.getNode().getIpv4(), - request.getNode().getIpv6(), - request.getNode().getIpv6NonLL(), - (int)request.getId(), - request.getIpv4(), - request.getIpv6().isEmpty() ? null : request.getIpv6(), - request.getDevice(), - request.getMpbgp(), - request.getAsn(), - f)) - .compose(_v -> Future.succeededFuture(BGPReply.newBuilder().build())) - .onFailure(err -> logger.error(String.format("Cannot reload BGP for %d", request.getId()), - err)); - } - - @Override - public Future<BGPReply> deleteBGP(BGPRequest request) { - return Future.<Void>future(f -> provisionService.unprovisionBGP((int)request.getId(), f)) - .compose(_v -> Future.succeededFuture(BGPReply.newBuilder().build())) - .onFailure(err -> logger.error(String.format("Cannot delete BGP for %d", request.getId()), - err)); - } - - @Override - public Future<WGReply> provisionWG(WGRequest request) { - return Future.<String>future(f -> provisionService.provisionVPNWireGuard( - request.getNode().getIpv4(), - request.getNode().getIpv6(), - request.getNode().getIpv6NonLL(), - (int)request.getId(), - request.getListenPort(), - request.getEndpoint().isEmpty() ? null : request.getEndpoint(), - request.getPeerPubKey(), - request.getSelfPrivKey(), - request.getSelfPresharedSecret(), - request.getPeerIPv4(), - request.getPeerIPv6().isEmpty() ? null : request.getPeerIPv6(), - f)) - .compose(dev -> Future.succeededFuture(WGReply.newBuilder() - .setDevice(dev).build())) - .onFailure(err -> logger.error(String.format("Cannot provision WireGuard for %d", request.getId()), - err)); - } - - @Override - public Future<WGReply> reloadWG(WGRequest request) { - return Future.<String>future(f -> provisionService.reloadVPNWireGuard( - request.getNode().getIpv4(), - request.getNode().getIpv6(), - request.getNode().getIpv6NonLL(), - (int)request.getId(), - request.getListenPort(), - request.getEndpoint().isEmpty() ? null : request.getEndpoint(), - request.getPeerPubKey(), - request.getSelfPrivKey(), - request.getSelfPresharedSecret(), - request.getPeerIPv4(), - request.getPeerIPv6().isEmpty() ? null : request.getPeerIPv6(), - f)) - .compose(dev -> Future.succeededFuture(WGReply.newBuilder() - .setDevice(dev).build())) - .onFailure(err -> logger.error(String.format("Cannot reload WireGuard for %d", request.getId()), - err)); - } - - @Override - public Future<WGReply> deleteWG(WGRequest request) { - return Future.<Void>future(f -> provisionService.unprovisionVPNWireGuard((int)request.getId(), f)) - .compose(_v -> Future.succeededFuture(WGReply.newBuilder().build())) - .onFailure(err -> logger.error(String.format("Cannot delete WireGuard for %d", request.getId()), - err)); + public Future<DeployResult> deploy(NodeConfig config) { + logger.info("Deployment started"); + final BGPProvisioner bgpProvisioner = new BGPProvisioner(vertx); + final WireGuardProvisioner wireGuardProvisioner = new WireGuardProvisioner(vertx); + final List<Future> calcFutures = new ArrayList<>(); + calcFutures.add(bgpProvisioner.calculateChanges(config.getNode(), config.getBgpsList())); + calcFutures.add(wireGuardProvisioner.calculateChanges(config.getNode(), config.getWgsList())); + + return CompositeFuture.all(calcFutures) + .compose(compositeFuture -> { + final List<Future> changes = new ArrayList<>(calcFutures.size()); + for (int i = 0; i < calcFutures.size(); i ++) { + final List<Change> list = compositeFuture.resultAt(i); + changes.addAll(list.stream().map(change -> change.execute(vertx)).collect(Collectors.toList())); + } + return CompositeFuture.all(changes); + }) + .onComplete(res -> logger.info("Deployment finished. Detailed log can be traced above.")) + .compose(compositeFuture -> Future.succeededFuture(null)); } } diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/BGPProvisioner.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/BGPProvisioner.java new file mode 100644 index 0000000..913f109 --- /dev/null +++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/BGPProvisioner.java @@ -0,0 +1,145 @@ +package moe.yuuta.dn42peering.agent.provision; + +import io.vertx.core.CompositeFuture; +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystemException; +import io.vertx.core.impl.logging.Logger; +import io.vertx.core.impl.logging.LoggerFactory; +import io.vertx.ext.web.common.template.TemplateEngine; +import io.vertx.ext.web.templ.freemarker.FreeMarkerTemplateEngine; +import moe.yuuta.dn42peering.agent.proto.BGPConfig; +import moe.yuuta.dn42peering.agent.proto.Node; + +import javax.annotation.Nonnull; +import java.io.File; +import java.nio.file.NoSuchFileException; +import java.util.*; +import java.util.stream.Collectors; + +public class BGPProvisioner implements IProvisioner<BGPConfig> { + private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName()); + + private final TemplateEngine engine; + private final Vertx vertx; + + public BGPProvisioner(@Nonnull Vertx vertx) { + this(FreeMarkerTemplateEngine.create(vertx, "ftlh"), vertx); + } + + public BGPProvisioner(@Nonnull TemplateEngine engine, + @Nonnull Vertx vertx) { + this.engine = engine; + this.vertx = vertx; + } + + @Nonnull + private Future<List<Change>> calculateDeleteChanges(@Nonnull List<BGPConfig> allDesired) { + final String[] actualNamesRaw = new File("/etc/bird/peers").list((dir, name) -> name.matches("dn42_.*\\.conf")); + final List<String> actualNames = Arrays.stream(actualNamesRaw == null ? new String[]{} : actualNamesRaw) + .sorted() + .collect(Collectors.toList()); + final String[] desiredNames = allDesired + .stream() + .map(desired -> generateBGPPath(desired.getId())) + .sorted() + .collect(Collectors.toList()) + .toArray(new String[]{}); + final List<Integer> toRemove = new ArrayList<>(actualNames.size()); + for (int i = 0; i < desiredNames.length; i ++) { + toRemove.clear(); + for(int j = 0; j < actualNames.size(); j ++) { + if(("/etc/bird/peers/" + actualNames.get(j)).equals(desiredNames[i])) { + toRemove.add(j); + } + } + for (int j = 0; j < toRemove.size(); j ++) { + actualNames.remove(toRemove.get(j).intValue()); + } + } + return Future.succeededFuture(actualNames.stream() + .map(string -> new FileChange("/etc/bird/peers/" + string, null, FileChange.Action.DELETE.toString())) + .collect(Collectors.toList())); + } + + @Nonnull + private static String generateBGPPath(long id) { + return String.format("/etc/bird/peers/dn42_%d.conf", id); + } + + @Nonnull + private Future<Buffer> readConfig(long id) { + return Future.future(f -> { + vertx.fileSystem() + .readFile(generateBGPPath(id)) + .onFailure(err -> { + if(err instanceof FileSystemException && + err.getCause() instanceof NoSuchFileException) { + f.complete(null); + } else { + f.fail(err); + } + }) + .onSuccess(f::complete); + }); + } + + @Nonnull + private Future<Buffer> renderConfig(@Nonnull BGPConfig config) { + final Map<String, Object> params = new HashMap<>(3); + params.put("name", config.getId()); + params.put("asn", config.getAsn()); + params.put("ipv4", config.getIpv4()); + params.put("ipv6", config.getIpv6().equals("") ? null : config.getIpv6()); + params.put("mpbgp", config.getMpbgp()); + params.put("dev", config.getInterface()); + return engine.render(params, "bird2.conf.ftlh"); + } + + @Nonnull + private Future<List<Change>> calculateSingleChange(@Nonnull BGPConfig desiredConfig) { + return CompositeFuture.all(readConfig(desiredConfig.getId()), renderConfig(desiredConfig)) + .compose(future -> { + final Buffer actualBuff = future.resultAt(0); + final String actual = actualBuff == null ? null : actualBuff.toString(); + final String desired = future.resultAt(1).toString(); + final List<Change> changes = new ArrayList<>(1); + if(actual == null) { + changes.add(new FileChange(generateBGPPath(desiredConfig.getId()), + desired, + FileChange.Action.CREATE_AND_WRITE.toString())); + } else if(!actual.equals(desired)) { + changes.add(new FileChange(generateBGPPath(desiredConfig.getId()), + desired, + FileChange.Action.OVERWRITE.toString())); + } + return Future.succeededFuture(changes); + }); + } + + @Nonnull + @Override + public Future<List<Change>> calculateChanges(@Nonnull Node node, @Nonnull List<BGPConfig> allDesired) { + final List<Future<List<Change>>> addOrModifyChanges = + allDesired.stream().map(this::calculateSingleChange).collect(Collectors.toList()); + final Future<List<Change>> deleteChanges = + calculateDeleteChanges(allDesired); + final Future<List<Change>> reloadChange = + Future.succeededFuture(Collections.singletonList( + new CommandChange(new String[] { "birdc", "configure" }))); + + final List<Future> futures = new ArrayList<>(addOrModifyChanges.size() + 2); + futures.addAll(addOrModifyChanges); + futures.add(deleteChanges); + futures.add(reloadChange); + return CompositeFuture.all(futures) + .compose(compositeFuture -> { + final List<Change> changes = new ArrayList<>(futures.size()); + for(int i = 0; i < futures.size(); i ++) { + changes.addAll(compositeFuture.resultAt(i)); + } + return Future.succeededFuture(changes); + }); + } +} diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/Change.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/Change.java new file mode 100644 index 0000000..b280e60 --- /dev/null +++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/Change.java @@ -0,0 +1,27 @@ +package moe.yuuta.dn42peering.agent.provision; + +import io.vertx.core.Future; +import io.vertx.core.Vertx; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class Change { + @Nonnull + public final String id; + @Nullable + public final String to; + @Nonnull + public final String action; + + public Change(@Nonnull String id, + @Nullable String to, + @Nonnull String action) { + this.id = id; + this.to = to; + this.action = action; + } + + @Nonnull + public abstract Future<Void> execute(@Nonnull Vertx vertx); +} diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/CommandChange.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/CommandChange.java new file mode 100644 index 0000000..ee60ee4 --- /dev/null +++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/CommandChange.java @@ -0,0 +1,27 @@ +package moe.yuuta.dn42peering.agent.provision; + +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.impl.logging.Logger; +import io.vertx.core.impl.logging.LoggerFactory; + +import javax.annotation.Nonnull; +import java.util.Arrays; + +public class CommandChange extends Change { + private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName()); + @Nonnull + private final String[] commands; + + public CommandChange(@Nonnull String[] commands) { + super(Arrays.toString(commands), null, "exec"); + this.commands = commands; + } + + @Nonnull + @Override + public Future<Void> execute(@Nonnull Vertx vertx) { + logger.info("Executing " + Arrays.toString(commands)); + return AsyncShell.execSucc(vertx, commands); + } +} diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/FileChange.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/FileChange.java new file mode 100644 index 0000000..146e41c --- /dev/null +++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/FileChange.java @@ -0,0 +1,66 @@ +package moe.yuuta.dn42peering.agent.provision; + +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.OpenOptions; +import io.vertx.core.impl.logging.Logger; +import io.vertx.core.impl.logging.LoggerFactory; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FileChange extends Change { + private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName()); + + public enum Action { + CREATE_AND_WRITE, + OVERWRITE, + DELETE + } + + public FileChange(@Nonnull String path, + @Nullable String contents, + @Nonnull String action) { + super(path, contents, action); + Action.valueOf(action); // Verify + } + + @Nonnull + @Override + public Future<Void> execute(@Nonnull Vertx vertx) { + switch (Action.valueOf(action)) { + case CREATE_AND_WRITE: + logger.info("Writing " + id + " with:\n" + to); + return vertx.fileSystem().open(id, new OpenOptions() + .setCreateNew(true) + .setTruncateExisting(true) + .setWrite(true)) + .compose(asyncFile -> { + return asyncFile.write(Buffer.buffer(to == null ? "" : to), 0) + .compose(_v -> Future.succeededFuture(asyncFile)); + }) + .compose(asyncFile -> { + return asyncFile.close(); + }); + case OVERWRITE: + logger.info("Overwriting " + id + " with:\n" + to); + return vertx.fileSystem().open(id, new OpenOptions() + .setCreateNew(false) + .setTruncateExisting(true) + .setWrite(true)) + .compose(asyncFile -> { + return asyncFile.write(Buffer.buffer(to == null ? "" : to), 0) + .compose(_v -> Future.succeededFuture(asyncFile)); + }) + .compose(asyncFile -> { + return asyncFile.close(); + }); + case DELETE: + logger.info("Deleting " + id); + return vertx.fileSystem().delete(id); + default: + throw new UnsupportedOperationException("Unknown file change action " + action); + } + } +} diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IProvisionService.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IProvisionService.java deleted file mode 100644 index c3d416c..0000000 --- a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IProvisionService.java +++ /dev/null @@ -1,84 +0,0 @@ -package moe.yuuta.dn42peering.agent.provision; - -import io.vertx.codegen.annotations.Fluent; -import io.vertx.codegen.annotations.ProxyGen; -import io.vertx.core.AsyncResult; -import io.vertx.core.Handler; -import io.vertx.core.Vertx; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -@ProxyGen -public interface IProvisionService { - String ADDRESS = IProvisionService.class.getName(); - - @Nonnull - static IProvisionService create(@Nonnull Vertx vertx) { - return new IProvisionServiceVertxEBProxy(vertx, ADDRESS); - } - - @Fluent - @Nonnull - IProvisionService provisionBGP(@Nonnull String localIP4, - @Nonnull String localIP6, - @Nonnull String localIP6NonLL, - int id, - @Nonnull String ipv4, - @Nullable String ipv6, - @Nullable String device, - boolean mpbgp, - @Nonnull String asn, - @Nonnull Handler<AsyncResult<Void>> handler); - - @Fluent - @Nonnull - IProvisionService reloadBGP(@Nonnull String localIP4, - @Nonnull String localIP6, - @Nonnull String localIP6NonLL, - int id, - @Nonnull String ipv4, - @Nullable String ipv6, - @Nullable String device, - boolean mpbgp, - @Nonnull String asn, - @Nonnull Handler<AsyncResult<Void>> handler); - - @Fluent - @Nonnull - IProvisionService unprovisionBGP(int id, @Nonnull Handler<AsyncResult<Void>> handler); - - @Fluent - @Nonnull - IProvisionService provisionVPNWireGuard(@Nonnull String localIP4, - @Nonnull String localIP6, - @Nonnull String localIP6NonLL, - int id, - int listenPort, - @Nullable String endpointWithPort, - @Nonnull String peerPubKey, - @Nonnull String selfPrivKey, - @Nonnull String selfPresharedSecret, - @Nonnull String peerIPv4, - @Nullable String peerIPv6, - @Nonnull Handler<AsyncResult<String>> handler); - - @Fluent - @Nonnull - IProvisionService reloadVPNWireGuard(@Nonnull String localIP4, - @Nonnull String localIP6, - @Nonnull String localIP6NonLL, - int id, - int listenPort, - @Nullable String endpointWithPort, - @Nonnull String peerPubKey, - @Nonnull String selfPrivKey, - @Nonnull String selfPresharedSecret, - @Nonnull String peerIPv4, - @Nullable String peerIPv6, - @Nonnull Handler<AsyncResult<String>> handler); - - @Fluent - @Nonnull - IProvisionService unprovisionVPNWireGuard(int id, @Nonnull Handler<AsyncResult<Void>> handler); -} diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IProvisioner.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IProvisioner.java new file mode 100644 index 0000000..19c49a0 --- /dev/null +++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IProvisioner.java @@ -0,0 +1,12 @@ +package moe.yuuta.dn42peering.agent.provision; + +import io.vertx.core.Future; +import moe.yuuta.dn42peering.agent.proto.Node; + +import javax.annotation.Nonnull; +import java.util.List; + +public interface IProvisioner<T> { + @Nonnull + Future<List<Change>> calculateChanges(@Nonnull Node node, @Nonnull List<T> allDesired); +} diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/ProvisionServiceImpl.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/ProvisionServiceImpl.java deleted file mode 100644 index 733d111..0000000 --- a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/ProvisionServiceImpl.java +++ /dev/null @@ -1,302 +0,0 @@ -package moe.yuuta.dn42peering.agent.provision; - -import io.vertx.core.AsyncResult; -import io.vertx.core.Future; -import io.vertx.core.Handler; -import io.vertx.core.Vertx; -import io.vertx.core.file.AsyncFile; -import io.vertx.core.file.OpenOptions; -import io.vertx.core.impl.logging.Logger; -import io.vertx.core.impl.logging.LoggerFactory; -import io.vertx.ext.web.common.template.TemplateEngine; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.IOException; -import java.net.Inet6Address; -import java.util.HashMap; -import java.util.Map; - -class ProvisionServiceImpl implements IProvisionService { - private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName()); - - private final Vertx vertx; - private final TemplateEngine engine; - - ProvisionServiceImpl(@Nonnull Vertx vertx, @Nonnull TemplateEngine engine) { - this.vertx = vertx; - this.engine = engine; - } - - @Nonnull - private static String generateBGPPath(int id) { - return String.format("/etc/bird/peers/dn42_%d.conf", id); - } - - @Nonnull - private static String generateWGPath(@Nonnull String dev) { - return String.format("/etc/wireguard/%s.conf", dev); - } - - @Nonnull - private static String generateWireGuardDevName(long id) { - return String.format("wg_%d", id); - } - - @Nonnull - private static String getLockNameForBGP(long id) { - return String.format("BGP:%d", id); - } - - @Nonnull - private static String getLockNameForWG(long id) { - return String.format("WG:%d", id); - } - - @Nonnull - private Future<Void> writeBGPConfig(@Nonnull String localIP4, - @Nonnull String localIP6, - @Nonnull String localIP6NonLL, - int id, - @Nonnull String ipv4, - @Nullable String ipv6, - @Nullable String device, - boolean mpbgp, - @Nonnull String asn, - boolean create) { - final String asnNum = asn.replace("AS", ""); - return vertx.fileSystem().open(generateBGPPath(id), new OpenOptions() - .setCreateNew(create) - .setTruncateExisting(true) - .setWrite(true)) - .compose(asyncFile -> { - if (mpbgp) return Future.succeededFuture(asyncFile); - final Map<String, Object> params = new HashMap<>(3); - params.put("name", id); - params.put("ipv4", ipv4); - params.put("asn", asnNum); - return engine.render(params, "bird2_v4.conf.ftlh") - .compose(buffer -> asyncFile.write(buffer) - .compose(_v1 -> Future.succeededFuture(asyncFile))); - }) - .compose(asyncFile -> { - if (ipv6 == null) return Future.succeededFuture(asyncFile); - final Map<String, Object> params = new HashMap<>(4); - params.put("name", id); - params.put("ipv6", ipv6); - params.put("asn", asnNum); - params.put("dev", device); - return engine.render(params, "bird2_v6.conf.ftlh") - .compose(buffer -> asyncFile.write(buffer) - .compose(_v1 -> Future.succeededFuture(asyncFile))); - }) - .compose(AsyncFile::close); - } - - @Nonnull - private Future<Void> writeWGConfig(boolean create, - @Nonnull String localIP4, - @Nonnull String localIP6, - @Nonnull String localIP6NonLL, - @Nonnull String dev, - int listenPort, - @Nullable String endpointWithPort, - @Nonnull String peerPubKey, - @Nonnull String selfPrivKey, - @Nonnull String selfPresharedSecret, - @Nonnull String peerIPv4, - @Nullable String peerIPv6) { - return vertx.fileSystem().open(generateWGPath(dev), new OpenOptions() - .setCreateNew(create) - .setTruncateExisting(true) - .setWrite(true)) - .compose(asyncFile -> { - final Map<String, Object> params = new HashMap<>(9); - params.put("listen_port", listenPort); - params.put("self_priv_key", selfPrivKey); - params.put("dev", dev); - params.put("self_ipv4", localIP4); - params.put("peer_ipv4", peerIPv4); - params.put("peer_ipv6", peerIPv6); - if (peerIPv6 != null) { - try { - final boolean ll = Inet6Address.getByName(peerIPv6).isLinkLocalAddress(); - params.put("peer_ipv6_ll", ll); - if(ll) - params.put("self_ipv6", localIP6); - else - params.put("self_ipv6", localIP6NonLL); - } catch (IOException e) { - return Future.failedFuture(e); - } - } - params.put("preshared_key", selfPresharedSecret); - params.put("endpoint", endpointWithPort); - params.put("peer_pub_key", peerPubKey); - - return engine.render(params, "wg.conf.ftlh") - .compose(buffer -> asyncFile.write(buffer) - .compose(_v1 -> Future.succeededFuture(asyncFile))); - }) - .compose(AsyncFile::close); - } - - @Nonnull - private Future<Void> deleteBGPConfig(int id) { - return vertx.fileSystem().exists(generateBGPPath(id)) - .compose(exists -> { - if (exists) return vertx.fileSystem().delete(generateBGPPath(id)); - else return Future.succeededFuture(null); - }); - } - - @Nonnull - private Future<Void> deleteWGConfig(@Nonnull String dev) { - return vertx.fileSystem().delete(generateWGPath(dev)); - } - - @Nonnull - @Override - public IProvisionService provisionBGP(@Nonnull String localIP4, - @Nonnull String localIP6, - @Nonnull String localIP6NonLL, - int id, - @Nonnull String ipv4, - @Nullable String ipv6, - @Nullable String device, - boolean mpbgp, - @Nonnull String asn, - @Nonnull Handler<AsyncResult<Void>> handler) { - vertx.sharedData().getLocalLockWithTimeout(getLockNameForBGP(id), 1000) - .compose(lock -> - writeBGPConfig(localIP4, localIP6, localIP6NonLL, id, ipv4, ipv6, device, mpbgp, asn, true) - .compose(_v -> AsyncShell.execSucc(vertx, "birdc", "configure")) - .onComplete(ar -> lock.release()) - ) - .onComplete(handler); - return this; - } - - @Nonnull - @Override - public IProvisionService reloadBGP(@Nonnull String localIP4, - @Nonnull String localIP6, - @Nonnull String localIP6NonLL, - int id, - @Nonnull String ipv4, - @Nullable String ipv6, - @Nullable String device, - boolean mpbgp, - @Nonnull String asn, - @Nonnull Handler<AsyncResult<Void>> handler) { - vertx.sharedData().getLocalLockWithTimeout(getLockNameForBGP(id), 1000) - .compose(lock -> - writeBGPConfig(localIP4, localIP6, localIP6NonLL, id, ipv4, ipv6, device, mpbgp, asn, false) - .compose(_v -> AsyncShell.execSucc(vertx, "birdc", "configure")) - .onComplete(ar -> lock.release()) - ) - .onComplete(handler); - return this; - } - - @Nonnull - @Override - public IProvisionService unprovisionBGP(int id, @Nonnull Handler<AsyncResult<Void>> handler) { - vertx.sharedData().getLocalLockWithTimeout(getLockNameForBGP(id), 1000) - .compose(lock -> - deleteBGPConfig(id) - .compose(_v -> AsyncShell.execSucc(vertx, "birdc", "configure")) - .onComplete(ar -> lock.release()) - ) - .onComplete(handler); - return this; - } - - @Nonnull - @Override - public IProvisionService provisionVPNWireGuard(@Nonnull String localIP4, - @Nonnull String localIP6, - @Nonnull String localIP6NonLL, - int id, - int listenPort, - @Nullable String endpointWithPort, - @Nonnull String peerPubKey, - @Nonnull String selfPrivKey, - @Nonnull String selfPresharedSecret, - @Nonnull String peerIPv4, - @Nullable String peerIPv6, - @Nonnull Handler<AsyncResult<String>> handler) { - vertx.sharedData() - .getLocalLockWithTimeout(getLockNameForWG(id), 1000) - .compose(lock -> writeWGConfig(true, - localIP4, - localIP6, - localIP6NonLL, - generateWireGuardDevName(id), - listenPort, - endpointWithPort, - peerPubKey, - selfPrivKey, - selfPresharedSecret, - peerIPv4, - peerIPv6) - .compose(_v -> AsyncShell.execSucc(vertx, "systemctl", "enable", "--now", "-q", - String.format("wg-quick@%s", generateWireGuardDevName(id)))) - .onComplete(_v -> lock.release())) - .compose(_v -> Future.succeededFuture(generateWireGuardDevName(id))) - .onComplete(handler); - return this; - } - - @Nonnull - @Override - public IProvisionService reloadVPNWireGuard(@Nonnull String localIP4, - @Nonnull String localIP6, - @Nonnull String localIP6NonLL, - int id, - int listenPort, - @Nullable String endpointWithPort, - @Nonnull String peerPubKey, - @Nonnull String selfPrivKey, - @Nonnull String selfPresharedSecret, - @Nonnull String peerIPv4, - @Nullable String peerIPv6, - @Nonnull Handler<AsyncResult<String>> handler) { - vertx.sharedData() - .getLocalLockWithTimeout(getLockNameForWG(id), 1000) - .compose(lock -> writeWGConfig(false, - localIP4, - localIP6, - localIP6NonLL, - generateWireGuardDevName(id), - listenPort, - endpointWithPort, - peerPubKey, - selfPrivKey, - selfPresharedSecret, - peerIPv4, - peerIPv6) - .compose(_v -> AsyncShell.execSucc(vertx, "systemctl", "enable", "-q", - String.format("wg-quick@%s", generateWireGuardDevName(id)))) - .compose(_v -> AsyncShell.execSucc(vertx, "systemctl", "reload-or-restart", - String.format("wg-quick@%s", generateWireGuardDevName(id)))) - .onComplete(_v -> lock.release())) - .compose(_v -> Future.succeededFuture(generateWireGuardDevName(id))) - .onComplete(handler); - return this; - } - - @Nonnull - @Override - public IProvisionService unprovisionVPNWireGuard(int id, @Nonnull Handler<AsyncResult<Void>> handler) { - vertx.sharedData() - .getLocalLockWithTimeout(getLockNameForWG(id), 1000) - .compose(lock -> AsyncShell.execSucc(vertx, "systemctl", "disable", "--now", "-q", - String.format("wg-quick@%s", generateWireGuardDevName(id))) - // We need to stop the service first, then delete the configuration. - .compose(_v -> deleteWGConfig(generateWireGuardDevName(id))) - .onComplete(_v -> lock.release())) - .onComplete(handler); - return this; - } -} diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/ProvisionVerticle.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/ProvisionVerticle.java deleted file mode 100644 index eb2c678..0000000 --- a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/ProvisionVerticle.java +++ /dev/null @@ -1,36 +0,0 @@ -package moe.yuuta.dn42peering.agent.provision; - -import io.vertx.core.AbstractVerticle; -import io.vertx.core.Promise; -import io.vertx.core.eventbus.MessageConsumer; -import io.vertx.core.impl.logging.Logger; -import io.vertx.core.impl.logging.LoggerFactory; -import io.vertx.core.json.JsonObject; -import io.vertx.ext.web.templ.freemarker.FreeMarkerTemplateEngine; -import io.vertx.serviceproxy.ServiceBinder; - -public class ProvisionVerticle extends AbstractVerticle { - private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName()); - - private MessageConsumer<JsonObject> consumer; - - @Override - public void start(Promise<Void> startPromise) throws Exception { - consumer = new ServiceBinder(vertx) - .setAddress(IProvisionService.ADDRESS) - .register(IProvisionService.class, new ProvisionServiceImpl(vertx, - FreeMarkerTemplateEngine.create(vertx, "ftlh"))); - consumer.completionHandler(ar -> { - if (ar.succeeded()) { - startPromise.complete(); - } else { - startPromise.fail(ar.cause()); - } - }); - } - - @Override - public void stop(Promise<Void> stopPromise) throws Exception { - consumer.unregister(stopPromise); - } -} diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/WireGuardProvisioner.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/WireGuardProvisioner.java new file mode 100644 index 0000000..de89953 --- /dev/null +++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/WireGuardProvisioner.java @@ -0,0 +1,199 @@ +package moe.yuuta.dn42peering.agent.provision; + +import io.vertx.core.CompositeFuture; +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystemException; +import io.vertx.core.impl.logging.Logger; +import io.vertx.core.impl.logging.LoggerFactory; +import io.vertx.ext.web.common.template.TemplateEngine; +import io.vertx.ext.web.templ.freemarker.FreeMarkerTemplateEngine; +import moe.yuuta.dn42peering.agent.proto.Node; +import moe.yuuta.dn42peering.agent.proto.WireGuardConfig; + +import javax.annotation.Nonnull; +import java.io.File; +import java.io.IOException; +import java.net.Inet6Address; +import java.nio.file.NoSuchFileException; +import java.util.*; +import java.util.stream.Collectors; + +public class WireGuardProvisioner implements IProvisioner<WireGuardConfig> { + private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName()); + + private final TemplateEngine engine; + private final Vertx vertx; + + public WireGuardProvisioner(@Nonnull Vertx vertx) { + this(FreeMarkerTemplateEngine.create(vertx, "ftlh"), vertx); + } + + public WireGuardProvisioner(@Nonnull TemplateEngine engine, + @Nonnull Vertx vertx) { + this.engine = engine; + this.vertx = vertx; + } + + @Nonnull + private Future<List<Change>> calculateDeleteChanges(@Nonnull List<WireGuardConfig> allDesired) { + final String[] actualNamesRaw = new File("/etc/wireguard/").list((dir, name) -> name.matches("wg_.*\\.conf")); + final List<String> actualNames = Arrays.stream(actualNamesRaw == null ? new String[]{} : actualNamesRaw) + .sorted() + .collect(Collectors.toList()); + final String[] desiredNames = allDesired + .stream() + .map(desired -> generateWGPath(desired.getInterface())) + .sorted() + .collect(Collectors.toList()) + .toArray(new String[]{}); + final List<Integer> toRemove = new ArrayList<>(actualNames.size()); + for (int i = 0; i < desiredNames.length; i ++) { + toRemove.clear(); + for(int j = 0; j < actualNames.size(); j ++) { + if(("/etc/wireguard/" + actualNames.get(j)).equals(desiredNames[i])) { + toRemove.add(j); + } + } + for (int j = 0; j < toRemove.size(); j ++) { + actualNames.remove(toRemove.get(j).intValue()); + } + } + return Future.succeededFuture(actualNames.stream() + .flatMap(string -> { + return Arrays.stream(new Change[] { + new CommandChange(new String[]{ "systemctl", "disable", "--now", "-q", "wg-quick@" + string }), + new FileChange("/etc/wireguard/" + string, null, FileChange.Action.DELETE.toString()) + }); + }) + .collect(Collectors.toList())); + } + + @Nonnull + private static String generateWGPath(@Nonnull String iif) { + return String.format("/etc/wireguard/%s.conf", iif); + } + + @Nonnull + private Future<Buffer> readConfig(@Nonnull String iif) { + return Future.future(f -> { + vertx.fileSystem() + .readFile(generateWGPath(iif)) + .onFailure(err -> { + if(err instanceof FileSystemException && + err.getCause() instanceof NoSuchFileException) { + f.complete(null); + } else { + f.fail(err); + } + }) + .onSuccess(f::complete); + }); + } + + @Nonnull + private Future<Buffer> renderConfig(@Nonnull Node node, @Nonnull WireGuardConfig config) { + final Map<String, Object> params = new HashMap<>(9); + params.put("listen_port", config.getListenPort()); + params.put("self_priv_key", config.getSelfPrivKey()); + params.put("dev", config.getInterface()); + params.put("self_ipv4", node.getIpv4()); + params.put("peer_ipv4", config.getPeerIPv4()); + params.put("peer_ipv6", config.getPeerIPv6()); + if (!config.getPeerIPv6().equals("")) { + try { + final boolean ll = Inet6Address.getByName(config.getPeerIPv6()).isLinkLocalAddress(); + params.put("peer_ipv6_ll", ll); + if(ll) + params.put("self_ipv6", node.getIpv6()); + else + params.put("self_ipv6", node.getIpv6NonLL()); + } catch (IOException e) { + return Future.failedFuture(e); + } + } + params.put("preshared_key", config.getSelfPresharedSecret()); + if(!config.getEndpoint().equals("")) { + params.put("endpoint", config.getEndpoint()); + } + params.put("peer_pub_key", config.getPeerPubKey()); + + return engine.render(params, "wg.conf.ftlh"); + } + + @Nonnull + private Future<List<Change>> calculateSingleConfigChange(@Nonnull Node node, + @Nonnull WireGuardConfig desiredConfig) { + return CompositeFuture.all(readConfig(desiredConfig.getInterface()), renderConfig(node, desiredConfig)) + .compose(future -> { + final Buffer actualBuff = future.resultAt(0); + final String actual = actualBuff == null ? null : actualBuff.toString(); + final String desired = future.resultAt(1).toString(); + final List<Change> changes = new ArrayList<>(1); + if(actual == null) { + changes.add(new FileChange(generateWGPath(desiredConfig.getInterface()), + desired, + FileChange.Action.CREATE_AND_WRITE.toString())); + changes.add( + new CommandChange(new String[] { "systemctl", + "enable", + "--now", + "-q", + "wg-quick@" + desiredConfig.getInterface() })); + } else if(!actual.equals(desired)) { + // TODO: Smart reloading / restarting + changes.add(new FileChange(generateWGPath(desiredConfig.getInterface()), + desired, + FileChange.Action.OVERWRITE.toString())); + changes.add( + new CommandChange(new String[] { "systemctl", + "restart", + "-q", + "wg-quick@" + desiredConfig.getInterface() })); + } + return Future.succeededFuture(changes); + }); + } + + @Nonnull + private Future<List<Change>> calculateSingleServiceStatusChange(@Nonnull WireGuardConfig desiredConfig) { + // Check if the service is not started or in wrong state. + return AsyncShell.exec(vertx, "systemctl", "is-active", "wg-quick@" + desiredConfig.getInterface()) + .compose(res -> { + if(res == 0) { + return Future.succeededFuture(Collections.emptyList()); + } + return Future.succeededFuture(Collections.singletonList( + new CommandChange(new String[] { "systemctl", + "enable", + "--now", + "-q", + "wg-quick@" + desiredConfig.getInterface() }) + )); + }); + } + + @Nonnull + @Override + public Future<List<Change>> calculateChanges(@Nonnull Node node, @Nonnull List<WireGuardConfig> allDesired) { + final List<Future<List<Change>>> addOrModifyChanges = + allDesired.stream().map(desired -> calculateSingleConfigChange(node, desired)).collect(Collectors.toList()); + final List<Future<List<Change>>> serviceChanges = + allDesired.stream().map(this::calculateSingleServiceStatusChange).collect(Collectors.toList()); + final Future<List<Change>> deleteChanges = + calculateDeleteChanges(allDesired); + final List<Future> futures = new ArrayList<>(addOrModifyChanges.size() + 1); + futures.addAll(addOrModifyChanges); + futures.addAll(serviceChanges); + futures.add(deleteChanges); + return CompositeFuture.all(futures) + .compose(compositeFuture -> { + final List<Change> changes = new ArrayList<>(futures.size()); + for(int i = 0; i < futures.size(); i ++) { + changes.addAll(compositeFuture.resultAt(i)); + } + return Future.succeededFuture(changes); + }); + } +} diff --git a/agent/src/main/resources/bird2.conf.ftlh b/agent/src/main/resources/bird2.conf.ftlh new file mode 100644 index 0000000..fa98469 --- /dev/null +++ b/agent/src/main/resources/bird2.conf.ftlh @@ -0,0 +1,12 @@ +<#if !mpbgp> +protocol bgp dn42_${name?long?c} from dnpeers { + neighbor ${ipv4} as ${asn?long?c}; + direct; +} +</#if> +<#if ipv6??> +protocol bgp dn42_${name?long?c}_v6 from dnpeers { + neighbor ${ipv6}%${dev} as ${asn?long?c}; + direct; +} +</#if>
\ No newline at end of file diff --git a/agent/src/main/resources/bird2_v4.conf.ftlh b/agent/src/main/resources/bird2_v4.conf.ftlh deleted file mode 100644 index ea10c9e..0000000 --- a/agent/src/main/resources/bird2_v4.conf.ftlh +++ /dev/null @@ -1,5 +0,0 @@ -protocol bgp dn42_${name} from dnpeers { - neighbor ${ipv4} as ${asn}; - direct; -} - diff --git a/agent/src/main/resources/bird2_v6.conf.ftlh b/agent/src/main/resources/bird2_v6.conf.ftlh deleted file mode 100644 index 579fa84..0000000 --- a/agent/src/main/resources/bird2_v6.conf.ftlh +++ /dev/null @@ -1,4 +0,0 @@ -protocol bgp dn42_${name}_v6 from dnpeers { - neighbor ${ipv6}%${dev} as ${asn}; - direct; -}
\ No newline at end of file diff --git a/central/src/main/java/moe/yuuta/dn42peering/manage/ManageHandler.java b/central/src/main/java/moe/yuuta/dn42peering/manage/ManageHandler.java index b6ff237..6b22858 100644 --- a/central/src/main/java/moe/yuuta/dn42peering/manage/ManageHandler.java +++ b/central/src/main/java/moe/yuuta/dn42peering/manage/ManageHandler.java @@ -36,12 +36,12 @@ import moe.yuuta.dn42peering.whois.IWhoisService; import javax.annotation.Nonnull; import java.io.IOException; -import java.util.*; +import java.util.Arrays; +import java.util.List; import static io.vertx.ext.web.validation.builder.Parameters.param; import static io.vertx.json.schema.common.dsl.Schemas.*; import static moe.yuuta.dn42peering.manage.ManagementUI.*; -import static moe.yuuta.dn42peering.manage.ManagementProvision.*; public class ManageHandler implements ISubRouter { private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName()); @@ -153,8 +153,7 @@ public class ManageHandler implements ISubRouter { .setStatusCode(303) .putHeader("Location", "/manage") .end(); - provisionPeer(nodeService, provisionService, peer).onComplete(ar -> - handleProvisionResult(peerService, peer, ar)); + provisionService.deploy(peer.getNode(), ar -> {}); }) .onFailure(err -> { if (err instanceof FormException) { @@ -261,8 +260,8 @@ public class ManageHandler implements ISubRouter { .end(); final Peer existingPeer = pair.a; final Peer inPeer = pair.b; - reloadPeer(nodeService, provisionService, existingPeer, inPeer).onComplete(ar -> - handleProvisionResult(peerService, inPeer, ar)); + provisionService.deploy(existingPeer.getNode(), ar -> {}); + provisionService.deploy(inPeer.getNode(), ar -> {}); }) .onFailure(err -> { if (err instanceof FormException) { @@ -304,12 +303,15 @@ public class ManageHandler implements ISubRouter { } return Future.succeededFuture(peer); }) - .compose(peer -> unprovisionPeer(nodeService, provisionService, peer)) - .compose(_v -> Future.<Void>future(f -> peerService.deletePeer(asn, id, f))) - .onSuccess(_id -> ctx.response() - .setStatusCode(303) - .putHeader("Location", "/manage") - .end()) + .compose(peer -> Future.<Void>future(f -> peerService.deletePeer(asn, id, f)) + .compose(_v1 -> Future.succeededFuture(peer))) + .onSuccess(peer -> { + ctx.response() + .setStatusCode(303) + .putHeader("Location", "/manage") + .end(); + provisionService.deploy(peer.getNode(), ar -> {}); + }) .onFailure(err -> { if(!(err instanceof HTTPException)) logger.error("Cannot delete peer.", err); ctx.fail(err); diff --git a/central/src/main/java/moe/yuuta/dn42peering/manage/ManagementProvision.java b/central/src/main/java/moe/yuuta/dn42peering/manage/ManagementProvision.java deleted file mode 100644 index 11ec50f..0000000 --- a/central/src/main/java/moe/yuuta/dn42peering/manage/ManagementProvision.java +++ /dev/null @@ -1,199 +0,0 @@ -package moe.yuuta.dn42peering.manage; - -import io.vertx.core.AsyncResult; -import io.vertx.core.Future; -import io.vertx.core.impl.logging.Logger; -import io.vertx.core.impl.logging.LoggerFactory; -import moe.yuuta.dn42peering.jaba.Pair; -import moe.yuuta.dn42peering.node.INodeService; -import moe.yuuta.dn42peering.node.Node; -import moe.yuuta.dn42peering.peer.IPeerService; -import moe.yuuta.dn42peering.peer.Peer; -import moe.yuuta.dn42peering.peer.ProvisionStatus; -import moe.yuuta.dn42peering.provision.BGPRequestCommon; -import moe.yuuta.dn42peering.provision.IProvisionRemoteService; -import moe.yuuta.dn42peering.provision.WGRequestCommon; - -import javax.annotation.Nonnull; -import java.io.IOException; - -class ManagementProvision { - private static final Logger logger = LoggerFactory.getLogger(ManagementProvision.class.getSimpleName()); - - @Nonnull - public static Future<Void> reloadPeer(@Nonnull INodeService nodeService, - @Nonnull IProvisionRemoteService provisionService, - @Nonnull Peer existingPeer, @Nonnull Peer inPeer) { - // Check if we can reload on the fly. - // Otherwise, we can only deprovision and provision. - // This will cause unnecessary wastes. - boolean canReload = inPeer.getType() == existingPeer.getType() && - inPeer.getNode() == existingPeer.getNode(); - // wg-quick does not support switching IP addresses. - // TODO: Move reload detection to agents. - if(canReload && // Only check if no other factors prevent us from reloading. - inPeer.getType() == Peer.VPNType.WIREGUARD && - existingPeer.getType() == Peer.VPNType.WIREGUARD) { - if(!inPeer.getIpv4().equals(existingPeer.getIpv6())) { - canReload = false; - } - if(inPeer.getIpv6() != null && !inPeer.getIpv6().equals(existingPeer.getIpv6())) { - try { - // LL addrs does not have anything to do with ifconfig. - if(inPeer.isIPv6LinkLocal() && existingPeer.isIPv6LinkLocal()) - canReload = true; - else - canReload = false; - } catch (IOException ignored) {} - } - } - // wg-quick will also not clear EndPoint setting if we just reload it. - if(canReload && // Only check if no other factors prevent us from reloading. - inPeer.getType() == Peer.VPNType.WIREGUARD && - existingPeer.getType() == Peer.VPNType.WIREGUARD) { - if(inPeer.getWgEndpoint() == null && - existingPeer.getWgEndpoint() != null) { - canReload = false; - } - } - Future<Void> future; - if (canReload) { - future = Future.<Node>future(f -> nodeService.getNode(inPeer.getNode(), f)) - .compose(node -> { - if(node == null || !node.getSupportedVPNTypes().contains(inPeer.getType())) { - return Future.failedFuture("The node does not exist"); - } - return Future.succeededFuture(node); - }) - .compose(node -> { - switch (existingPeer.getType()) { - case WIREGUARD: - final WGRequestCommon wgReq = inPeer.toWGRequest(); - wgReq.setNode(node.toRPCNode()); - return Future.<String>future(f -> provisionService.reloadWG( - node.toRPCNode(), - wgReq, - f) - ).compose(device -> Future.succeededFuture(new Pair<>(node, device))); - default: - throw new UnsupportedOperationException("Bug: Unknown type."); - } - }) - .compose(pair -> { - final BGPRequestCommon bgpReq = inPeer.toBGPRequest(); - bgpReq.setNode(pair.a.toRPCNode()); - bgpReq.setDevice(pair.b); - return Future.future(f -> provisionService.reloadBGP( - pair.a.toRPCNode(), - bgpReq, - f)); - }); - } else { - future = unprovisionPeer(nodeService, provisionService, existingPeer) - .compose(f -> provisionPeer(nodeService, provisionService, inPeer)); - } - return future; - } - - public static Future<Void> unprovisionPeer(@Nonnull INodeService nodeService, - @Nonnull IProvisionRemoteService provisionService, - @Nonnull Peer existingPeer) { - return Future.<Node>future(f -> nodeService.getNode(existingPeer.getNode(), f)) - .compose(node -> { - if(node == null) { - return Future.failedFuture("The node does not exist"); - } - return Future.succeededFuture(node); - }) - .compose(node -> { - switch (existingPeer.getType()) { - case WIREGUARD: - return Future.<Void>future(f -> provisionService.deleteWG( - node.toRPCNode(), - new WGRequestCommon(null, - (long)existingPeer.getId(), - null, - null, - null, - null, - null, - null, - null), - f)) - .compose(res -> Future.succeededFuture(node)); - default: - throw new UnsupportedOperationException("Bug: Unknown type."); - } - }) - .compose(node -> { - return Future.future(f -> provisionService.deleteBGP( - node.toRPCNode(), - new BGPRequestCommon(null, - (long)existingPeer.getId(), - null, - null, - null, - null, - null), - f)); - }) - ; - } - - @Nonnull - public static Future<Void> provisionPeer(@Nonnull INodeService nodeService, - @Nonnull IProvisionRemoteService provisionService, - @Nonnull Peer inPeer) { - return Future.<Node>future(f -> nodeService.getNode(inPeer.getNode(), f)) - .compose(node -> { - if(node == null || !node.getSupportedVPNTypes().contains(inPeer.getType())) { - return Future.failedFuture("The node does not exist"); - } - return Future.succeededFuture(node); - }) - .compose(node -> { - switch (inPeer.getType()) { - case WIREGUARD: - final WGRequestCommon wgReq = inPeer.toWGRequest(); - wgReq.setNode(node.toRPCNode()); - return Future.<String>future(f -> provisionService.provisionWG( - node.toRPCNode(), - wgReq, - f) - ).compose(device -> Future.succeededFuture(new Pair<>(node, device))); - default: - throw new UnsupportedOperationException("Bug: Unknown type."); - } - }) - .compose(pair -> { - final BGPRequestCommon bgpReq = inPeer.toBGPRequest(); - bgpReq.setNode(pair.a.toRPCNode()); - bgpReq.setDevice(pair.b); - return Future.future(f -> provisionService.provisionBGP( - pair.a.toRPCNode(), - bgpReq, - f)); - }); - } - - public static void handleProvisionResult(@Nonnull IPeerService peerService, - @Nonnull Peer inPeer, - @Nonnull AsyncResult<Void> res) { - if(res.succeeded()) { - peerService.changeProvisionStatus(inPeer.getId(), - ProvisionStatus.PROVISIONED, ar -> { - if (ar.failed()) { - logger.error(String.format("Cannot update %d to provisioned.", inPeer.getId()), ar.cause()); - } - }); - } else { - logger.error(String.format("Cannot provision %d.", inPeer.getId()), res.cause()); - peerService.changeProvisionStatus(inPeer.getId(), - ProvisionStatus.FAIL, ar -> { - if (ar.failed()) { - logger.error(String.format("Cannot update %d to failed.", inPeer.getId()), ar.cause()); - } - }); - } - } -} diff --git a/central/src/main/java/moe/yuuta/dn42peering/node/Node.java b/central/src/main/java/moe/yuuta/dn42peering/node/Node.java index 250930f..d4b5731 100644 --- a/central/src/main/java/moe/yuuta/dn42peering/node/Node.java +++ b/central/src/main/java/moe/yuuta/dn42peering/node/Node.java @@ -1,17 +1,13 @@ package moe.yuuta.dn42peering.node; -import io.grpc.ManagedChannel; import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.annotations.GenIgnore; import io.vertx.codegen.format.SnakeCase; -import io.vertx.core.Vertx; import io.vertx.core.json.JsonObject; -import io.vertx.grpc.VertxChannelBuilder; import io.vertx.sqlclient.templates.annotations.Column; import io.vertx.sqlclient.templates.annotations.ParametersMapped; import io.vertx.sqlclient.templates.annotations.RowMapped; import io.vertx.sqlclient.templates.annotations.TemplateParameter; -import moe.yuuta.dn42peering.provision.NodeCommon; import moe.yuuta.dn42peering.peer.Peer; import javax.annotation.Nonnull; @@ -108,13 +104,13 @@ public class Node { @GenIgnore @Nonnull - public NodeCommon toRPCNode() { - return new NodeCommon(id, - dn42Ip4, - dn42Ip6, - dn42Ip6NonLL, - internalIp, - internalPort); + public moe.yuuta.dn42peering.agent.proto.Node.Builder toRPCNode() { + return moe.yuuta.dn42peering.agent.proto.Node.newBuilder() + .setAsn(Long.parseLong(getAsn().substring(2))) + .setId(getId()) + .setIpv4(getDn42Ip4()) + .setIpv6(getDn42Ip6()) + .setIpv6NonLL(getDn42Ip6NonLL()); } // BEGIN GETTER / SETTER diff --git a/central/src/main/java/moe/yuuta/dn42peering/peer/Peer.java b/central/src/main/java/moe/yuuta/dn42peering/peer/Peer.java index b54f4e0..89082fd 100644 --- a/central/src/main/java/moe/yuuta/dn42peering/peer/Peer.java +++ b/central/src/main/java/moe/yuuta/dn42peering/peer/Peer.java @@ -10,8 +10,8 @@ import io.vertx.sqlclient.templates.annotations.Column; import io.vertx.sqlclient.templates.annotations.ParametersMapped; import io.vertx.sqlclient.templates.annotations.RowMapped; import io.vertx.sqlclient.templates.annotations.TemplateParameter; -import moe.yuuta.dn42peering.provision.BGPRequestCommon; -import moe.yuuta.dn42peering.provision.WGRequestCommon; +import moe.yuuta.dn42peering.agent.proto.BGPConfig; +import moe.yuuta.dn42peering.agent.proto.WireGuardConfig; import javax.annotation.Nonnull; import java.io.IOException; @@ -191,31 +191,40 @@ public class Peer { public boolean isIPv6LinkLocal() throws IOException { return Inet6Address.getByName(ipv6).isLinkLocalAddress(); } - + + @Nonnull @GenIgnore - public WGRequestCommon toWGRequest() { - return new WGRequestCommon( - null, - (long)id, - Integer.parseInt(calcWireGuardPort()), - wgEndpoint == null && wgEndpointPort == null ? "" : - String.format("%s:%d", getWgEndpoint(), getWgEndpointPort()), - wgPeerPubkey, - wgSelfPrivKey, - wgPresharedSecret, - ipv4, - ipv6 == null ? "" : ipv6); + public WireGuardConfig toWireGuardConfig() { + if(type != VPNType.WIREGUARD) throw new IllegalStateException("The VPN type is not WireGuard."); + return WireGuardConfig.newBuilder() + .setId(id) + .setListenPort(Integer.parseInt(calcWireGuardPort())) + .setEndpoint(wgEndpoint == null && wgEndpointPort == null ? "" : + String.format("%s:%d", getWgEndpoint(), getWgEndpointPort())) + .setPeerPubKey(wgPeerPubkey) + .setSelfPrivKey(wgSelfPrivKey) + .setSelfPresharedSecret(wgPresharedSecret) + .setPeerIPv4(ipv4) + .setPeerIPv6(ipv6 == null ? "" : ipv6) + .setInterface(calculateWireGuardInterfaceName()) + .build(); } @GenIgnore - public BGPRequestCommon toBGPRequest() { - return new BGPRequestCommon(null, - (long)id, - asn, - mpbgp, - ipv4, - ipv6 == null ? "" : ipv6, - null); + public BGPConfig toBGPConfig() { + return BGPConfig.newBuilder() + .setId(id) + .setAsn(Long.parseLong(asn.substring(2))) + .setMpbgp(mpbgp) + .setIpv4(ipv4) + .setIpv6(ipv6 == null ? "" : ipv6) + .setInterface(calculateWireGuardInterfaceName()) + .build(); + } + + @Nonnull + public String calculateWireGuardInterfaceName() { + return String.format("wg_%d", id); } // START GETTERS / SETTERS diff --git a/central/src/main/java/moe/yuuta/dn42peering/provision/BGPRequestCommon.java b/central/src/main/java/moe/yuuta/dn42peering/provision/BGPRequestCommon.java deleted file mode 100644 index 8ca9ea2..0000000 --- a/central/src/main/java/moe/yuuta/dn42peering/provision/BGPRequestCommon.java +++ /dev/null @@ -1,127 +0,0 @@ -package moe.yuuta.dn42peering.provision; - -import io.vertx.codegen.annotations.DataObject; -import io.vertx.core.json.JsonObject; -import moe.yuuta.dn42peering.agent.proto.BGPRequest; - -import javax.annotation.Nonnull; - -@DataObject -public class BGPRequestCommon { - private NodeCommon node; - private Long id; - private String asn; - private Boolean mpbgp; - private String ipv4; - private String ipv6; - private String device; - - public BGPRequestCommon(NodeCommon node, - Long id, - String asn, - Boolean mpbgp, - String ipv4, - String ipv6, - String device) { - this.node = node; - this.id = id; - this.asn = asn; - this.mpbgp = mpbgp; - this.ipv4 = ipv4; - this.ipv6 = ipv6; - this.device = device; - } - - public BGPRequestCommon(@Nonnull JsonObject json) { - if(json.getValue("node") != null) this.node = new NodeCommon(json.getJsonObject("node")); - if(json.getValue("id") != null) this.id = json.getLong("id"); - this.asn = json.getString("asn"); - if(json.getValue("mpbgp") != null) this.mpbgp = json.getBoolean("mpbgp"); - this.ipv4 = json.getString("ipv4"); - this.ipv6 = json.getString("ipv6"); - this.device = json.getString("device"); - } - - @Nonnull - public JsonObject toJson() { - return new JsonObject() - .put("node", node == null ? null : node.toJson()) - .put("id", id) - .put("asn", asn) - .put("mpbgp", mpbgp) - .put("ipv4", ipv4) - .put("ipv6", ipv6) - .put("device", device); - } - - @Nonnull - public BGPRequest toGRPC() { - final BGPRequest.Builder builder = BGPRequest.newBuilder(); - if(node != null) builder.setNode(node.toGRPC()); - if(id != null) builder.setId(id); - if(asn != null) builder.setAsn(asn); - if(mpbgp != null) builder.setMpbgp(mpbgp); - if(ipv4 != null) builder.setIpv4(ipv4); - if(ipv6 != null) builder.setIpv6(ipv6); - if(device != null) builder.setDevice(device); - return builder.build(); - } - - // Getters & Setters - - public NodeCommon getNode() { - return node; - } - - public void setNode(NodeCommon node) { - this.node = node; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getAsn() { - return asn; - } - - public void setAsn(String asn) { - this.asn = asn; - } - - public Boolean getMpbgp() { - return mpbgp; - } - - public void setMpbgp(Boolean mpbgp) { - this.mpbgp = mpbgp; - } - - public String getIpv4() { - return ipv4; - } - - public void setIpv4(String ipv4) { - this.ipv4 = ipv4; - } - - public String getIpv6() { - return ipv6; - } - - public void setIpv6(String ipv6) { - this.ipv6 = ipv6; - } - - public String getDevice() { - return device; - } - - public void setDevice(String device) { - this.device = device; - } -} diff --git a/central/src/main/java/moe/yuuta/dn42peering/provision/IProvisionRemoteService.java b/central/src/main/java/moe/yuuta/dn42peering/provision/IProvisionRemoteService.java index ace1c54..16433ca 100644 --- a/central/src/main/java/moe/yuuta/dn42peering/provision/IProvisionRemoteService.java +++ b/central/src/main/java/moe/yuuta/dn42peering/provision/IProvisionRemoteService.java @@ -5,7 +5,6 @@ import io.vertx.codegen.annotations.ProxyGen; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; import io.vertx.core.Vertx; -import moe.yuuta.dn42peering.node.Node; import javax.annotation.Nonnull; @@ -20,37 +19,6 @@ public interface IProvisionRemoteService { @Fluent @Nonnull - IProvisionRemoteService provisionBGP(@Nonnull NodeCommon node, - @Nonnull BGPRequestCommon request, + IProvisionRemoteService deploy(long nodeId, @Nonnull Handler<AsyncResult<Void>> handler); - - @Fluent - @Nonnull - IProvisionRemoteService reloadBGP(@Nonnull NodeCommon node, - @Nonnull BGPRequestCommon request, - @Nonnull Handler<AsyncResult<Void>> handler); - - @Fluent - @Nonnull - IProvisionRemoteService deleteBGP(@Nonnull NodeCommon node, - @Nonnull BGPRequestCommon request, - @Nonnull Handler<AsyncResult<Void>> handler); - - @Fluent - @Nonnull - IProvisionRemoteService provisionWG(@Nonnull NodeCommon node, - @Nonnull WGRequestCommon request, - @Nonnull Handler<AsyncResult<String>> handler); - - @Fluent - @Nonnull - IProvisionRemoteService reloadWG(@Nonnull NodeCommon node, - @Nonnull WGRequestCommon request, - @Nonnull Handler<AsyncResult<String>> handler); - - @Fluent - @Nonnull - IProvisionRemoteService deleteWG(@Nonnull NodeCommon node, - @Nonnull WGRequestCommon request, - @Nonnull Handler<AsyncResult<Void>> handler); } diff --git a/central/src/main/java/moe/yuuta/dn42peering/provision/NodeCommon.java b/central/src/main/java/moe/yuuta/dn42peering/provision/NodeCommon.java deleted file mode 100644 index 316965c..0000000 --- a/central/src/main/java/moe/yuuta/dn42peering/provision/NodeCommon.java +++ /dev/null @@ -1,112 +0,0 @@ -package moe.yuuta.dn42peering.provision; - -import io.vertx.codegen.annotations.DataObject; -import io.vertx.core.json.JsonObject; -import moe.yuuta.dn42peering.agent.proto.Node; - -import javax.annotation.Nonnull; - -@DataObject -public class NodeCommon { - private Long id; - private String ipv4; - private String ipv6; - private String ipv6NonLL; - private String internalIp; - private int internalPort; - - public NodeCommon(long id, - @Nonnull String ipv4, - @Nonnull String ipv6, - @Nonnull String ipv6NonLL, - @Nonnull String internalIp, - @Nonnull Integer internalPort) { - this.id = id; - this.ipv4 = ipv4; - this.ipv6 = ipv6; - this.ipv6NonLL = ipv6NonLL; - this.internalIp = internalIp; - this.internalPort = internalPort; - } - - public NodeCommon(@Nonnull JsonObject json) { - if(json.getValue("id") != null) this.id = json.getLong("id"); - else this.id = null; - this.ipv4 = json.getString("ipv4"); - this.ipv6 = json.getString("ipv6"); - this.ipv6NonLL = json.getString("ipv6_non_ll"); - this.internalIp = json.getString("internal_ip"); - if(json.getValue("internal_port") != null) this.internalPort = json.getInteger("internal_port"); - } - - @Nonnull - public JsonObject toJson() { - return new JsonObject() - .put("id", id) - .put("ipv4", ipv4) - .put("ipv6", ipv6) - .put("ipv6_non_ll", ipv6NonLL) - .put("internal_ip", internalIp) - .put("internal_port", internalPort); - } - - @Nonnull - public Node toGRPC() { - final Node.Builder builder = Node.newBuilder() - .setIpv4(ipv4) - .setIpv6(ipv6) - .setIpv6NonLL(ipv6NonLL); - if(id != null) builder.setId(id); - return builder.build(); - } - - // Getters & Getters - - public Long getId() { - return id; - } - - public String getIpv4() { - return ipv4; - } - - public String getIpv6() { - return ipv6; - } - - public String getIpv6NonLL() { - return ipv6NonLL; - } - - public void setId(Long id) { - this.id = id; - } - - public void setIpv4(String ipv4) { - this.ipv4 = ipv4; - } - - public void setIpv6(String ipv6) { - this.ipv6 = ipv6; - } - - public void setIpv6NonLL(String ipv6NonLL) { - this.ipv6NonLL = ipv6NonLL; - } - - String getInternalIp() { - return internalIp; - } - - void setInternalIp(String internalIp) { - this.internalIp = internalIp; - } - - int getInternalPort() { - return internalPort; - } - - void setInternalPort(int internalPort) { - this.internalPort = internalPort; - } -} diff --git a/central/src/main/java/moe/yuuta/dn42peering/provision/ProvisionRemoteServiceImpl.java b/central/src/main/java/moe/yuuta/dn42peering/provision/ProvisionRemoteServiceImpl.java index a642326..3e432bd 100644 --- a/central/src/main/java/moe/yuuta/dn42peering/provision/ProvisionRemoteServiceImpl.java +++ b/central/src/main/java/moe/yuuta/dn42peering/provision/ProvisionRemoteServiceImpl.java @@ -1,106 +1,88 @@ package moe.yuuta.dn42peering.provision; import io.grpc.ManagedChannel; -import io.vertx.core.*; +import io.vertx.core.AsyncResult; +import io.vertx.core.Future; +import io.vertx.core.Handler; +import io.vertx.core.Vertx; import io.vertx.grpc.VertxChannelBuilder; +import moe.yuuta.dn42peering.agent.proto.NodeConfig; import moe.yuuta.dn42peering.agent.proto.VertxAgentGrpc; +import moe.yuuta.dn42peering.jaba.Pair; +import moe.yuuta.dn42peering.node.INodeService; import moe.yuuta.dn42peering.node.Node; +import moe.yuuta.dn42peering.peer.IPeerService; +import moe.yuuta.dn42peering.peer.Peer; import javax.annotation.Nonnull; +import java.util.List; class ProvisionRemoteServiceImpl implements IProvisionRemoteService { private final Vertx vertx; + private final INodeService nodeService; + private final IPeerService peerService; ProvisionRemoteServiceImpl(@Nonnull Vertx vertx) { this.vertx = vertx; + this.nodeService = INodeService.createProxy(vertx); + this.peerService = IPeerService.createProxy(vertx); } - private @Nonnull ManagedChannel toChannel(@Nonnull NodeCommon node) { + private @Nonnull ManagedChannel toChannel(@Nonnull Node node) { return VertxChannelBuilder.forAddress(vertx, node.getInternalIp(), node.getInternalPort()) .usePlaintext() .build(); } - - @Nonnull - @Override - public IProvisionRemoteService provisionBGP(@Nonnull NodeCommon node, - @Nonnull BGPRequestCommon request, - @Nonnull Handler<AsyncResult<Void>> handler) { - final ManagedChannel channel = toChannel(node); - final VertxAgentGrpc.AgentVertxStub stub = VertxAgentGrpc.newVertxStub(channel); - stub.provisionBGP(request.toGRPC()) - .<Void>compose(reply -> Future.succeededFuture(null)) - .onComplete(res -> channel.shutdown()) - .onComplete(handler); - return this; - } - - @Nonnull - @Override - public IProvisionRemoteService reloadBGP(@Nonnull NodeCommon node, - @Nonnull BGPRequestCommon request, - @Nonnull Handler<AsyncResult<Void>> handler) { - final ManagedChannel channel = toChannel(node); - final VertxAgentGrpc.AgentVertxStub stub = VertxAgentGrpc.newVertxStub(channel); - stub.reloadBGP(request.toGRPC()) - .<Void>compose(reply -> Future.succeededFuture(null)) - .onComplete(res -> channel.shutdown()) - .onComplete(handler); - return this; - } - - @Nonnull - @Override - public IProvisionRemoteService deleteBGP(@Nonnull NodeCommon node, - @Nonnull BGPRequestCommon request, - @Nonnull Handler<AsyncResult<Void>> handler) { - final ManagedChannel channel = toChannel(node); - final VertxAgentGrpc.AgentVertxStub stub = VertxAgentGrpc.newVertxStub(channel); - stub.deleteBGP(request.toGRPC()) - .<Void>compose(reply -> Future.succeededFuture(null)) - .onComplete(res -> channel.shutdown()) - .onComplete(handler); - return this; - } - - @Nonnull - @Override - public IProvisionRemoteService provisionWG(@Nonnull NodeCommon node, - @Nonnull WGRequestCommon request, - @Nonnull Handler<AsyncResult<String>> handler) { - final ManagedChannel channel = toChannel(node); - final VertxAgentGrpc.AgentVertxStub stub = VertxAgentGrpc.newVertxStub(channel); - stub.provisionWG(request.toGRPC()) - .compose(wgReply -> Future.succeededFuture(wgReply.getDevice())) - .onComplete(res -> channel.shutdown()) - .onComplete(handler); - return this; - } - - @Nonnull - @Override - public IProvisionRemoteService reloadWG(@Nonnull NodeCommon node, - @Nonnull WGRequestCommon request, - @Nonnull Handler<AsyncResult<String>> handler) { - final ManagedChannel channel = toChannel(node); - final VertxAgentGrpc.AgentVertxStub stub = VertxAgentGrpc.newVertxStub(channel); - stub.reloadWG(request.toGRPC()) - .compose(wgReply -> Future.succeededFuture(wgReply.getDevice())) - .onComplete(res -> channel.shutdown()) - .onComplete(handler); - return this; - } @Nonnull @Override - public IProvisionRemoteService deleteWG(@Nonnull NodeCommon node, - @Nonnull WGRequestCommon request, - @Nonnull Handler<AsyncResult<Void>> handler) { - final ManagedChannel channel = toChannel(node); - final VertxAgentGrpc.AgentVertxStub stub = VertxAgentGrpc.newVertxStub(channel); - stub.deleteWG(request.toGRPC()) - .<Void>compose(wgReply -> Future.succeededFuture(null)) - .onComplete(res -> channel.shutdown()) + public IProvisionRemoteService deploy(long nodeId, + @Nonnull Handler<AsyncResult<Void>> handler) { + vertx.sharedData().getLockWithTimeout("deploy_" + nodeId, 30 * 1000) + .<Void>compose(lock -> { + return Future.<moe.yuuta.dn42peering.node.Node>future(f -> nodeService.getNode((int)nodeId, f)) + .compose(node -> { + if (node == null) { + return Future.failedFuture("Invalid node"); + } else { + return Future.succeededFuture(node); + } + }) + .compose(node -> { + final NodeConfig.Builder builder = NodeConfig.newBuilder(); + builder.setNode(node.toRPCNode().build()); + return Future.succeededFuture(new Pair<>(node, builder)); + }) + .compose(pair -> { + final Node node = pair.a; + final NodeConfig.Builder builder = pair.b; + return Future.<List<Peer>>future(f -> peerService.listUnderNode(node.getId(), f)) + .compose(peers -> { + peers.forEach(peer -> { + builder.addBgps(peer.toBGPConfig()); + switch (peer.getType()) { + case WIREGUARD: + builder.addWgs(peer.toWireGuardConfig()); + break; + default: + throw new IllegalArgumentException("Bug: Unsupported VPN type"); + } + }); + return Future.succeededFuture(pair); + }); + }) + .compose(pair -> { + final ManagedChannel channel = toChannel(pair.a); + final VertxAgentGrpc.AgentVertxStub stub = VertxAgentGrpc.newVertxStub(channel); + return stub.deploy(pair.b.build()) + .<Void>compose(reply -> Future.succeededFuture(null)) + .onComplete(res -> channel.shutdown()); + }) + .compose(_v -> { + lock.release(); + return Future.succeededFuture(); + }); + }) .onComplete(handler); return this; } diff --git a/central/src/main/java/moe/yuuta/dn42peering/provision/WGRequestCommon.java b/central/src/main/java/moe/yuuta/dn42peering/provision/WGRequestCommon.java deleted file mode 100644 index d4694b6..0000000 --- a/central/src/main/java/moe/yuuta/dn42peering/provision/WGRequestCommon.java +++ /dev/null @@ -1,155 +0,0 @@ -package moe.yuuta.dn42peering.provision; - -import io.vertx.codegen.annotations.DataObject; -import io.vertx.core.json.JsonObject; -import moe.yuuta.dn42peering.agent.proto.WGRequest; - -import javax.annotation.Nonnull; - -@DataObject -public class WGRequestCommon { - private NodeCommon node; - private Long id; - private Integer listenPort; - private String endpoint; - private String peerPubKey; - private String selfPrivKey; - private String selfPresharedSecret; - private String peerIPv4; - private String peerIPv6; - - public WGRequestCommon(NodeCommon node, - Long id, - Integer listenPort, - String endpoint, - String peerPubKey, - String selfPrivKey, - String selfPresharedSecret, - String peerIPv4, - String peerIPv6) { - this.node = node; - this.id = id; - this.listenPort = listenPort; - this.endpoint = endpoint; - this.peerPubKey = peerPubKey; - this.selfPrivKey = selfPrivKey; - this.selfPresharedSecret = selfPresharedSecret; - this.peerIPv4 = peerIPv4; - this.peerIPv6 = peerIPv6; - } - - public WGRequestCommon(@Nonnull JsonObject json) { - if(json.getValue("node") != null) this.node = new NodeCommon(json.getJsonObject("node")); - if(json.getValue("id") != null) this.id = json.getLong("id"); - if(json.getValue("listen_port") != null) this.listenPort = json.getInteger("listen_port"); - this.endpoint = json.getString("endpoint"); - this.peerPubKey = json.getString("peer_public_key"); - this.selfPrivKey = json.getString("self_private_key"); - this.selfPresharedSecret = json.getString("self_preshared_secret"); - this.peerIPv4 = json.getString("peer_ipv4"); - this.peerIPv6 = json.getString("peer_ipv6"); - } - - @Nonnull - public JsonObject toJson() { - return new JsonObject() - .put("node", node == null ? null : node.toJson()) - .put("id", id) - .put("listen_port", listenPort) - .put("endpoint", endpoint) - .put("peer_public_key", peerPubKey) - .put("self_private_key", selfPrivKey) - .put("self_preshared_secret", selfPresharedSecret) - .put("peer_ipv4", peerIPv4) - .put("peer_ipv6", peerIPv6); - } - - @Nonnull - public WGRequest toGRPC() { - final WGRequest.Builder builder = WGRequest.newBuilder(); - if(node != null) builder.setNode(node.toGRPC()); - if(id != null) builder.setId(id); - if(listenPort != null) builder.setListenPort(listenPort); - if(endpoint != null) builder.setEndpoint(endpoint); - if(peerPubKey != null) builder.setPeerPubKey(peerPubKey); - if(selfPrivKey != null) builder.setSelfPrivKey(selfPrivKey); - if(selfPresharedSecret != null) builder.setSelfPresharedSecret(selfPresharedSecret); - if(peerIPv4 != null) builder.setPeerIPv4(peerIPv4); - if(peerIPv6 != null) builder.setPeerIPv6(peerIPv6); - return builder.build(); - } - - // Getters & Setters - - public NodeCommon getNode() { - return node; - } - - public void setNode(NodeCommon node) { - this.node = node; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Integer getListenPort() { - return listenPort; - } - - public void setListenPort(Integer listenPort) { - this.listenPort = listenPort; - } - - public String getEndpoint() { - return endpoint; - } - - public void setEndpoint(String endpoint) { - this.endpoint = endpoint; - } - - public String getPeerPubKey() { - return peerPubKey; - } - - public void setPeerPubKey(String peerPubKey) { - this.peerPubKey = peerPubKey; - } - - public String getSelfPrivKey() { - return selfPrivKey; - } - - public void setSelfPrivKey(String selfPrivKey) { - this.selfPrivKey = selfPrivKey; - } - - public String getSelfPresharedSecret() { - return selfPresharedSecret; - } - - public void setSelfPresharedSecret(String selfPresharedSecret) { - this.selfPresharedSecret = selfPresharedSecret; - } - - public String getPeerIPv4() { - return peerIPv4; - } - - public void setPeerIPv4(String peerIPv4) { - this.peerIPv4 = peerIPv4; - } - - public String getPeerIPv6() { - return peerIPv6; - } - - public void setPeerIPv6(String peerIPv6) { - this.peerIPv6 = peerIPv6; - } -} diff --git a/central/src/main/resources/manage/index.ftlh b/central/src/main/resources/manage/index.ftlh index 2cac44a..d56c9aa 100644 --- a/central/src/main/resources/manage/index.ftlh +++ b/central/src/main/resources/manage/index.ftlh @@ -14,14 +14,12 @@ <tr> <th>IP</th> <th>Method</th> - <th>Provision</th> <th>Actions</th> </tr> <#list peers as peer> <tr> <td>${peer.ipv4}<#if peer.ipv6??><br />${peer.ipv6}</#if></td> <td>${peer.type}</td> - <td>${peer.provisionStatus}</td> <td><a href="/manage/edit?id=${peer.id}">Edit</a> | <a href="/manage/show-configuration?id=${peer.id}">Example Conf</a></td> </tr> diff --git a/rpc-common/src/main/proto/agent.proto b/rpc-common/src/main/proto/agent.proto index 585213e..1843917 100644 --- a/rpc-common/src/main/proto/agent.proto +++ b/rpc-common/src/main/proto/agent.proto @@ -6,42 +6,39 @@ option java_outer_classname = "AgentProto"; package moe.yuuta.dn42peering.agent; service Agent { - rpc ProvisionBGP (BGPRequest) returns (BGPReply) {} - rpc ReloadBGP (BGPRequest) returns (BGPReply) {} - rpc DeleteBGP (BGPRequest) returns (BGPReply) {} - - rpc ProvisionWG (WGRequest) returns (WGReply) {} - rpc ReloadWG (WGRequest) returns (WGReply) {} - rpc DeleteWG (WGRequest) returns (WGReply) {} + rpc Deploy (NodeConfig) returns (DeployResult) {} } -message BGPRequest { - Node node = 1; - uint64 id = 2; - string asn = 3; - bool mpbgp = 4; - string ipv4 = 5; - string ipv6 = 6; - string device = 7; +message DeployResult { + repeated uint64 successfulIDs = 1; + repeated uint64 failedIDs = 2; } -message BGPReply { +message NodeConfig { + Node node = 1; + repeated BGPConfig bgps = 2; + repeated WireGuardConfig wgs = 3; } -message WGRequest { - Node node = 1; - uint64 id = 2; - uint32 listenPort = 3; - string endpoint = 4; - string peerPubKey = 5; - string selfPrivKey = 6; - string selfPresharedSecret = 7; - string peerIPv4 = 8; - string peerIPv6 = 9; +message BGPConfig { + uint64 id = 1; + uint64 asn = 2; + bool mpbgp = 3; + string ipv4 = 4; + string ipv6 = 5; + string interface = 6; } -message WGReply { - string device = 1; +message WireGuardConfig { + uint64 id = 1; + uint32 listenPort = 2; + string endpoint = 3; + string peerPubKey = 4; + string selfPrivKey = 5; + string selfPresharedSecret = 6; + string peerIPv4 = 7; + string peerIPv6 = 8; + string interface = 9; } message Node { @@ -49,4 +46,5 @@ message Node { string ipv4 = 2; string ipv6 = 3; string ipv6NonLL = 4; + uint64 asn = 5; }
\ No newline at end of file |