Merge pull request #190 from algairim/nginx-multi

Introduce nginx-multi type
diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/proxy/nginx/NginxController.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/proxy/nginx/NginxController.java
index 63cd317..d2ddd98 100644
--- a/software/webapp/src/main/java/org/apache/brooklyn/entity/proxy/nginx/NginxController.java
+++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/proxy/nginx/NginxController.java
@@ -18,10 +18,7 @@
  */
 package org.apache.brooklyn.entity.proxy.nginx;
 
-import java.util.Map;
-
 import com.google.common.collect.ImmutableMap;
-
 import org.apache.brooklyn.api.catalog.Catalog;
 import org.apache.brooklyn.api.entity.ImplementedBy;
 import org.apache.brooklyn.api.objs.HasShortName;
@@ -38,6 +35,8 @@
 import org.apache.brooklyn.entity.software.base.SoftwareProcess;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 
+import java.util.Map;
+
 /**
  * An entity that represents an Nginx proxy (e.g. for routing requests to servers in a cluster).
  * <p>
@@ -56,7 +55,7 @@
  * or different ports if that is supported.
  * see more info on Ssl in {@link ProxySslConfig}.
  */
-@Catalog(name="Nginx Server", description="A single Nginx server. Provides HTTP and reverse proxy services", iconUrl="classpath:///nginx-logo.jpeg")
+@Catalog(name="Nginx Server", description="A single Nginx server. Provides HTTP and reverse proxy services", iconUrl="classpath:///nginx-logo.png")
 @ImplementedBy(NginxControllerImpl.class)
 public interface NginxController extends AbstractController, HasShortName {
 
diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nginx/UpstreamSyncPolicy.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nginx/UpstreamSyncPolicy.java
new file mode 100644
index 0000000..fc978f3
--- /dev/null
+++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nginx/UpstreamSyncPolicy.java
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+package org.apache.brooklyn.entity.webapp.nginx;
+
+import com.google.common.base.Predicates;
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.entity.group.AbstractMembershipTrackingPolicy;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.text.Strings;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Keeps NGINX 'upstream' server addresses in sync with membership and service-up changes on a corresponding service group.
+ */
+public class UpstreamSyncPolicy extends AbstractMembershipTrackingPolicy {
+
+    public static ConfigKey<Entity> NGINX_NODE = ConfigKeys.newConfigKey(Entity.class, "nginxNode");
+    public static ConfigKey<String> GROUP_NAME = ConfigKeys.newStringConfigKey("groupName");
+
+    @Override
+    protected void onEntityEvent(EventType type, Entity entity) {
+        defaultHighlightAction(type, entity);
+
+        Entity nginx = config().get(NGINX_NODE);
+        Boolean nginxIsUp = nginx.sensors().get(Startable.SERVICE_UP);
+        if (!Boolean.TRUE.equals(nginxIsUp))
+            return;
+
+        String groupName = config().get(GROUP_NAME);
+
+        List<String> serverAddresses = getEntity().getChildren().stream().map((e) -> {
+            Boolean serviceUp = e.sensors().get(Startable.SERVICE_UP);
+            String hostName = e.sensors().get(Sensors.newStringSensor("host.name"));
+            String port = e.sensors().get(Sensors.newStringSensor("http.port"));
+            if (!Boolean.TRUE.equals(serviceUp) || hostName == null || port == null)
+                return null;
+            return hostName + ":" + port;
+        }).filter(Predicates.notNull()).collect(Collectors.toList());
+
+        Maybe<Effector<?>> renderTargets = nginx.getEntityType().getEffectorByName("render-targets");
+        Entities.invokeEffectorWithArgs(getEntity(), nginx, renderTargets.get(), groupName, Strings.join(serverAddresses, " "));
+    }
+}
diff --git a/software/webapp/src/main/resources/catalog.bom b/software/webapp/src/main/resources/catalog.bom
index bd086da..2c91da5 100644
--- a/software/webapp/src/main/resources/catalog.bom
+++ b/software/webapp/src/main/resources/catalog.bom
@@ -40,7 +40,7 @@
         type: org.apache.brooklyn.entity.webapp.DynamicWebAppFabric
         name: Dynamic Web App Fabric
     - id: org.apache.brooklyn.entity.proxy.nginx.NginxController
-      iconUrl: classpath:///nginx-logo.jpeg
+      iconUrl: classpath:///nginx-logo.png
       item:
         type: org.apache.brooklyn.entity.proxy.nginx.NginxController
         name: Nginx Server
@@ -96,3 +96,5 @@
         type: org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster
         name: Controlled Dynamic Web-app Cluster
         description: A cluster of load-balanced web-apps, which can be dynamically re-sized
+
+    - yaml/nginx-multi.bom
diff --git a/software/webapp/src/main/resources/nginx-logo.jpeg b/software/webapp/src/main/resources/nginx-logo.jpeg
deleted file mode 100644
index 07153a3..0000000
--- a/software/webapp/src/main/resources/nginx-logo.jpeg
+++ /dev/null
Binary files differ
diff --git a/software/webapp/src/main/resources/nginx-logo.png b/software/webapp/src/main/resources/nginx-logo.png
new file mode 100644
index 0000000..b3faf63
--- /dev/null
+++ b/software/webapp/src/main/resources/nginx-logo.png
Binary files differ
diff --git a/software/webapp/src/main/resources/scripts/nginx-render-server.sh b/software/webapp/src/main/resources/scripts/nginx-render-server.sh
new file mode 100644
index 0000000..4037b2e
--- /dev/null
+++ b/software/webapp/src/main/resources/scripts/nginx-render-server.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+# 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.
+
+# TODO: verify required environment variables are set
+# TODO: emit comment block
+
+echo "server {"
+
+[[ -n "$hostName" ]] && echo "  server_name ${hostName};"
+
+echo "  location / { proxy_pass http://targets-${groupName}; }"
+echo "}"
diff --git a/software/webapp/src/main/resources/scripts/nginx-render-upstream.sh b/software/webapp/src/main/resources/scripts/nginx-render-upstream.sh
new file mode 100644
index 0000000..2466437
--- /dev/null
+++ b/software/webapp/src/main/resources/scripts/nginx-render-upstream.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+# 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.
+
+# TODO: verify required environment variables are set
+# TODO: emit comment block
+
+echo "upstream targets-${groupName} {"
+
+# can't have an empty 'upstream' block, so add a placeholder 'server' entry if needed
+[[ -z "${serverAddresses}" ]] && echo "  server 0.0.0.0;"
+
+for s in ${serverAddresses}; do
+  echo "  server $s;"
+done
+
+echo "}"
diff --git a/software/webapp/src/main/resources/yaml/nginx-multi.bom b/software/webapp/src/main/resources/yaml/nginx-multi.bom
new file mode 100644
index 0000000..4c99502
--- /dev/null
+++ b/software/webapp/src/main/resources/yaml/nginx-multi.bom
@@ -0,0 +1,167 @@
+# 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.
+
+brooklyn.catalog:
+  items:
+    - id:              nginx-multi
+      name:            Multi-cluster NGINX
+      iconUrl:         classpath:///nginx-logo.png
+      description: |
+        Create a RHEL or CentOS load balancer which can be pointed at groups of servers and routed via multiple host names.
+        Use <code>render-targets</code> to configure a unique group of servers and <code>render-routing</code> to configure a
+        unique route for the group. Use <code>delete-targets</code> and <code>delete-routing</code> to delete previously
+        configure group or route, respectively.
+      item:
+        name:          NGINX (multi)
+        type:          org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess
+
+        brooklyn.parameters:
+
+          - name:      install.command
+            pinned:    false
+          - name:      customize.command 
+            pinned:    false
+          - name:      launch.command
+            pinned:    false
+          - name:      checkRunning.command
+            pinned:    false
+          - name:      stop.command
+            pinned:    false
+
+        #------------------------------------------------------------------------------------------
+
+        brooklyn.config:
+
+          http.port: 80
+
+          dontRequireTtyForSudo: true
+          sshMonitoring.enabled: false
+
+          shell.env:
+            NGINX_PID:           /var/run/nginx.pid
+            CONF_DIR:            /etc/nginx/conf.d
+            SCRIPT_DIR:          $brooklyn:attributeWhenReady("install.dir")
+
+          files.install:
+            classpath://scripts/nginx-render-server.sh:    nginx-render-server.sh
+            classpath://scripts/nginx-render-upstream.sh:  nginx-render-upstream.sh
+
+          install.command: |
+            sudo yum install -y firewalld
+            sudo systemctl enable firewalld
+            sudo systemctl start firewalld
+            chmod +x ${SCRIPT_DIR}/*.sh
+            sudo yum install -y nginx
+            sudo firewall-cmd --permanent --zone=public --add-service=http
+            sudo firewall-cmd --reload
+
+          launch.command: |
+            sudo nginx
+
+          stop.command: |
+            # let worker processes complete processing of in-flight requests
+            sudo nginx -s quit
+
+          checkRunning.command: |
+            PID=`cat "${NGINX_PID}"`
+            sudo kill -0 $PID
+
+        #------------------------------------------------------------------------------------------
+
+        brooklyn.initializers:
+
+          - type: org.apache.brooklyn.core.effector.ssh.SshCommandEffector
+            brooklyn.config:
+              name:                reload
+              description:         Reload config files
+              command: |
+                sudo nginx -s reload
+
+          - type: org.apache.brooklyn.core.effector.ssh.SshCommandEffector
+            brooklyn.config:
+              name:                render-targets
+              description:         Create config file for 'upstream' block
+              parameters:
+                groupName:
+                    description: Name of this group
+                serverAddresses:
+                  description: Collection of servers separated by space, e.g. '1.2.3.4:8080 2.3.4.5'
+              command: |
+                confFile="${CONF_DIR}/targets-${groupName}.conf"
+                [[ -f "$confFile" ]] && echo "Updating ${confFile}:" || echo "Creating ${confFile}:"
+                ${SCRIPT_DIR}/nginx-render-upstream.sh | sudo tee ${confFile}
+                echo "Reloading config"
+                sudo nginx -s reload
+                echo "Done"
+
+          - type: org.apache.brooklyn.core.effector.ssh.SshCommandEffector
+            brooklyn.config:
+              name:                render-routing
+              description:         Create config file for 'server' block
+              parameters:
+                logicalName:
+                  description: Name of this routing configuration
+                hostName: {}
+                groupName:
+                  description: Name of group to render routing for
+              command: |
+                confFile="${CONF_DIR}/routing-${logicalName}.conf"
+                [[ -z "$logicalName" ]] && confFile="${CONF_DIR}/default.conf"
+                [[ -f "$confFile" ]] && echo "Updating ${confFile}:" || echo "Creating ${confFile}:"
+                ${SCRIPT_DIR}/nginx-render-server.sh | sudo tee ${confFile}
+                echo "Reloading config"
+                sudo nginx -s reload
+                echo "Done"
+
+          - type: org.apache.brooklyn.core.effector.ssh.SshCommandEffector
+            brooklyn.config:
+              name:                delete-routing
+              description:         Remove an existing 'server' block
+              parameters:
+                logicalName:
+                  description: Name of the routing configuration to remove
+
+              command: |
+                confFile="${CONF_DIR}/routing-${logicalName}.conf"
+                [[ -z "$logicalName" ]] && confFile="${CONF_DIR}/default.conf"
+                echo "Deleting ${confFile}"
+                sudo rm -f ${confFile}
+                echo "Reloading config"
+                sudo nginx -s reload
+                echo "Done"
+
+          - type: org.apache.brooklyn.core.effector.ssh.SshCommandEffector
+            brooklyn.config:
+              name:                delete-targets
+              description:         Remove an existing 'upstream' block
+              parameters:
+                groupName:
+                  description: Name of the group to remove
+              command: |
+                confFile="${CONF_DIR}/targets-${groupName}.conf"
+                echo "Deleting ${confFile}"
+                sudo rm -f ${confFile}
+                echo "Reloading config"
+                sudo nginx -s reload
+                echo "Done"
+
+    - id:              nginx-multi-upstream-sync
+      itemType:        policy
+      name:            Multi-cluster NGINX config synchronizer
+      iconUrl:         classpath:///nginx-logo.png
+      item:
+        type:          org.apache.brooklyn.entity.webapp.nginx.UpstreamSyncPolicy
\ No newline at end of file