From c1714ff97f3574fa0c8c280104a2bac8bdf15439 Mon Sep 17 00:00:00 2001 From: maxwellpettit Date: Fri, 17 Mar 2017 12:14:38 -0400 Subject: [PATCH] 0003019: Add Registration Screen when Registering a Remote Node --- .../ext/INodeRegistrationListener.java | 18 ++ .../service/IRegistrationService.java | 171 +++++++++--------- .../service/impl/DataLoaderService.java | 7 + .../service/impl/RegistrationService.java | 143 +++++++++++---- 4 files changed, 218 insertions(+), 121 deletions(-) create mode 100644 symmetric-core/src/main/java/org/jumpmind/symmetric/ext/INodeRegistrationListener.java diff --git a/symmetric-core/src/main/java/org/jumpmind/symmetric/ext/INodeRegistrationListener.java b/symmetric-core/src/main/java/org/jumpmind/symmetric/ext/INodeRegistrationListener.java new file mode 100644 index 0000000000..abfb94317e --- /dev/null +++ b/symmetric-core/src/main/java/org/jumpmind/symmetric/ext/INodeRegistrationListener.java @@ -0,0 +1,18 @@ +package org.jumpmind.symmetric.ext; + +import org.jumpmind.extension.IExtensionPoint; + +public interface INodeRegistrationListener extends IExtensionPoint { + + public void registrationUrlUpdated(String url); + + public void registrationNextAttemptUpdated(int seconds); + + public void registrationStarting(); + + public void registrationFailed(String message); + + public void registrationSyncTriggers(); + + public void registrationSuccessful(); +} \ No newline at end of file diff --git a/symmetric-core/src/main/java/org/jumpmind/symmetric/service/IRegistrationService.java b/symmetric-core/src/main/java/org/jumpmind/symmetric/service/IRegistrationService.java index 402ec4856f..6cada09375 100644 --- a/symmetric-core/src/main/java/org/jumpmind/symmetric/service/IRegistrationService.java +++ b/symmetric-core/src/main/java/org/jumpmind/symmetric/service/IRegistrationService.java @@ -1,37 +1,37 @@ -/** - * Licensed to JumpMind Inc under one or more contributor - * license agreements. See the NOTICE file distributed - * with this work for additional information regarding - * copyright ownership. JumpMind Inc licenses this file - * to you under the GNU General Public License, version 3.0 (GPLv3) - * (the "License"); you may not use this file except in compliance - * with the License. - * - * You should have received a copy of the GNU General Public License, - * version 3.0 (GPLv3) along with this library; if not, see - * . - * - * 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.jumpmind.symmetric.service; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.List; - -import org.jumpmind.symmetric.model.Node; -import org.jumpmind.symmetric.model.RegistrationRequest; - +/** + * Licensed to JumpMind Inc under one or more contributor + * license agreements. See the NOTICE file distributed + * with this work for additional information regarding + * copyright ownership. JumpMind Inc licenses this file + * to you under the GNU General Public License, version 3.0 (GPLv3) + * (the "License"); you may not use this file except in compliance + * with the License. + * + * You should have received a copy of the GNU General Public License, + * version 3.0 (GPLv3) along with this library; if not, see + * . + * + * 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.jumpmind.symmetric.service; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +import org.jumpmind.symmetric.model.Node; +import org.jumpmind.symmetric.model.RegistrationRequest; + /** * This service provides an API that deals with {@link Node} registration */ -public interface IRegistrationService { +public interface IRegistrationService { /** * Register a "Pull Only" node. This type of node has no Symmetric configuration and can only be used to PULL data from another node. @@ -51,8 +51,8 @@ public interface IRegistrationService { * @param isRequestedRegistration * An indicator that registration has been requested by the * remote client - */ - public boolean registerNode(Node node, String remoteHost, String remoteAddress, OutputStream out, boolean isRequestedRegistration) throws IOException; + */ + public boolean registerNode(Node node, String remoteHost, String remoteAddress, OutputStream out, boolean isRequestedRegistration) throws IOException; /** * Register a node for the given group name and external id if the @@ -63,64 +63,71 @@ public interface IRegistrationService { * remote client */ public boolean registerNode(Node node, OutputStream out, boolean isRequestedRegistration) throws IOException; - - /** - * Open registration for a single new node given a node group (f.e., - * "STORE") and external ID (f.e., "00001"). The unique node ID and password - * are generated and stored in the node and node_security tables with the - * registration_enabled flag turned on. The next node to try registering for - * this node group and external ID will be given this information. - * @return the node id - */ - public String openRegistration(String nodeGroupId, String externalId); - + + /** + * Open registration for a single new node given a node group (f.e., + * "STORE") and external ID (f.e., "00001"). The unique node ID and password + * are generated and stored in the node and node_security tables with the + * registration_enabled flag turned on. The next node to try registering for + * this node group and external ID will be given this information. + * @return the node id + */ + public String openRegistration(String nodeGroupId, String externalId); + public String openRegistration(String nodeGroup, String externalId, String remoteHost, String remoteAddress); public String openRegistration(Node node); - public boolean isRegistrationOpen(String nodeGroupId, String externalId); - - /** - * Re-open registration for a single node that already exists in the - * database. A new password is generated and the registration_enabled flag - * is turned on. The next node to try registering for this node group and - * external ID will be given this information. - */ - public void reOpenRegistration(String nodeId); - - /** - * Mark the passed in node as registered in node_security - * @param nodeId is the node that has just finished 'successfully' registering - */ - public void markNodeAsRegistered(String nodeId); - - public boolean isAutoRegistration(); - - /** - * Client method which attempts to register with the registration.url to - * pull configuration if the node has not already been registered. If the - * registration server cannot be reach this method will continue to try with - * random sleep periods up to one minute up until the registration succeeds - * or the maximum number of attempts has been reached. - */ + public boolean isRegistrationOpen(String nodeGroupId, String externalId); + + /** + * Re-open registration for a single node that already exists in the + * database. A new password is generated and the registration_enabled flag + * is turned on. The next node to try registering for this node group and + * external ID will be given this information. + */ + public void reOpenRegistration(String nodeId); + + /** + * Mark the passed in node as registered in node_security + * @param nodeId is the node that has just finished 'successfully' registering + */ + public void markNodeAsRegistered(String nodeId); + + public boolean isAutoRegistration(); + + /** + * Client method which attempts to register with the registration.url to + * pull configuration if the node has not already been registered. If the + * registration server cannot be reach this method will continue to try with + * random sleep periods up to one minute up until the registration succeeds + * or the maximum number of attempts has been reached. + */ public void registerWithServer(); + /** + * Client method which attempts to register with the registration.url to + * pull configuration if the node has not already been registered. + * Returns true if registered successfully + */ + public boolean attemptToRegisterWithServer(int maxNumberOfAttempts); + public List getRegistrationRequests(boolean includeNodesWithOpenRegistrations); public boolean deleteRegistrationRequest(RegistrationRequest request); - public void saveRegistrationRequest(RegistrationRequest request); - - public boolean isRegisteredWithServer(); - - /** - * Add an entry to the registation_redirect table so that if a node tries to register here. It will be redirected to the correct node. - */ - public void saveRegistrationRedirect(String externalIdToRedirect, String nodeIdToRedirectTo); - - public String getRedirectionUrlFor(String externalId); - + public void saveRegistrationRequest(RegistrationRequest request); + + public boolean isRegisteredWithServer(); + + /** + * Add an entry to the registation_redirect table so that if a node tries to register here. It will be redirected to the correct node. + */ + public void saveRegistrationRedirect(String externalIdToRedirect, String nodeIdToRedirectTo); + + public String getRedirectionUrlFor(String externalId); + public void requestNodeCopy(); - - + + } \ No newline at end of file diff --git a/symmetric-core/src/main/java/org/jumpmind/symmetric/service/impl/DataLoaderService.java b/symmetric-core/src/main/java/org/jumpmind/symmetric/service/impl/DataLoaderService.java index a8d4a6ce69..ac7b190ef2 100644 --- a/symmetric-core/src/main/java/org/jumpmind/symmetric/service/impl/DataLoaderService.java +++ b/symmetric-core/src/main/java/org/jumpmind/symmetric/service/impl/DataLoaderService.java @@ -61,6 +61,7 @@ import org.jumpmind.symmetric.common.Constants; import org.jumpmind.symmetric.common.ErrorConstants; import org.jumpmind.symmetric.common.ParameterConstants; +import org.jumpmind.symmetric.ext.INodeRegistrationListener; import org.jumpmind.symmetric.io.IoConstants; import org.jumpmind.symmetric.io.data.Batch; import org.jumpmind.symmetric.io.data.Batch.BatchType; @@ -273,6 +274,12 @@ public void loadDataFromPull(Node remote, RemoteNodeStatus status) throws IOExce transport = transportManager.getRegisterTransport(local, parameterService.getRegistrationUrl()); log.info("Using registration URL of {}", transport.getUrl()); + + List registrationListeners = extensionService.getExtensionPointList(INodeRegistrationListener.class); + for (INodeRegistrationListener l : registrationListeners) { + l.registrationUrlUpdated(transport.getUrl()); + } + remote = new Node(); remote.setSyncUrl(parameterService.getRegistrationUrl()); isRegisterTransport = true; diff --git a/symmetric-core/src/main/java/org/jumpmind/symmetric/service/impl/RegistrationService.java b/symmetric-core/src/main/java/org/jumpmind/symmetric/service/impl/RegistrationService.java index d10042f61f..8e1afc451c 100644 --- a/symmetric-core/src/main/java/org/jumpmind/symmetric/service/impl/RegistrationService.java +++ b/symmetric-core/src/main/java/org/jumpmind/symmetric/service/impl/RegistrationService.java @@ -40,6 +40,7 @@ import org.jumpmind.symmetric.common.Constants; import org.jumpmind.symmetric.common.ParameterConstants; import org.jumpmind.symmetric.config.INodeIdCreator; +import org.jumpmind.symmetric.ext.INodeRegistrationListener; import org.jumpmind.symmetric.model.Node; import org.jumpmind.symmetric.model.NodeGroupLink; import org.jumpmind.symmetric.model.NodeSecurity; @@ -398,6 +399,10 @@ public void markNodeAsRegistered(String nodeId) { } throw ex; } finally { + List registrationListeners = extensionService.getExtensionPointList(INodeRegistrationListener.class); + for (INodeRegistrationListener l : registrationListeners) { + l.registrationSyncTriggers(); + } symmetricDialect.enableSyncTriggers(transaction); close(transaction); } @@ -407,6 +412,10 @@ private void sleepBeforeRegistrationRetry() { long sleepTimeInMs = DateUtils.MILLIS_PER_SECOND * randomTimeSlot.getRandomValueSeededByExternalId(); log.info("Could not register. Sleeping for {}ms before attempting again.", sleepTimeInMs); + List registrationListeners = extensionService.getExtensionPointList(INodeRegistrationListener.class); + for (INodeRegistrationListener l : registrationListeners) { + l.registrationNextAttemptUpdated((int)(sleepTimeInMs/1000)); + } AppUtils.sleep(sleepTimeInMs); } @@ -420,66 +429,122 @@ public boolean isRegisteredWithServer() { public void registerWithServer() { boolean registered = isRegisteredWithServer(); int maxNumberOfAttempts = parameterService - .getInt(ParameterConstants.REGISTRATION_NUMBER_OF_ATTEMPTS); + .getInt(ParameterConstants.REGISTRATION_NUMBER_OF_ATTEMPTS); + while (!registered && (maxNumberOfAttempts < 0 || maxNumberOfAttempts > 0) && engine.isStarted()) { + + maxNumberOfAttempts--; + + registered = attemptToRegisterWithServer(maxNumberOfAttempts); + + if (!registered && maxNumberOfAttempts != 0) { + sleepBeforeRegistrationRetry(); + } + } + + if (!registered) { + throw new RegistrationFailedException(String.format( + "Failed after trying to register %s times.", + parameterService.getString(ParameterConstants.REGISTRATION_NUMBER_OF_ATTEMPTS))); + } + } + + public synchronized boolean attemptToRegisterWithServer(int maxNumberOfAttempts) { + List registrationListeners = extensionService.getExtensionPointList(INodeRegistrationListener.class); + + boolean registered = isRegisteredWithServer(); + if (!registered) { try { + for (INodeRegistrationListener l : registrationListeners) { + l.registrationStarting(); + } log.info("This node is unregistered. It will attempt to register using the registration.url"); String channelId = null; registered = dataLoaderService.loadDataFromPull(null, channelId).getStatus() == Status.DATA_PROCESSED; } catch (ConnectException e) { - log.warn("The request to register failed because the client failed to connect to the server. The connection error message was: {}", e.getMessage()); + log.warn( + "The request to register failed because the client failed to connect to the server. The connection error message was: {}", + e.getMessage()); + for (INodeRegistrationListener l : registrationListeners) { + l.registrationFailed( + "The request to register failed because the client failed to connect to the server. The connection error message was: " + + e.getMessage()); + } } catch (UnknownHostException e) { - log.warn("The request to register failed because the host was unknown. The unknown host exception was {}", e.getMessage()); - } catch (ConnectionRejectedException ex) { - log.warn("The request to register was rejected by the server. Either the server node is not started, the server is not configured properly or the registration url is incorrect"); + log.warn("The request to register failed because the host was unknown. The unknown host exception was: {}", e.getMessage()); + for (INodeRegistrationListener l : registrationListeners) { + l.registrationFailed("The request to register failed because the host was unknown. The unknown host exception was: " + + e.getMessage()); + } + } catch (ConnectionRejectedException e) { + log.warn( + "The request to register was rejected by the server. Either the server node is not started, the server is not configured properly or the registration url is incorrect"); + for (INodeRegistrationListener l : registrationListeners) { + l.registrationFailed( + "The request to register was rejected by the server. Either the server node is not started, the server is not configured properly or the registration url is incorrect"); + } } catch (RegistrationNotOpenException e) { log.warn("Unable to register with server because registration is not open."); + for (INodeRegistrationListener l : registrationListeners) { + l.registrationFailed("Unable to register with server because registration is not open."); + } } catch (ServiceUnavailableException e) { log.warn("Unable to register with server because the service is not available. It may be starting up."); + for (INodeRegistrationListener l : registrationListeners) { + l.registrationFailed("Unable to register with server because the service is not available. It may be starting up."); + } } catch (Exception e) { - log.error("Unexpected error during registration: " + (StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() : e.getClass().getName()), e); + log.error("Unexpected error during registration: " + + (StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() : e.getClass().getName()), e); + for (INodeRegistrationListener l : registrationListeners) { + l.registrationFailed("Unexpected error during registration: " + + (StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() : e.getClass().getName())); + } } - maxNumberOfAttempts--; + registered = checkRegistrationSuccessful(registered, maxNumberOfAttempts); - if (!registered && (maxNumberOfAttempts < 0 || maxNumberOfAttempts > 0)) { - registered = isRegisteredWithServer(); - if (registered) { - log.info("We registered, but were not able to acknowledge our registration. Sending a sql event to the node where we registered to indicate that we are alive and registered"); - Node identity = nodeService.findIdentity(); - Node parentNode = nodeService.findNode(identity.getCreatedAtNodeId()); - dataService - .insertSqlEvent( - parentNode, - "update " - + tablePrefix - + "_node_security set registration_enabled=1, registration_time=current_timestamp where node_id='" - + identity.getNodeId() + "'", false, -1, null); - } - } + } + return registered; + } + protected boolean checkRegistrationSuccessful(boolean registered, int maxNumberOfAttempts) { + if (!registered && (maxNumberOfAttempts < 0 || maxNumberOfAttempts > 0)) { + registered = isRegisteredWithServer(); if (registered) { - Node node = nodeService.findIdentity(); - if (node != null) { - log.info("Successfully registered node [id={}]", node.getNodeId()); - extensionService.refresh(); - dataService.heartbeat(true); - } else { - log.error("Node identity is missing after registration. The registration server may be misconfigured or have an error"); - registered = false; - } - } - - if (!registered && maxNumberOfAttempts != 0) { - sleepBeforeRegistrationRetry(); + log.info("We registered, but were not able to acknowledge our registration. Sending a sql event to the node where we registered to indicate that we are alive and registered"); + Node identity = nodeService.findIdentity(); + Node parentNode = nodeService.findNode(identity.getCreatedAtNodeId()); + dataService + .insertSqlEvent( + parentNode, + "update " + + tablePrefix + + "_node_security set registration_enabled=1, registration_time=current_timestamp where node_id='" + + identity.getNodeId() + "'", false, -1, null); } } - if (!registered) { - throw new RegistrationFailedException(String.format( - "Failed after trying to register %s times.", - parameterService.getString(ParameterConstants.REGISTRATION_NUMBER_OF_ATTEMPTS))); + if (registered) { + List registrationListeners = extensionService.getExtensionPointList(INodeRegistrationListener.class); + + Node node = nodeService.findIdentity(); + if (node != null) { + log.info("Successfully registered node [id={}]", node.getNodeId()); + extensionService.refresh(); + dataService.heartbeat(true); + for (INodeRegistrationListener l : registrationListeners) { + l.registrationSuccessful(); + } + } else { + log.error("Node identity is missing after registration. The registration server may be misconfigured or have an error"); + for (INodeRegistrationListener l : registrationListeners) { + l.registrationFailed("Node identity is missing after registration. The registration server may be misconfigured or have an error"); + } + registered = false; + } } + return registered; } /**