This is an automated email from the ASF dual-hosted git repository.

jxue pushed a commit to branch helix-gateway-service
in repository https://gitbox.apache.org/repos/asf/helix.git


The following commit(s) were added to refs/heads/helix-gateway-service by this 
push:
     new 5f1e7ff7d Set up helix-gateway folder, add PoC code and add protobuf 
(#2826)
5f1e7ff7d is described below

commit 5f1e7ff7d37780c3261bdca0f66ef06fb4cacfaa
Author: Junkai Xue <junkai....@gmail.com>
AuthorDate: Mon Jul 8 10:25:21 2024 -0700

    Set up helix-gateway folder, add PoC code and add protobuf (#2826)
    
    * Set up helix-gateway folder structure and add PoC code
    
    * Add basic proto for gRPC calls
---
 helix-gateway/LICENSE                              | 270 +++++++++++++++++++++
 helix-gateway/NOTICE                               |  37 +++
 helix-gateway/helix-gateway-1.4.1-SNAPSHOT.ivy     |  56 +++++
 helix-gateway/pom.xml                              | 163 +++++++++++++
 helix-gateway/src/assemble/assembly.xml            |  60 +++++
 .../org/apache/helix/gateway/HelixGatewayMain.java |  74 ++++++
 .../helix/gateway/constant/MessageStatus.java      |   5 +
 .../apache/helix/gateway/constant/MessageType.java |   5 +
 .../helix/gateway/mock/ControllerManager.java      | 111 +++++++++
 .../apache/helix/gateway/mock/MockApplication.java | 100 ++++++++
 .../helix/gateway/mock/MockProtoRequest.java       |  56 +++++
 .../helix/gateway/mock/MockProtoResponse.java      |  15 ++
 .../helix/gateway/service/ClusterManager.java      |  51 ++++
 .../HelixGatewayOnlineOfflineStateModel.java       |  81 +++++++
 ...HelixGatewayOnlineOfflineStateModelFactory.java |  17 ++
 .../helix/gateway/service/HelixGatewayService.java |  53 ++++
 .../HelixGatewayService.proto                      |  35 +++
 helix-gateway/src/test/conf/testng.xml             |  27 +++
 helix-gateway/src/test/resources/log4j2.properties |  63 +++++
 pom.xml                                            |   1 +
 20 files changed, 1280 insertions(+)

diff --git a/helix-gateway/LICENSE b/helix-gateway/LICENSE
new file mode 100644
index 000000000..d78ae52e7
--- /dev/null
+++ b/helix-gateway/LICENSE
@@ -0,0 +1,270 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
+For xstream:
+
+Copyright (c) 2003-2006, Joe Walnes
+Copyright (c) 2006-2009, 2011 XStream Committers
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this 
list of
+conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, 
this list of
+conditions and the following disclaimer in the documentation and/or other 
materials provided
+with the distribution.
+
+3. Neither the name of XStream nor the names of its contributors may be used 
to endorse
+or promote products derived from this software without specific prior written
+permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 
EVENT
+SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY
+WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+for jline:
+
+Copyright (c) 2002-2006, Marc Prud'hommeaux <m...@cornell.edu>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the following
+conditions are met:
+
+Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with
+the distribution.
+
+Neither the name of JLine nor the names of its contributors
+may be used to endorse or promote products derived from this
+software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/helix-gateway/NOTICE b/helix-gateway/NOTICE
new file mode 100644
index 000000000..ff5a745fe
--- /dev/null
+++ b/helix-gateway/NOTICE
@@ -0,0 +1,37 @@
+Apache Helix
+Copyright 2014 The Apache Software Foundation
+
+
+I. Included Software
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+Codehaus (http://www.codehaus.org/).
+Licensed under the BSD License.
+
+This product includes software developed at
+jline (http://jline.sourceforge.net/).
+Licensed under the BSD License.
+
+This product includes software developed at
+restlet (http://www.restlet.org/about/legal).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+Google (http://www.google.com/).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+snakeyaml (http://www.snakeyaml.org/).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+zkclient (https://github.com/sgroschupf/zkclient).
+Licensed under the Apache License 2.0.
+
+II. License Summary
+- Apache License 2.0
+- BSD License
\ No newline at end of file
diff --git a/helix-gateway/helix-gateway-1.4.1-SNAPSHOT.ivy 
b/helix-gateway/helix-gateway-1.4.1-SNAPSHOT.ivy
new file mode 100644
index 000000000..5d0f28d45
--- /dev/null
+++ b/helix-gateway/helix-gateway-1.4.1-SNAPSHOT.ivy
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<ivy-module version="1.0">
+       <info organisation="org.apache.helix"
+               module="helix-gateway"
+               revision="1.4.1-SNAPSHOT"
+               status="integration"
+               publication="20240627141623"
+       />
+       <configurations>
+               <conf name="default" visibility="public" description="runtime 
dependencies and master artifact can be used with this conf" 
extends="runtime,master"/>
+               <conf name="master" visibility="public" description="contains 
only the artifact published by this module itself, with no transitive 
dependencies"/>
+               <conf name="compile" visibility="public" description="this is 
the default scope, used if none is specified. Compile dependencies are 
available in all classpaths."/>
+               <conf name="provided" visibility="public" description="this is 
much like compile, but indicates you expect the JDK or a container to provide 
it. It is only available on the compilation classpath, and is not transitive."/>
+               <conf name="runtime" visibility="public" description="this 
scope indicates that the dependency is not required for compilation, but is for 
execution. It is in the runtime and test classpaths, but not the compile 
classpath." extends="compile"/>
+               <conf name="test" visibility="private" description="this scope 
indicates that the dependency is not required for normal use of the 
application, and is only available for the test compilation and execution 
phases."/>
+               <conf name="system" visibility="public" description="this scope 
is similar to provided except that you have to provide the JAR which contains 
it explicitly. The artifact is always available and is not looked up in a 
repository."/>
+       </configurations>
+       <publications>
+               <artifact name="helix-gateway" type="jar" ext="jar" 
conf="master"/>
+       </publications>
+       <dependencies>
+         <dependency org="org.slf4j" name="slf4j-api" rev="1.7.32" 
force="true" conf="compile->compile(*),master(*);runtime->runtime(*)">
+        <artifact name="slf4j-api" ext="jar"/>
+    </dependency>
+    <dependency org="org.apache.logging.log4j" name="log4j-slf4j-impl" 
rev="2.17.1" force="true" 
conf="compile->compile(*),master(*);runtime->runtime(*)">
+        <artifact name="log4j-slf4j-impl" ext="jar"/>
+    </dependency>
+    <dependency org="org.yaml" name="snakeyaml" rev="1.30">
+        <artifact name="snakeyaml" m:classifier="sources" ext="jar"/>
+    </dependency>
+               <dependency org="org.apache.helix" name="helix-core" 
rev="1.4.1-SNAPSHOT" force="true" 
conf="compile->compile(*),master(*);runtime->runtime(*)"/>
+               <dependency org="com.fasterxml.jackson.core" 
name="jackson-databind" rev="2.12.6.1" force="true" 
conf="compile->compile(*),master(*);runtime->runtime(*)"/>
+               <dependency org="com.fasterxml.jackson.core" 
name="jackson-core" rev="2.12.6" force="true" 
conf="compile->compile(*),master(*);runtime->runtime(*)"/>
+               <dependency org="commons-cli" name="commons-cli" rev="1.2" 
force="true" conf="compile->compile(*),master(*);runtime->runtime(*)"/>
+               <dependency org="io.dropwizard.metrics" name="metrics-jersey2" 
rev="4.1.14" force="true" 
conf="compile->compile(*),master(*);runtime->runtime(*)"/>
+               <dependency org="io.dropwizard.metrics" name="metrics-jmx" 
rev="4.1.14" force="true" 
conf="compile->compile(*),master(*);runtime->runtime(*)"/>
+       </dependencies>
+</ivy-module>
diff --git a/helix-gateway/pom.xml b/helix-gateway/pom.xml
new file mode 100644
index 000000000..b40de852a
--- /dev/null
+++ b/helix-gateway/pom.xml
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <parent>
+    <groupId>org.apache.helix</groupId>
+    <artifactId>helix</artifactId>
+    <version>1.4.1-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>helix-gateway</artifactId>
+  <packaging>bundle</packaging>
+  <name>Apache Helix :: Helix Gateway</name>
+
+  <licenses>
+    <license>
+      <name>Apache License, Version 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+
+  <organization>
+    <name>Apache Software Foundation</name>
+    <url>http://www.apache.org</url>
+  </organization>
+
+  <properties>
+    <osgi.import>
+      org.slf4j*;version="[1.7,2)",
+      org.apache.logging.log4j*;version="[2.17,3)",
+      org.apache.logging.slf4j*;version="[2.17,3)",
+      *
+    </osgi.import>
+    
<osgi.export>org.apache.helix*;version="${project.version};-noimport:=true</osgi.export>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.helix</groupId>
+      <artifactId>metrics-common</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.helix</groupId>
+      <artifactId>zookeeper-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <version>1.7.32</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-slf4j-impl</artifactId>
+      <version>2.17.1</version>
+    </dependency>
+    <dependency>
+      <groupId>org.testng</groupId>
+      <artifactId>testng</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.helix</groupId>
+      <artifactId>helix-core</artifactId>
+    </dependency>
+  </dependencies>
+  <build>
+    <resources>
+      <resource>
+        <directory>${basedir}</directory>
+        <includes>
+          <include>DISCLAIMER</include>
+        </includes>
+      </resource>
+    </resources>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.12.1</version>
+        <executions>
+          <execution>
+            <id>JDK 8</id>
+            <phase>compile</phase>
+            <goals>
+              <goal>compile</goal>
+            </goals>
+            <configuration>
+              
<outputDirectory>${project.build.outputDirectory}_jdk8</outputDirectory>
+              <release>8</release>
+              <fork>true</fork>
+            </configuration>
+          </execution>
+          <execution>
+            <id>JDK 11</id>
+            <phase>compile</phase>
+            <goals>
+              <goal>compile</goal>
+            </goals>
+            <configuration>
+              <release>11</release>
+              <fork>true</fork>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <version>3.3.0</version>
+        <executions>
+          <execution>
+            <id>default-package-jdk11</id>
+            <phase>package</phase>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+            <configuration>
+              
<classesDirectory>${project.build.outputDirectory}_jdk8</classesDirectory>
+              <classifier>jdk8</classifier>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <configuration>
+          <descriptors>
+            <descriptor>src/assemble/assembly.xml</descriptor>
+          </descriptors>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/helix-gateway/src/assemble/assembly.xml 
b/helix-gateway/src/assemble/assembly.xml
new file mode 100644
index 000000000..a3d451fd1
--- /dev/null
+++ b/helix-gateway/src/assemble/assembly.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<assembly>
+  <id>pkg</id>
+  <formats>
+    <format>tar</format>
+  </formats>
+  <fileSets>
+    <fileSet>
+      <directory>target/helix-gateway-pkg/bin</directory>
+      <outputDirectory>bin</outputDirectory>
+      <lineEnding>unix</lineEnding>
+      <fileMode>0755</fileMode>
+      <directoryMode>0755</directoryMode>
+    </fileSet>
+    <fileSet>
+      <directory>target/helix-gateway-pkg/repo/</directory>
+      <outputDirectory>repo</outputDirectory>
+      <fileMode>0755</fileMode>
+      <directoryMode>0755</directoryMode>
+      <excludes>
+        <exclude>**/*.xml</exclude>
+      </excludes>
+    </fileSet>
+   <fileSet>
+      <directory>target/helix-gateway-pkg/conf</directory>
+      <outputDirectory>conf</outputDirectory>
+      <lineEnding>unix</lineEnding>
+      <fileMode>0755</fileMode>
+      <directoryMode>0755</directoryMode>
+    </fileSet>
+    <fileSet>
+      <directory>${project.basedir}</directory>
+      <outputDirectory>/</outputDirectory>
+      <includes>
+        <include>LICENSE</include>
+        <include>NOTICE</include>
+        <include>DISCLAIMER</include>
+      </includes>
+      <fileMode>0755</fileMode>
+    </fileSet>
+  </fileSets>
+</assembly>
diff --git 
a/helix-gateway/src/main/java/org/apache/helix/gateway/HelixGatewayMain.java 
b/helix-gateway/src/main/java/org/apache/helix/gateway/HelixGatewayMain.java
new file mode 100644
index 000000000..0577aba02
--- /dev/null
+++ b/helix-gateway/src/main/java/org/apache/helix/gateway/HelixGatewayMain.java
@@ -0,0 +1,74 @@
+package org.apache.helix.gateway;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.helix.ConfigAccessor;
+import org.apache.helix.HelixAdmin;
+import org.apache.helix.InstanceType;
+import org.apache.helix.gateway.mock.ControllerManager;
+import org.apache.helix.gateway.mock.MockApplication;
+import org.apache.helix.gateway.service.HelixGatewayService;
+import org.apache.helix.manager.zk.ZKHelixAdmin;
+import org.apache.helix.model.ClusterConfig;
+import org.apache.helix.model.IdealState;
+import org.apache.helix.model.OnlineOfflineSMD;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.impl.client.ZkClient;
+
+public final class HelixGatewayMain {
+
+  private static final String ZK_ADDRESS = "localhost:2181";
+  private static final String CLUSTER_NAME = "TEST_CLUSTER";
+
+  private HelixGatewayMain() {
+  }
+
+  public static void main(String[] args) throws InterruptedException {
+    RealmAwareZkClient zkClient = new ZkClient(ZK_ADDRESS);
+    zkClient.setZkSerializer(new ZNRecordSerializer());
+    ConfigAccessor configAccessor = new ConfigAccessor(zkClient);
+    HelixAdmin admin = new ZKHelixAdmin(zkClient);
+    if (admin.getClusters().isEmpty()) {
+      admin.addCluster(CLUSTER_NAME);
+      admin.addStateModelDef(CLUSTER_NAME, "OnlineOffline", 
OnlineOfflineSMD.build());
+    }
+
+    ClusterConfig clusterConfig = 
configAccessor.getClusterConfig(CLUSTER_NAME);
+    clusterConfig.getRecord().setSimpleField("allowParticipantAutoJoin", 
"true");
+    configAccessor.updateClusterConfig(CLUSTER_NAME, clusterConfig);
+
+    String resourceName = "Test_Resource";
+
+    if (admin.getResourceIdealState(CLUSTER_NAME, resourceName) == null) {
+      admin.addResource(CLUSTER_NAME, resourceName, 2, "OnlineOffline",
+          IdealState.RebalanceMode.FULL_AUTO.name(),
+          
"org.apache.helix.controller.rebalancer.strategy.CrushEdRebalanceStrategy");
+      admin.rebalance(CLUSTER_NAME, resourceName, 3);
+    }
+
+    ControllerManager controllerManager =
+        new ControllerManager(ZK_ADDRESS, CLUSTER_NAME, "CONTROLLER", 
InstanceType.CONTROLLER);
+    controllerManager.syncStart();
+
+    HelixGatewayService service = new HelixGatewayService(ZK_ADDRESS);
+    service.start();
+
+    List<MockApplication> mockApplications = new ArrayList<>();
+    for (int i = 0; i < 6; i++) {
+      MockApplication mockApplication =
+          new MockApplication("INSTANCE_" + i, CLUSTER_NAME, 
service.getClusterManager());
+      service.registerParticipant(mockApplication);
+      mockApplications.add(mockApplication);
+    }
+
+    Thread.sleep(100000000);
+
+    MockApplication mockApplication = mockApplications.get(3);
+    service.deregisterParticipant(mockApplication.getClusterName(),
+        mockApplication.getInstanceName());
+
+    controllerManager.syncStop();
+  }
+}
diff --git 
a/helix-gateway/src/main/java/org/apache/helix/gateway/constant/MessageStatus.java
 
b/helix-gateway/src/main/java/org/apache/helix/gateway/constant/MessageStatus.java
new file mode 100644
index 000000000..b0bffc68c
--- /dev/null
+++ 
b/helix-gateway/src/main/java/org/apache/helix/gateway/constant/MessageStatus.java
@@ -0,0 +1,5 @@
+package org.apache.helix.gateway.constant;
+
+public enum MessageStatus {
+  SUCCESS, FAILURE
+}
diff --git 
a/helix-gateway/src/main/java/org/apache/helix/gateway/constant/MessageType.java
 
b/helix-gateway/src/main/java/org/apache/helix/gateway/constant/MessageType.java
new file mode 100644
index 000000000..7523901b5
--- /dev/null
+++ 
b/helix-gateway/src/main/java/org/apache/helix/gateway/constant/MessageType.java
@@ -0,0 +1,5 @@
+package org.apache.helix.gateway.constant;
+
+public enum MessageType {
+  ADD, REMOVE, CHANGE_ROLE
+}
diff --git 
a/helix-gateway/src/main/java/org/apache/helix/gateway/mock/ControllerManager.java
 
b/helix-gateway/src/main/java/org/apache/helix/gateway/mock/ControllerManager.java
new file mode 100644
index 000000000..3d33874c4
--- /dev/null
+++ 
b/helix-gateway/src/main/java/org/apache/helix/gateway/mock/ControllerManager.java
@@ -0,0 +1,111 @@
+package org.apache.helix.gateway.mock;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.helix.HelixManagerProperty;
+import org.apache.helix.InstanceType;
+import org.apache.helix.manager.zk.HelixManagerStateListener;
+import org.apache.helix.manager.zk.ZKHelixManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ControllerManager extends ZKHelixManager implements Runnable {
+  private static final int DISCONNECT_WAIT_TIME_MS = 3000;
+  private static Logger logger = 
LoggerFactory.getLogger(ControllerManager.class);
+  private static AtomicLong uid = new AtomicLong(10000);
+  private final String _clusterName;
+  private final String _instanceName;
+  private final InstanceType _type;
+  protected CountDownLatch _startCountDown = new CountDownLatch(1);
+  protected CountDownLatch _stopCountDown = new CountDownLatch(1);
+  protected CountDownLatch _waitStopFinishCountDown = new CountDownLatch(1);
+  protected boolean _started = false;
+  protected Thread _watcher;
+  private long _uid;
+
+  public ControllerManager(String zkAddr, String clusterName, String 
instanceName,
+      InstanceType type) {
+    super(clusterName, instanceName, type, zkAddr);
+    _clusterName = clusterName;
+    _instanceName = instanceName;
+    _type = type;
+    _uid = uid.getAndIncrement();
+  }
+
+  protected ControllerManager(String clusterName, String instanceName, 
InstanceType instanceType,
+      String zkAddress, HelixManagerStateListener stateListener,
+      HelixManagerProperty helixManagerProperty) {
+    super(clusterName, instanceName, instanceType, zkAddress, stateListener, 
helixManagerProperty);
+    _clusterName = clusterName;
+    _instanceName = instanceName;
+    _type = instanceType;
+    _uid = uid.getAndIncrement();
+  }
+
+  public void syncStop() {
+    _stopCountDown.countDown();
+    try {
+      _waitStopFinishCountDown.await();
+      _started = false;
+    } catch (InterruptedException e) {
+      logger.error("Interrupted waiting for finish", e);
+    }
+  }
+
+  // This should not be called more than once because HelixManager.connect() 
should not be called more than once.
+  public void syncStart() {
+    if (_started) {
+      throw new RuntimeException(
+          "Helix Controller already started. Do not call syncStart() more than 
once.");
+    } else {
+      _started = true;
+    }
+
+    _watcher = new Thread(this);
+    _watcher.setName(
+        String.format("ClusterManager_Watcher_%s_%s_%s_%d", _clusterName, 
_instanceName,
+            _type.name(), _uid));
+    logger.debug("ClusterManager_watcher_{}_{}_{}_{} started, stacktrace {}", 
_clusterName,
+        _instanceName, _type.name(), _uid, 
Thread.currentThread().getStackTrace());
+    _watcher.start();
+
+    try {
+      _startCountDown.await();
+    } catch (InterruptedException e) {
+      logger.error("Interrupted waiting for start", e);
+    }
+  }
+
+  @Override
+  public void run() {
+    try {
+      connect();
+      _startCountDown.countDown();
+      _stopCountDown.await();
+    } catch (Exception e) {
+      logger.error("exception running controller-manager", e);
+    } finally {
+      _startCountDown.countDown();
+      disconnect();
+      _waitStopFinishCountDown.countDown();
+    }
+  }
+
+  /**
+   @SuppressWarnings("finalizer")
+   @Override public void finalize() {
+   _watcher.interrupt();
+   try {
+   _watcher.join(DISCONNECT_WAIT_TIME_MS);
+   } catch (InterruptedException e) {
+   logger.error("ClusterManager watcher cleanup in the finalize method was 
interrupted.", e);
+   } finally {
+   if (isConnected()) {
+   logger.warn(
+   "The HelixManager ({}-{}-{}) is still connected after {} ms wait. This is a 
potential resource leakage!",
+   _clusterName, _instanceName, _type.name(), DISCONNECT_WAIT_TIME_MS);
+   }
+   }
+   }*/
+}
diff --git 
a/helix-gateway/src/main/java/org/apache/helix/gateway/mock/MockApplication.java
 
b/helix-gateway/src/main/java/org/apache/helix/gateway/mock/MockApplication.java
new file mode 100644
index 000000000..679f95f05
--- /dev/null
+++ 
b/helix-gateway/src/main/java/org/apache/helix/gateway/mock/MockApplication.java
@@ -0,0 +1,100 @@
+package org.apache.helix.gateway.mock;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.Executors;
+
+import org.apache.helix.gateway.service.ClusterManager;
+
+public class MockApplication {
+  private final ClusterManager _clusterManager;
+  private Map<String, Map<String, String>> _currentStates;
+  private String _instanceName;
+  private String _clusterName;
+  private Queue<MockProtoRequest> _requestQueue;
+
+  public MockApplication(String instanceName, String clusterName, 
ClusterManager clusterManager) {
+    _instanceName = instanceName;
+    _clusterName = clusterName;
+    _currentStates = new HashMap<>();
+    _requestQueue = new LinkedList<>();
+    _clusterManager = clusterManager;
+    Executors.newScheduledThreadPool(1)
+        .scheduleAtFixedRate(this::process, 0, 5000, 
java.util.concurrent.TimeUnit.MILLISECONDS);
+  }
+
+  public void process() {
+    List<MockProtoResponse> completedMessages = new ArrayList<>();
+    synchronized (_requestQueue) {
+      while (!_requestQueue.isEmpty()) {
+        MockProtoRequest request = _requestQueue.poll();
+        switch (request.getMessageType()) {
+          case ADD:
+            addShard(request.getResourceName(), request.getShardName());
+            completedMessages.add(new 
MockProtoResponse(request.getMessageId()));
+            break;
+          case REMOVE:
+            removeShard(request.getResourceName(), request.getShardName());
+            completedMessages.add(new 
MockProtoResponse(request.getMessageId()));
+            break;
+          case CHANGE_ROLE:
+            changeRole(request.getResourceName(), request.getShardName(), 
request.getFromState(),
+                request.getToState());
+            completedMessages.add(new 
MockProtoResponse(request.getMessageId()));
+            break;
+          default:
+            System.out.println("Unknown message type: " + 
request.getMessageType());
+            throw new RuntimeException("Unknown message type: " + 
request.getMessageType());
+        }
+      }
+    }
+    _clusterManager.receiveResponse(completedMessages, _instanceName);
+  }
+
+  public void addRequest(MockProtoRequest request) {
+    synchronized (_requestQueue) {
+      _requestQueue.add(request);
+    }
+  }
+
+  public String getInstanceName() {
+    return _instanceName;
+  }
+
+  public String getClusterName() {
+    return _clusterName;
+  }
+
+  public void join() {
+    System.out.println(
+        "Joining Mock Application for instance " + _instanceName + " in 
cluster " + _clusterName);
+  }
+
+  public synchronized void addShard(String resourceName, String shardName) {
+    System.out.println("ADD | " + shardName + " | " + resourceName + " | " + 
_instanceName);
+  }
+
+  public synchronized void removeShard(String resourceName, String shardName) {
+    System.out.println("REMOVE | " + shardName + " | " + resourceName + " | " 
+ _instanceName);
+  }
+
+  public synchronized void changeRole(String resourceName, String shardName, 
String fromState,
+      String toState) {
+    System.out.println(
+        "CHANGE ROLE | " + shardName + " | " + resourceName + " | " + 
_instanceName + " | "
+            + fromState + " -> " + toState);
+    _currentStates.computeIfAbsent(resourceName, k -> new 
HashMap<>()).put(shardName, toState);
+  }
+
+  private void sleep(long ms) {
+    try {
+      Thread.sleep(ms);
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git 
a/helix-gateway/src/main/java/org/apache/helix/gateway/mock/MockProtoRequest.java
 
b/helix-gateway/src/main/java/org/apache/helix/gateway/mock/MockProtoRequest.java
new file mode 100644
index 000000000..4e462e254
--- /dev/null
+++ 
b/helix-gateway/src/main/java/org/apache/helix/gateway/mock/MockProtoRequest.java
@@ -0,0 +1,56 @@
+package org.apache.helix.gateway.mock;
+
+import org.apache.helix.gateway.constant.MessageType;
+
+public class MockProtoRequest {
+
+  private String _messageId;
+  private String _instanceName;
+
+  private MessageType _messageType;
+  private String _resourceName;
+  private String _shardName;
+
+  private String _fromState;
+  private String _toState;
+
+  public MockProtoRequest(MessageType messageType, String resourceName, String 
shardName,
+      String instanceName, String messageId, String fromState, String toState) 
{
+    System.out.println(
+        messageType + " | " + shardName + " | " + resourceName + " | " + 
instanceName + " | "
+            + messageId + " | " + fromState + " | " + toState);
+    _messageId = messageId;
+    _instanceName = instanceName;
+    _messageType = messageType;
+    _resourceName = resourceName;
+    _shardName = shardName;
+  }
+
+  public MessageType getMessageType() {
+    return _messageType;
+  }
+
+  public String getResourceName() {
+    return _resourceName;
+  }
+
+  public String getShardName() {
+    return _shardName;
+  }
+
+  public String getFromState() {
+    return _fromState;
+  }
+
+  public String getToState() {
+    return _toState;
+  }
+
+  public String getMessageId() {
+    return _messageId;
+  }
+
+  public String getInstanceName() {
+    return _instanceName;
+  }
+}
diff --git 
a/helix-gateway/src/main/java/org/apache/helix/gateway/mock/MockProtoResponse.java
 
b/helix-gateway/src/main/java/org/apache/helix/gateway/mock/MockProtoResponse.java
new file mode 100644
index 000000000..108f49807
--- /dev/null
+++ 
b/helix-gateway/src/main/java/org/apache/helix/gateway/mock/MockProtoResponse.java
@@ -0,0 +1,15 @@
+package org.apache.helix.gateway.mock;
+
+public class MockProtoResponse {
+
+  private String _messageId;
+
+  public MockProtoResponse(String messageId) {
+    System.out.println("Finished process of message : " + messageId);
+    _messageId = messageId;
+  }
+
+  public String getMessageId() {
+    return _messageId;
+  }
+}
diff --git 
a/helix-gateway/src/main/java/org/apache/helix/gateway/service/ClusterManager.java
 
b/helix-gateway/src/main/java/org/apache/helix/gateway/service/ClusterManager.java
new file mode 100644
index 000000000..ad1a6eca6
--- /dev/null
+++ 
b/helix-gateway/src/main/java/org/apache/helix/gateway/service/ClusterManager.java
@@ -0,0 +1,51 @@
+package org.apache.helix.gateway.service;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.helix.gateway.mock.MockApplication;
+import org.apache.helix.gateway.mock.MockProtoRequest;
+import org.apache.helix.gateway.mock.MockProtoResponse;
+
+public class ClusterManager {
+  private Map<String, Map<String, AtomicBoolean>> _flagMap;
+  private Map<String, MockApplication> _channelMap;
+  private Lock _lock = new ReentrantLock();
+
+  public ClusterManager() {
+    _flagMap = new ConcurrentHashMap<>();
+    _channelMap = new ConcurrentHashMap<>();
+  }
+
+  public void addChannel(MockApplication mockApplication) {
+    _channelMap.put(mockApplication.getInstanceName(), mockApplication);
+    _flagMap.computeIfAbsent(mockApplication.getInstanceName(), k -> new 
ConcurrentHashMap<>());
+  }
+
+  public void removeChannel(String instanceName) {
+    _channelMap.remove(instanceName);
+    _flagMap.remove(instanceName);
+  }
+
+  public AtomicBoolean sendMessage(MockProtoRequest request) {
+    MockApplication mockApplication = 
_channelMap.get(request.getInstanceName());
+    synchronized (mockApplication) {
+      mockApplication.addRequest(request);
+      AtomicBoolean flag = new AtomicBoolean(false);
+      _flagMap.computeIfAbsent(request.getInstanceName(), k -> new 
ConcurrentHashMap<>())
+          .put(request.getMessageId(), flag);
+      return flag;
+    }
+  }
+
+  public synchronized void receiveResponse(List<MockProtoResponse> responses, 
String instanceName) {
+    for (MockProtoResponse response : responses) {
+      AtomicBoolean flag = 
_flagMap.get(instanceName).remove(response.getMessageId());
+      flag.set(true);
+    }
+  }
+}
diff --git 
a/helix-gateway/src/main/java/org/apache/helix/gateway/service/HelixGatewayOnlineOfflineStateModel.java
 
b/helix-gateway/src/main/java/org/apache/helix/gateway/service/HelixGatewayOnlineOfflineStateModel.java
new file mode 100644
index 000000000..37453e7d9
--- /dev/null
+++ 
b/helix-gateway/src/main/java/org/apache/helix/gateway/service/HelixGatewayOnlineOfflineStateModel.java
@@ -0,0 +1,81 @@
+package org.apache.helix.gateway.service;
+
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.helix.NotificationContext;
+import org.apache.helix.gateway.constant.MessageType;
+import org.apache.helix.gateway.mock.MockProtoRequest;
+import org.apache.helix.model.Message;
+import org.apache.helix.participant.statemachine.StateModel;
+
+public class HelixGatewayOnlineOfflineStateModel extends StateModel {
+  private boolean _firstTime = true;
+  private ClusterManager _clusterManager;
+
+  private String _resourceName;
+  private String _partitionKey;
+
+  private AtomicBoolean _completed;
+
+  public HelixGatewayOnlineOfflineStateModel(String resourceName, String 
partitionKey,
+      ClusterManager clusterManager) {
+    _resourceName = resourceName;
+    _partitionKey = partitionKey;
+    _clusterManager = clusterManager;
+  }
+
+  public void onBecomeOnlineFromOffline(Message message, NotificationContext 
context) {
+    if (_firstTime) {
+      wait(_clusterManager.sendMessage(
+          new MockProtoRequest(MessageType.ADD, message.getResourceName(),
+              message.getPartitionName(), message.getTgtName(), 
UUID.randomUUID().toString(),
+              message.getToState(), message.getFromState())));
+      System.out.println(
+          "Message for " + message.getPartitionName() + " instance " + 
message.getTgtName()
+              + " with ADD for " + message.getResourceName() + " processed");
+      _firstTime = false;
+    }
+    wait(_clusterManager.sendMessage(
+        new MockProtoRequest(MessageType.CHANGE_ROLE, 
message.getResourceName(),
+            message.getPartitionName(), message.getTgtName(), 
UUID.randomUUID().toString(),
+            message.getToState(), message.getFromState())));
+    System.out.println(
+        "Message for " + message.getPartitionName() + " instance " + 
message.getTgtName()
+            + " with CHANGE_ROLE_OFFLINE_ONLINE for " + 
message.getResourceName() + " processed");
+  }
+
+  public void onBecomeOfflineFromOnline(Message message, NotificationContext 
context) {
+    wait(_clusterManager.sendMessage(
+        new MockProtoRequest(MessageType.CHANGE_ROLE, 
message.getResourceName(),
+            message.getPartitionName(), message.getTgtName(), 
UUID.randomUUID().toString(),
+            message.getToState(), message.getFromState())));
+    System.out.println(
+        "Message for " + message.getPartitionName() + " instance " + 
message.getTgtName()
+            + " with CHANGE_ROLE_ONLINE_OFFLINE for " + 
message.getResourceName() + " processed");
+  }
+
+  public void onBecomeDroppedFromOffline(Message message, NotificationContext 
context) {
+    wait(_clusterManager.sendMessage(
+        new MockProtoRequest(MessageType.REMOVE, message.getResourceName(),
+            message.getPartitionName(), message.getTgtName(), 
UUID.randomUUID().toString(),
+            message.getToState(), message.getFromState())));
+    System.out.println(
+        "Message for " + message.getPartitionName() + " instance " + 
message.getTgtName()
+            + " with REMOVE for " + message.getResourceName() + " processed");
+  }
+
+  private void wait(AtomicBoolean completed) {
+    _completed = completed;
+    while (true) {
+      try {
+        if (_completed.get()) {
+          break;
+        }
+        Thread.sleep(100);
+      } catch (InterruptedException e) {
+        e.printStackTrace();
+      }
+    }
+  }
+}
diff --git 
a/helix-gateway/src/main/java/org/apache/helix/gateway/service/HelixGatewayOnlineOfflineStateModelFactory.java
 
b/helix-gateway/src/main/java/org/apache/helix/gateway/service/HelixGatewayOnlineOfflineStateModelFactory.java
new file mode 100644
index 000000000..71570ef15
--- /dev/null
+++ 
b/helix-gateway/src/main/java/org/apache/helix/gateway/service/HelixGatewayOnlineOfflineStateModelFactory.java
@@ -0,0 +1,17 @@
+package org.apache.helix.gateway.service;
+
+import org.apache.helix.participant.statemachine.StateModelFactory;
+
+public class HelixGatewayOnlineOfflineStateModelFactory extends 
StateModelFactory<HelixGatewayOnlineOfflineStateModel> {
+  private ClusterManager _clusterManager;
+
+  public HelixGatewayOnlineOfflineStateModelFactory(ClusterManager 
clusterManager) {
+    _clusterManager = clusterManager;
+  }
+
+  @Override
+  public HelixGatewayOnlineOfflineStateModel createNewStateModel(String 
resourceName,
+      String partitionKey) {
+    return new HelixGatewayOnlineOfflineStateModel(resourceName, partitionKey, 
_clusterManager);
+  }
+}
diff --git 
a/helix-gateway/src/main/java/org/apache/helix/gateway/service/HelixGatewayService.java
 
b/helix-gateway/src/main/java/org/apache/helix/gateway/service/HelixGatewayService.java
new file mode 100644
index 000000000..b4d21f921
--- /dev/null
+++ 
b/helix-gateway/src/main/java/org/apache/helix/gateway/service/HelixGatewayService.java
@@ -0,0 +1,53 @@
+package org.apache.helix.gateway.service;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.helix.HelixManager;
+import org.apache.helix.HelixManagerFactory;
+import org.apache.helix.InstanceType;
+import org.apache.helix.gateway.mock.MockApplication;
+
+public class HelixGatewayService {
+  final private Map<String, Map<String, HelixManager>> _participantsMap;
+
+  final private String _zkAddress;
+  private final ClusterManager _clusterManager;
+
+  public HelixGatewayService(String zkAddress) {
+    _participantsMap = new ConcurrentHashMap<>();
+    _zkAddress = zkAddress;
+    _clusterManager = new ClusterManager();
+  }
+
+  public ClusterManager getClusterManager() {
+    return _clusterManager;
+  }
+
+  public void start() {
+    System.out.println("Starting Helix Gateway Service");
+  }
+
+  public void registerParticipant(MockApplication mockApplication) {
+    HelixManager manager = 
_participantsMap.computeIfAbsent(mockApplication.getClusterName(),
+        k -> new 
ConcurrentHashMap<>()).computeIfAbsent(mockApplication.getInstanceName(),
+        k -> 
HelixManagerFactory.getZKHelixManager(mockApplication.getClusterName(),
+            mockApplication.getInstanceName(), InstanceType.PARTICIPANT, 
_zkAddress));
+    manager.getStateMachineEngine().registerStateModelFactory("OnlineOffline",
+        new HelixGatewayOnlineOfflineStateModelFactory(_clusterManager));
+    try {
+      _clusterManager.addChannel(mockApplication);
+      manager.connect();
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public void deregisterParticipant(String clusterName, String 
participantName) {
+    HelixManager manager = 
_participantsMap.get(clusterName).remove(participantName);
+    if (manager != null) {
+      manager.disconnect();
+      _clusterManager.removeChannel(participantName);
+    }
+  }
+}
diff --git 
a/helix-gateway/src/main/protobuf/org.apache.helix.gateway/HelixGatewayService.proto
 
b/helix-gateway/src/main/protobuf/org.apache.helix.gateway/HelixGatewayService.proto
new file mode 100644
index 000000000..42347f038
--- /dev/null
+++ 
b/helix-gateway/src/main/protobuf/org.apache.helix.gateway/HelixGatewayService.proto
@@ -0,0 +1,35 @@
+syntax = "proto3";
+
+package proto.org.apache.helix.gateway;
+
+message SingleTransitionRequest {
+  enum TransitionType {
+    ADD_SHARD = 0;
+    DELETE_SHARD = 1;
+    CHANGE_ROLE = 2;
+  }
+  string transitionID = 1;    // ID of transition message
+  TransitionType transitionType = 2;   // Transition type for shard operations
+  string resourceID = 3;              // Resource ID
+  string shardID = 4;                 // Shard to perform operation
+  optional string startState = 5;      // Shard start state, it is not 
mandatory. Application can decide how to get target state.
+  optional string targetState = 6;     // Shard target state.
+}
+
+message TransitionRequests {
+  repeated SingleTransitionRequest request = 1;
+}
+
+message SingleTransitionResponse {
+  string transitionID = 1;       // ID of transition message
+  bool isSuccess = 2;           // Was transition successfully performed
+  optional string currentState = 3;   // If it failed, what is the current 
state it should reported as.
+}
+
+message TransitionResponse {
+  repeated SingleTransitionResponse response = 1;
+}
+
+service GatewayService {
+  rpc transition(TransitionRequests) returns (TransitionResponse) {}
+}
diff --git a/helix-gateway/src/test/conf/testng.xml 
b/helix-gateway/src/test/conf/testng.xml
new file mode 100644
index 000000000..19446d49b
--- /dev/null
+++ b/helix-gateway/src/test/conf/testng.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd";>
+<suite name="Suite" parallel="false">
+  <test name="Test" preserve-order="true">
+    <packages>
+      <package name="org.apache.helix.helix.gateway.*"/>
+    </packages>
+  </test>
+</suite>
diff --git a/helix-gateway/src/test/resources/log4j2.properties 
b/helix-gateway/src/test/resources/log4j2.properties
new file mode 100644
index 000000000..5aa8df88a
--- /dev/null
+++ b/helix-gateway/src/test/resources/log4j2.properties
@@ -0,0 +1,63 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# See https://logging.apache.org/log4j/2.0/manual/configuration.html#Properties
+rootLogger.level = error
+rootLogger.appenderRef.stdout.ref = STDOUT
+
+appender.console.name = STDOUT
+appender.console.type = Console
+appender.console.layout.type = PatternLayout
+appender.console.layout.pattern = %-4r [%t] %-5p %c %x - %m%n
+
+appender.rolling.name = R
+appender.rolling.type = RollingFile
+appender.rolling.layout.type = PatternLayout
+appender.rolling.layout.pattern = %5p [%C:%M] (%F:%L) - %m%n
+appender.rolling.fileName = target/ClusterManagerLogs/log.txt
+appender.rolling.filePattern = 
target/ClusterManagerLogs/log.%d{dd-MMM-hh}.txt.gz
+appender.rolling.policies.type = Policies
+appender.rolling.policies.startup.type = OnStartupTriggeringPolicy
+
+# Via https://logging.apache.org/log4j/2.x/manual/migration.html
+# 'The Log4j 1.x SimpleLayout can be emulated with PatternLayout "%level - 
%m%n"'
+appender.statusdump.name = STATUSDUMP
+appender.statusdump.type = RollingFile
+appender.statusdump.layout.type = PatternLayout
+appender.statusdump.layout.pattern = "%level - %m%n"
+appender.statusdump.fileName = target/ClusterManagerLogs/statusUpdates.log
+appender.statusdump.filePattern = 
target/ClusterManagerLogs/statusUpdates.%d{dd-MMM-hh}.log.gz
+appender.statusdump.policies.type = Policies
+appender.statusdump.policies.startup.type = OnStartupTriggeringPolicy
+
+logger.i0itec.name = org.I0Itec
+logger.i0itec.level = error
+
+logger.apache.name = org.apache
+logger.apache.level = error
+
+logger.noelios.name = com.noelios
+logger.noelios.level = error
+
+logger.restlet.name = org.restlet
+logger.restlet.level = error
+
+logger.helixzkdatadump.name = org.apache.helix.monitoring.ZKPathDataDumpTask
+logger.helixzkdatadump.level = error
+logger.helixzkdatadump.appenderRef.statusdump.ref = STATUSDUMP
diff --git a/pom.xml b/pom.xml
index fd69ab711..d1aaae3aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -318,6 +318,7 @@
     <module>recipes</module>
     <module>helix-view-aggregator</module>
     <module>meta-client</module>
+    <module>helix-gateway</module>
   </modules>
 
   <mailingLists>

Reply via email to