diff --git a/.idea/dictionaries/bhale.xml b/.idea/dictionaries/bhale.xml index 7af11507f1..e3ff95b37b 100644 --- a/.idea/dictionaries/bhale.xml +++ b/.idea/dictionaries/bhale.xml @@ -6,7 +6,6 @@ appdynamics applicationid argv - aspectsecurity atpack bootclasspath buildpack @@ -100,7 +99,6 @@ stubjre submodules tasklib - teamserver tenanttoken tmpdir tokenized diff --git a/.idea/runConfigurations/versions__JSON_.xml b/.idea/runConfigurations/versions__JSON_.xml new file mode 100644 index 0000000000..1d3402880a --- /dev/null +++ b/.idea/runConfigurations/versions__JSON_.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/tomcat.yml b/config/tomcat.yml index e1ff5a78a9..fa12afd1fb 100644 --- a/config/tomcat.yml +++ b/config/tomcat.yml @@ -39,3 +39,6 @@ redis_store: database: 0 timeout: 2000 connection_pool_size: 2 +geode_store: + version: 0.+ + repository_root: "https://tomcat-javabuildpack-spike.s3.amazonaws.com/geode-store" \ No newline at end of file diff --git a/lib/java_buildpack/container/tomcat.rb b/lib/java_buildpack/container/tomcat.rb index e81475b89a..8138fe6864 100644 --- a/lib/java_buildpack/container/tomcat.rb +++ b/lib/java_buildpack/container/tomcat.rb @@ -15,12 +15,13 @@ require 'java_buildpack/component/modular_component' require 'java_buildpack/container' +require 'java_buildpack/container/tomcat/tomcat_access_logging_support' +require 'java_buildpack/container/tomcat/tomcat_external_configuration' +require 'java_buildpack/container/tomcat/tomcat_geode_store' require 'java_buildpack/container/tomcat/tomcat_insight_support' require 'java_buildpack/container/tomcat/tomcat_instance' -require 'java_buildpack/container/tomcat/tomcat_external_configuration' require 'java_buildpack/container/tomcat/tomcat_lifecycle_support' require 'java_buildpack/container/tomcat/tomcat_logging_support' -require 'java_buildpack/container/tomcat/tomcat_access_logging_support' require 'java_buildpack/container/tomcat/tomcat_redis_store' require 'java_buildpack/util/java_main_utils' @@ -50,9 +51,10 @@ def command def sub_components(context) components = [ TomcatInstance.new(sub_configuration_context(context, 'tomcat')), + TomcatAccessLoggingSupport.new(sub_configuration_context(context, 'access_logging_support')), + TomcatGeodeStore.new(sub_configuration_context(context, 'geode_store')), TomcatLifecycleSupport.new(sub_configuration_context(context, 'lifecycle_support')), TomcatLoggingSupport.new(sub_configuration_context(context, 'logging_support')), - TomcatAccessLoggingSupport.new(sub_configuration_context(context, 'access_logging_support')), TomcatRedisStore.new(sub_configuration_context(context, 'redis_store')), TomcatInsightSupport.new(context) ] diff --git a/lib/java_buildpack/container/tomcat/tomcat_geode_store.rb b/lib/java_buildpack/container/tomcat/tomcat_geode_store.rb new file mode 100644 index 0000000000..b42fab975f --- /dev/null +++ b/lib/java_buildpack/container/tomcat/tomcat_geode_store.rb @@ -0,0 +1,176 @@ +# Cloud Foundry Java Buildpack +# Copyright 2013-2017 the original author or authors. +# +# 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. + +require 'java_buildpack/component/versioned_dependency_component' +require 'java_buildpack/container' +require 'java_buildpack/container/tomcat/tomcat_utils' +require 'java_buildpack/logging/logger_factory' + +module JavaBuildpack + module Container + + # Encapsulates the detect, compile, and release functionality for Tomcat Redis support. + class TomcatGeodeStore < JavaBuildpack::Component::VersionedDependencyComponent + include JavaBuildpack::Container + + # (see JavaBuildpack::Component::BaseComponent#compile) + def compile + return unless supports? + download_tar(false, tomcat_lib, tar_name) + mutate_context + mutate_server + create_cache_client_xml + end + + # (see JavaBuildpack::Component::BaseComponent#release) + def release + return unless supports? + credentials = @application.services.find_service(FILTER)['credentials'] + user = credentials[KEY_USERS].find { |u| u['username'] == 'cluster_operator' } + + @droplet.java_opts.add_system_property 'gemfire.security-username', 'cluster_operator' + @droplet.java_opts.add_system_property 'gemfire.security-password', user['password'] + @droplet.java_opts.add_system_property 'gemfire.security-client-auth-init', + 'io.pivotal.cloudcache.ClientAuthInitialize.create' + end + + protected + + # (see JavaBuildpack::Component::VersionedDependencyComponent#supports?) + def supports? + @application.services.one_service? FILTER, KEY_LOCATORS, KEY_USERS + end + + private + + FILTER = /session-replication/ + KEY_LOCATORS = 'locators'.freeze + KEY_USERS = 'users'.freeze + + SESSION_MANAGER_CLASS_NAME = 'org.apache.geode.modules.session.catalina.Tomcat8DeltaSessionManager'.freeze + REGION_ATTRIBUTES_ID = 'PARTITION_REDUNDANT_HEAP_LRU'.freeze + CACHE_CLIENT_LISTENER_CLASS_NAME = + 'org.apache.geode.modules.session.catalina.ClientServerCacheLifecycleListener'.freeze + SCHEMA_URL = 'http://geode.apache.org/schema/cache'.freeze + SCHEMA_INSTANCE_URL = 'http://www.w3.org/2001/XMLSchema-instance'.freeze + SCHEMA_LOCATION = 'http://geode.apache.org/schema/cache http://geode.apache.org/schema/cache/cache-1.0.xsd'.freeze + LOCATOR_REGEXP = Regexp.new('([^\\[]+)\\[([^\\]]+)\\]').freeze + FUNCTION_SERVICE_CLASS_NAMES = [ + 'org.apache.geode.modules.util.CreateRegionFunction', + 'org.apache.geode.modules.util.TouchPartitionedRegionEntriesFunction', + 'org.apache.geode.modules.util.TouchReplicatedRegionEntriesFunction', + 'org.apache.geode.modules.util.RegionSizeFunction' + ].freeze + + private_constant :FILTER, :KEY_LOCATORS, :KEY_USERS, :SESSION_MANAGER_CLASS_NAME, :REGION_ATTRIBUTES_ID, + :CACHE_CLIENT_LISTENER_CLASS_NAME, :SCHEMA_URL, :SCHEMA_INSTANCE_URL, :SCHEMA_LOCATION, + :LOCATOR_REGEXP, :FUNCTION_SERVICE_CLASS_NAMES + + def add_client_cache(document) + client_cache = document.add_element 'client-cache', + 'xmlns' => SCHEMA_URL, + 'xmlns:xsi' => SCHEMA_INSTANCE_URL, + 'xsi:schemaLocation' => SCHEMA_LOCATION, + 'version' => '1.0' + + add_pool client_cache + add_function_service client_cache + end + + def add_functions(function_service) + FUNCTION_SERVICE_CLASS_NAMES.each do |function_class_name| + function = function_service.add_element 'function' + class_name = function.add_element 'class-name' + class_name.add_text(function_class_name) + end + end + + def add_function_service(client_cache) + function_service = client_cache.add_element 'function-service' + add_functions function_service + end + + def add_listener(server) + server.add_element 'Listener', + 'className' => CACHE_CLIENT_LISTENER_CLASS_NAME + end + + def add_locators(pool) + service = @application.services.find_service FILTER + service['credentials']['locators'].each do |locator| + match_info = LOCATOR_REGEXP.match(locator) + pool.add_element 'locator', + 'host' => match_info[1], + 'port' => match_info[2] + end + end + + def add_manager(context) + context.add_element 'Manager', + 'className' => SESSION_MANAGER_CLASS_NAME, + 'enableLocalCache' => 'true', + 'regionAttributesId' => REGION_ATTRIBUTES_ID + end + + def add_pool(client_cache) + pool = client_cache.add_element 'pool', + 'name' => 'sessions', + 'subscription-enabled' => 'true' + add_locators pool + end + + def cache_client_xml + 'cache-client.xml' + end + + def cache_client_xml_path + @droplet.sandbox + 'conf' + cache_client_xml + end + + def create_cache_client_xml + document = REXML::Document.new('') + add_client_cache document + write_xml cache_client_xml_path, document + end + + def mutate_context + puts ' Adding Geode-based Session Replication' + + document = read_xml context_xml + context = REXML::XPath.match(document, '/Context').first + + add_manager context + + write_xml context_xml, document + end + + def mutate_server + document = read_xml server_xml + + server = REXML::XPath.match(document, '/Server').first + + add_listener server + + write_xml server_xml, document + end + + def tar_name + "geode-store-#{@version}.tar.gz" + end + + end + + end +end diff --git a/spec/fixtures/container_tomcat_geode_store/.java-buildpack/tomcat/conf/context.xml b/spec/fixtures/container_tomcat_geode_store/.java-buildpack/tomcat/conf/context.xml new file mode 100644 index 0000000000..7f96549265 --- /dev/null +++ b/spec/fixtures/container_tomcat_geode_store/.java-buildpack/tomcat/conf/context.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/spec/fixtures/container_tomcat_geode_store/.java-buildpack/tomcat/conf/server.xml b/spec/fixtures/container_tomcat_geode_store/.java-buildpack/tomcat/conf/server.xml new file mode 100644 index 0000000000..925aa05504 --- /dev/null +++ b/spec/fixtures/container_tomcat_geode_store/.java-buildpack/tomcat/conf/server.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + diff --git a/spec/fixtures/container_tomcat_geode_store/WEB-INF/.gitkeep b/spec/fixtures/container_tomcat_geode_store/WEB-INF/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spec/fixtures/container_tomcat_geode_store_cache_client_after.xml b/spec/fixtures/container_tomcat_geode_store_cache_client_after.xml new file mode 100644 index 0000000000..b68c118145 --- /dev/null +++ b/spec/fixtures/container_tomcat_geode_store_cache_client_after.xml @@ -0,0 +1,21 @@ + + + + + + + + + org.apache.geode.modules.util.CreateRegionFunction + + + org.apache.geode.modules.util.TouchPartitionedRegionEntriesFunction + + + org.apache.geode.modules.util.TouchReplicatedRegionEntriesFunction + + + org.apache.geode.modules.util.RegionSizeFunction + + + diff --git a/spec/fixtures/container_tomcat_geode_store_context_after.xml b/spec/fixtures/container_tomcat_geode_store_context_after.xml new file mode 100644 index 0000000000..412053d479 --- /dev/null +++ b/spec/fixtures/container_tomcat_geode_store_context_after.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/spec/fixtures/container_tomcat_geode_store_server_after.xml b/spec/fixtures/container_tomcat_geode_store_server_after.xml new file mode 100644 index 0000000000..56b8728e04 --- /dev/null +++ b/spec/fixtures/container_tomcat_geode_store_server_after.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + diff --git a/spec/fixtures/stub-geode-store.jar b/spec/fixtures/stub-geode-store.jar new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spec/fixtures/stub-geode-store.tar b/spec/fixtures/stub-geode-store.tar new file mode 100644 index 0000000000..816deb94d1 Binary files /dev/null and b/spec/fixtures/stub-geode-store.tar differ diff --git a/spec/java_buildpack/container/tomcat/tomcat_geode_store_spec.rb b/spec/java_buildpack/container/tomcat/tomcat_geode_store_spec.rb new file mode 100644 index 0000000000..26dab5a30d --- /dev/null +++ b/spec/java_buildpack/container/tomcat/tomcat_geode_store_spec.rb @@ -0,0 +1,110 @@ +# Cloud Foundry Java Buildpack +# Copyright 2013-2017 the original author or authors. +# +# 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. + +require 'spec_helper' +require 'component_helper' +require 'java_buildpack/container/tomcat/tomcat_geode_store' + +describe JavaBuildpack::Container::TomcatGeodeStore do + include_context 'component_helper' + + let(:component_id) { 'tomcat' } + + let(:configuration) do + { 'database' => 'test-database', + 'timeout' => 'test-timeout', + 'connection_pool_size' => 'test-connection-pool-size' } + end + + it 'does not detect without a session-replication service' do + expect(component.detect).to be_nil + end + + context 'when there is a session-replication service' do + before do + allow(services).to receive(:one_service?).with(/session-replication/, 'locators', 'users') + .and_return(true) + allow(services).to receive(:find_service).and_return( + 'credentials' => { + 'locators' => ['some-locator[some-port]', 'some-other-locator[some-other-port]'], + 'users' => + [ + { 'password' => 'fake-password', + 'username' => 'cluster_operator' } + ] + } + ) + + end + + it 'detect with a session-replication service' do + expect(component.detect).to eq("tomcat-geode-store=#{version}") + end + + it 'copies resources', + app_fixture: 'container_tomcat_geode_store', + cache_fixture: 'stub-geode-store.tar' do + + component.compile + + expect(sandbox + 'lib/stub-geode-store/stub-jar-1.jar').to exist + expect(sandbox + 'lib/stub-geode-store/stub-jar-2.jar').to exist + end + + it 'mutates context.xml', + app_fixture: 'container_tomcat_geode_store', + cache_fixture: 'stub-geode-store.tar' do + + component.compile + + expect((sandbox + 'conf/context.xml').read) + .to eq(Pathname.new('spec/fixtures/container_tomcat_geode_store_context_after.xml').read) + end + + it 'mutates server.xml', + app_fixture: 'container_tomcat_geode_store', + cache_fixture: 'stub-geode-store.tar' do + + component.compile + + expect((sandbox + 'conf/server.xml').read) + .to eq(Pathname.new('spec/fixtures/container_tomcat_geode_store_server_after.xml').read) + end + + it 'adds a cache-client.xml', + app_fixture: 'container_tomcat_geode_store', + cache_fixture: 'stub-geode-store.tar' do + + component.compile + + expect((sandbox + 'conf/cache-client.xml').read) + .to eq(Pathname.new('spec/fixtures/container_tomcat_geode_store_cache_client_after.xml').read) + end + + it 'passes security properties to the release', + app_fixture: 'container_tomcat_geode_store', + cache_fixture: 'stub-geode-store.tar' do + + component.release + + expect(java_opts).to include( + '-Dgemfire.security-client-auth-init=io.pivotal.cloudcache.ClientAuthInitialize.create' + ) + expect(java_opts).to include('-Dgemfire.security-username=cluster_operator') + expect(java_opts).to include('-Dgemfire.security-password=fake-password') + end + + end +end diff --git a/spec/java_buildpack/container/tomcat_spec.rb b/spec/java_buildpack/container/tomcat_spec.rb index ee65087d6c..281fab0b86 100644 --- a/spec/java_buildpack/container/tomcat_spec.rb +++ b/spec/java_buildpack/container/tomcat_spec.rb @@ -17,11 +17,12 @@ require 'component_helper' require 'fileutils' require 'java_buildpack/container/tomcat' +require 'java_buildpack/container/tomcat/tomcat_access_logging_support' +require 'java_buildpack/container/tomcat/tomcat_geode_store' require 'java_buildpack/container/tomcat/tomcat_insight_support' require 'java_buildpack/container/tomcat/tomcat_instance' require 'java_buildpack/container/tomcat/tomcat_lifecycle_support' require 'java_buildpack/container/tomcat/tomcat_logging_support' -require 'java_buildpack/container/tomcat/tomcat_access_logging_support' require 'java_buildpack/container/tomcat/tomcat_redis_store' describe JavaBuildpack::Container::Tomcat do @@ -30,24 +31,27 @@ let(:component) { StubTomcat.new context } let(:configuration) do - { 'tomcat' => tomcat_configuration, + { 'access_logging_support' => access_logging_support_configuration, + 'external_configuration' => tomcat_external_configuration, + 'geode_store' => geode_store_configuration, 'lifecycle_support' => lifecycle_support_configuration, 'logging_support' => logging_support_configuration, - 'access_logging_support' => access_logging_support_configuration, 'redis_store' => redis_store_configuration, - 'external_configuration' => tomcat_external_configuration } + 'tomcat' => tomcat_configuration } end - let(:tomcat_configuration) { { 'external_configuration_enabled' => false } } + let(:access_logging_support_configuration) { instance_double('logging-support-configuration') } let(:lifecycle_support_configuration) { instance_double('lifecycle-support-configuration') } let(:logging_support_configuration) { instance_double('logging-support-configuration') } - let(:access_logging_support_configuration) { instance_double('logging-support-configuration') } + let(:geode_store_configuration) { instance_double('geode_store_configuration') } let(:redis_store_configuration) { instance_double('redis-store-configuration') } + let(:tomcat_configuration) { { 'external_configuration_enabled' => false } } + let(:tomcat_external_configuration) { instance_double('tomcat_external_configuration') } it 'detects WEB-INF', @@ -69,17 +73,19 @@ end it 'creates submodules' do + allow(JavaBuildpack::Container::TomcatAccessLoggingSupport) + .to receive(:new).with(sub_configuration_context(access_logging_support_configuration)) + allow(JavaBuildpack::Container::TomcatGeodeStore) + .to receive(:new).with(sub_configuration_context(geode_store_configuration)) allow(JavaBuildpack::Container::TomcatInstance) .to receive(:new).with(sub_configuration_context(tomcat_configuration)) + allow(JavaBuildpack::Container::TomcatInsightSupport).to receive(:new).with(context) allow(JavaBuildpack::Container::TomcatLifecycleSupport) .to receive(:new).with(sub_configuration_context(lifecycle_support_configuration)) allow(JavaBuildpack::Container::TomcatLoggingSupport) .to receive(:new).with(sub_configuration_context(logging_support_configuration)) - allow(JavaBuildpack::Container::TomcatAccessLoggingSupport) - .to receive(:new).with(sub_configuration_context(access_logging_support_configuration)) allow(JavaBuildpack::Container::TomcatRedisStore) .to receive(:new).with(sub_configuration_context(redis_store_configuration)) - allow(JavaBuildpack::Container::TomcatInsightSupport).to receive(:new).with(context) component.sub_components context end