Implement initial Jmeter master/slave containers 97/60297/1
authorEddie Arrage <eddie.arrage@huawei.com>
Sat, 28 Jul 2018 01:02:35 +0000 (01:02 +0000)
committerEddie Arrage <eddie.arrage@huawei.com>
Sat, 28 Jul 2018 01:16:26 +0000 (01:16 +0000)
- Jmeter can be used for L4-7 functional and performance testing
- Jmeter master has gRPC server for management
- Generates Jmeter test plans from minimal yaml params file
(sample to be added with cloverctl) using template
- Optionally span tests across slave containers to allow greater
loads to be generated
- Specify loop/thread/slave count and URL list, which
dictates target and number of connections that will be attempted
- clover-controller will interface to gRPC interface on Jmeter
master
- Start tests on master and retrieve log/result files
- Render master and slave k8s manifests files

Change-Id: Id144c8f551b7d375ff252c8de0611f895b50387c
Signed-off-by: Eddie Arrage <eddie.arrage@huawei.com>
15 files changed:
clover/tools/jmeter/build_master.sh [new file with mode: 0755]
clover/tools/jmeter/build_slave.sh [new file with mode: 0755]
clover/tools/jmeter/jmeter-master/Dockerfile [new file with mode: 0644]
clover/tools/jmeter/jmeter-master/grpc/build_proto.sh [new file with mode: 0755]
clover/tools/jmeter/jmeter-master/grpc/jmeter.proto [new file with mode: 0644]
clover/tools/jmeter/jmeter-master/grpc/jmeter_pb2.py [new file with mode: 0644]
clover/tools/jmeter/jmeter-master/grpc/jmeter_pb2_grpc.py [new file with mode: 0644]
clover/tools/jmeter/jmeter-master/grpc/jmeter_server.py [new file with mode: 0644]
clover/tools/jmeter/jmeter-master/process/grpc_process.sh [new file with mode: 0755]
clover/tools/jmeter/jmeter-master/tests/jmx.template [new file with mode: 0644]
clover/tools/jmeter/jmeter-slave/Dockerfile [new file with mode: 0644]
clover/tools/jmeter/rmi_keystore.jks [new file with mode: 0644]
clover/tools/jmeter/yaml/manifest.template [new file with mode: 0644]
clover/tools/jmeter/yaml/render_master.py [new file with mode: 0644]
clover/tools/jmeter/yaml/render_slave.py [new file with mode: 0644]

diff --git a/clover/tools/jmeter/build_master.sh b/clover/tools/jmeter/build_master.sh
new file mode 100755 (executable)
index 0000000..5c5459a
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+IMAGE_PATH=${IMAGE_PATH:-"localhost:5000"}
+IMAGE_NAME=${IMAGE_NAME:-"clover-jmeter-master"}
+
+docker build -f jmeter-master/Dockerfile -t $IMAGE_NAME .
+docker tag $IMAGE_NAME $IMAGE_PATH/$IMAGE_NAME
+docker push $IMAGE_PATH/$IMAGE_NAME
diff --git a/clover/tools/jmeter/build_slave.sh b/clover/tools/jmeter/build_slave.sh
new file mode 100755 (executable)
index 0000000..1651c55
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+IMAGE_PATH=${IMAGE_PATH:-"localhost:5000"}
+IMAGE_NAME=${IMAGE_NAME:-"clover-jmeter-slave"}
+
+docker build -f jmeter-slave/Dockerfile -t $IMAGE_NAME .
+docker tag $IMAGE_NAME $IMAGE_PATH/$IMAGE_NAME
+docker push $IMAGE_PATH/$IMAGE_NAME
diff --git a/clover/tools/jmeter/jmeter-master/Dockerfile b/clover/tools/jmeter/jmeter-master/Dockerfile
new file mode 100644 (file)
index 0000000..da0e474
--- /dev/null
@@ -0,0 +1,29 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+FROM java:8
+
+RUN wget http://apache.mirrors.hoobly.com//jmeter/binaries/apache-jmeter-4.0.tgz
+RUN tar -xvzf apache-jmeter-4.0.tgz
+RUN rm apache-jmeter-4.0.tgz
+RUN mv apache-jmeter-4.0 /jmeter
+ENV JMETER_HOME /jmeter
+ENV PATH $JMETER_HOME/bin:$PATH
+COPY rmi_keystore.jks $JMETER_HOME/bin
+
+RUN apt update && apt install -y python-setuptools python-dev python-pip
+RUN python -m pip install --upgrade pip
+RUN python -m pip install enum34 futures cython
+RUN python -m pip install grpcio protobuf jinja2
+
+WORKDIR /jmeter/bin
+
+COPY jmeter-master/process process
+COPY jmeter-master/grpc grpc
+COPY jmeter-master/tests tests
+
+CMD ./process/grpc_process.sh no_init
diff --git a/clover/tools/jmeter/jmeter-master/grpc/build_proto.sh b/clover/tools/jmeter/jmeter-master/grpc/build_proto.sh
new file mode 100755 (executable)
index 0000000..52dfd0a
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+python -m grpc_tools.protoc -I./ --python_out=. --grpc_python_out=. jmeter.proto
diff --git a/clover/tools/jmeter/jmeter-master/grpc/jmeter.proto b/clover/tools/jmeter/jmeter-master/grpc/jmeter.proto
new file mode 100644 (file)
index 0000000..7213faa
--- /dev/null
@@ -0,0 +1,43 @@
+// Copyright (c) Authors of Clover
+//
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Apache License, Version 2.0
+// which accompanies this distribution, and is available at
+// http://www.apache.org/licenses/LICENSE-2.0
+
+syntax = "proto3";
+
+package jmeter;
+
+// The controller service definition.
+service Controller {
+
+  rpc GenTest (ConfigJmeter) returns (JmeterReply) {}
+  rpc StartTest (TestParams) returns (JmeterReply) {}
+  rpc GetResults (JResults) returns (JmeterReply) {}
+}
+
+message TestParams {
+  string num_slaves = 1;
+  string test_plan = 2;
+  string slave_ips = 3;
+}
+
+message ConfigJmeter {
+  string url_list = 1;
+  string num_threads = 2;
+  string url_names = 3;
+  string url_protocols = 4;
+  string url_methods = 5;
+  string loops = 6;
+  string ramp_time = 7;
+}
+
+message JmeterReply {
+  string message = 1;
+}
+
+message JResults {
+  string r_format = 1;
+  string r_file = 2;
+}
diff --git a/clover/tools/jmeter/jmeter-master/grpc/jmeter_pb2.py b/clover/tools/jmeter/jmeter-master/grpc/jmeter_pb2.py
new file mode 100644 (file)
index 0000000..e4a75fd
--- /dev/null
@@ -0,0 +1,291 @@
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: jmeter.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='jmeter.proto',
+  package='jmeter',
+  syntax='proto3',
+  serialized_pb=_b('\n\x0cjmeter.proto\x12\x06jmeter\"F\n\nTestParams\x12\x12\n\nnum_slaves\x18\x01 \x01(\t\x12\x11\n\ttest_plan\x18\x02 \x01(\t\x12\x11\n\tslave_ips\x18\x03 \x01(\t\"\x96\x01\n\x0c\x43onfigJmeter\x12\x10\n\x08url_list\x18\x01 \x01(\t\x12\x13\n\x0bnum_threads\x18\x02 \x01(\t\x12\x11\n\turl_names\x18\x03 \x01(\t\x12\x15\n\rurl_protocols\x18\x04 \x01(\t\x12\x13\n\x0burl_methods\x18\x05 \x01(\t\x12\r\n\x05loops\x18\x06 \x01(\t\x12\x11\n\tramp_time\x18\x07 \x01(\t\"\x1e\n\x0bJmeterReply\x12\x0f\n\x07message\x18\x01 \x01(\t\",\n\x08JResults\x12\x10\n\x08r_format\x18\x01 \x01(\t\x12\x0e\n\x06r_file\x18\x02 \x01(\t2\xb3\x01\n\nController\x12\x36\n\x07GenTest\x12\x14.jmeter.ConfigJmeter\x1a\x13.jmeter.JmeterReply\"\x00\x12\x36\n\tStartTest\x12\x12.jmeter.TestParams\x1a\x13.jmeter.JmeterReply\"\x00\x12\x35\n\nGetResults\x12\x10.jmeter.JResults\x1a\x13.jmeter.JmeterReply\"\x00\x62\x06proto3')
+)
+
+
+
+
+_TESTPARAMS = _descriptor.Descriptor(
+  name='TestParams',
+  full_name='jmeter.TestParams',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='num_slaves', full_name='jmeter.TestParams.num_slaves', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='test_plan', full_name='jmeter.TestParams.test_plan', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='slave_ips', full_name='jmeter.TestParams.slave_ips', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=24,
+  serialized_end=94,
+)
+
+
+_CONFIGJMETER = _descriptor.Descriptor(
+  name='ConfigJmeter',
+  full_name='jmeter.ConfigJmeter',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='url_list', full_name='jmeter.ConfigJmeter.url_list', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='num_threads', full_name='jmeter.ConfigJmeter.num_threads', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='url_names', full_name='jmeter.ConfigJmeter.url_names', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='url_protocols', full_name='jmeter.ConfigJmeter.url_protocols', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='url_methods', full_name='jmeter.ConfigJmeter.url_methods', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='loops', full_name='jmeter.ConfigJmeter.loops', index=5,
+      number=6, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ramp_time', full_name='jmeter.ConfigJmeter.ramp_time', index=6,
+      number=7, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=97,
+  serialized_end=247,
+)
+
+
+_JMETERREPLY = _descriptor.Descriptor(
+  name='JmeterReply',
+  full_name='jmeter.JmeterReply',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='message', full_name='jmeter.JmeterReply.message', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=249,
+  serialized_end=279,
+)
+
+
+_JRESULTS = _descriptor.Descriptor(
+  name='JResults',
+  full_name='jmeter.JResults',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='r_format', full_name='jmeter.JResults.r_format', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='r_file', full_name='jmeter.JResults.r_file', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=281,
+  serialized_end=325,
+)
+
+DESCRIPTOR.message_types_by_name['TestParams'] = _TESTPARAMS
+DESCRIPTOR.message_types_by_name['ConfigJmeter'] = _CONFIGJMETER
+DESCRIPTOR.message_types_by_name['JmeterReply'] = _JMETERREPLY
+DESCRIPTOR.message_types_by_name['JResults'] = _JRESULTS
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+TestParams = _reflection.GeneratedProtocolMessageType('TestParams', (_message.Message,), dict(
+  DESCRIPTOR = _TESTPARAMS,
+  __module__ = 'jmeter_pb2'
+  # @@protoc_insertion_point(class_scope:jmeter.TestParams)
+  ))
+_sym_db.RegisterMessage(TestParams)
+
+ConfigJmeter = _reflection.GeneratedProtocolMessageType('ConfigJmeter', (_message.Message,), dict(
+  DESCRIPTOR = _CONFIGJMETER,
+  __module__ = 'jmeter_pb2'
+  # @@protoc_insertion_point(class_scope:jmeter.ConfigJmeter)
+  ))
+_sym_db.RegisterMessage(ConfigJmeter)
+
+JmeterReply = _reflection.GeneratedProtocolMessageType('JmeterReply', (_message.Message,), dict(
+  DESCRIPTOR = _JMETERREPLY,
+  __module__ = 'jmeter_pb2'
+  # @@protoc_insertion_point(class_scope:jmeter.JmeterReply)
+  ))
+_sym_db.RegisterMessage(JmeterReply)
+
+JResults = _reflection.GeneratedProtocolMessageType('JResults', (_message.Message,), dict(
+  DESCRIPTOR = _JRESULTS,
+  __module__ = 'jmeter_pb2'
+  # @@protoc_insertion_point(class_scope:jmeter.JResults)
+  ))
+_sym_db.RegisterMessage(JResults)
+
+
+
+_CONTROLLER = _descriptor.ServiceDescriptor(
+  name='Controller',
+  full_name='jmeter.Controller',
+  file=DESCRIPTOR,
+  index=0,
+  options=None,
+  serialized_start=328,
+  serialized_end=507,
+  methods=[
+  _descriptor.MethodDescriptor(
+    name='GenTest',
+    full_name='jmeter.Controller.GenTest',
+    index=0,
+    containing_service=None,
+    input_type=_CONFIGJMETER,
+    output_type=_JMETERREPLY,
+    options=None,
+  ),
+  _descriptor.MethodDescriptor(
+    name='StartTest',
+    full_name='jmeter.Controller.StartTest',
+    index=1,
+    containing_service=None,
+    input_type=_TESTPARAMS,
+    output_type=_JMETERREPLY,
+    options=None,
+  ),
+  _descriptor.MethodDescriptor(
+    name='GetResults',
+    full_name='jmeter.Controller.GetResults',
+    index=2,
+    containing_service=None,
+    input_type=_JRESULTS,
+    output_type=_JMETERREPLY,
+    options=None,
+  ),
+])
+_sym_db.RegisterServiceDescriptor(_CONTROLLER)
+
+DESCRIPTOR.services_by_name['Controller'] = _CONTROLLER
+
+# @@protoc_insertion_point(module_scope)
diff --git a/clover/tools/jmeter/jmeter-master/grpc/jmeter_pb2_grpc.py b/clover/tools/jmeter/jmeter-master/grpc/jmeter_pb2_grpc.py
new file mode 100644 (file)
index 0000000..4df110d
--- /dev/null
@@ -0,0 +1,80 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+import grpc
+
+import jmeter_pb2 as jmeter__pb2
+
+
+class ControllerStub(object):
+  """The controller service definition.
+  """
+
+  def __init__(self, channel):
+    """Constructor.
+
+    Args:
+      channel: A grpc.Channel.
+    """
+    self.GenTest = channel.unary_unary(
+        '/jmeter.Controller/GenTest',
+        request_serializer=jmeter__pb2.ConfigJmeter.SerializeToString,
+        response_deserializer=jmeter__pb2.JmeterReply.FromString,
+        )
+    self.StartTest = channel.unary_unary(
+        '/jmeter.Controller/StartTest',
+        request_serializer=jmeter__pb2.TestParams.SerializeToString,
+        response_deserializer=jmeter__pb2.JmeterReply.FromString,
+        )
+    self.GetResults = channel.unary_unary(
+        '/jmeter.Controller/GetResults',
+        request_serializer=jmeter__pb2.JResults.SerializeToString,
+        response_deserializer=jmeter__pb2.JmeterReply.FromString,
+        )
+
+
+class ControllerServicer(object):
+  """The controller service definition.
+  """
+
+  def GenTest(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+  def StartTest(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+  def GetResults(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+
+def add_ControllerServicer_to_server(servicer, server):
+  rpc_method_handlers = {
+      'GenTest': grpc.unary_unary_rpc_method_handler(
+          servicer.GenTest,
+          request_deserializer=jmeter__pb2.ConfigJmeter.FromString,
+          response_serializer=jmeter__pb2.JmeterReply.SerializeToString,
+      ),
+      'StartTest': grpc.unary_unary_rpc_method_handler(
+          servicer.StartTest,
+          request_deserializer=jmeter__pb2.TestParams.FromString,
+          response_serializer=jmeter__pb2.JmeterReply.SerializeToString,
+      ),
+      'GetResults': grpc.unary_unary_rpc_method_handler(
+          servicer.GetResults,
+          request_deserializer=jmeter__pb2.JResults.FromString,
+          response_serializer=jmeter__pb2.JmeterReply.SerializeToString,
+      ),
+  }
+  generic_handler = grpc.method_handlers_generic_handler(
+      'jmeter.Controller', rpc_method_handlers)
+  server.add_generic_rpc_handlers((generic_handler,))
diff --git a/clover/tools/jmeter/jmeter-master/grpc/jmeter_server.py b/clover/tools/jmeter/jmeter-master/grpc/jmeter_server.py
new file mode 100644 (file)
index 0000000..cef180c
--- /dev/null
@@ -0,0 +1,118 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+from concurrent import futures
+from jinja2 import Template
+from urlparse import urlparse
+import time
+import sys
+import grpc
+import subprocess
+import pickle
+import logging
+import jmeter_pb2
+import jmeter_pb2_grpc
+
+
+_ONE_DAY_IN_SECONDS = 60 * 60 * 24
+GRPC_PORT = '[::]:50054'
+
+
+class Controller(jmeter_pb2_grpc.ControllerServicer):
+
+    def __init__(self, init_jmeter):
+        logging.basicConfig(filename='jmeter_server.log',
+                            level=logging.DEBUG)
+        self.jmeter = 0
+        if init_jmeter == 'init':
+            print('init test')
+
+    def GenTest(self, r, context):
+        try:
+            out_file = 'tests/test.jmx'
+            template_file = 'tests/jmx.template'
+            unames = pickle.loads(r.url_names)
+            umethods = pickle.loads(r.url_methods)
+            ulist = []
+            for url in pickle.loads(r.url_list):
+                u = urlparse(url)
+                d = {}
+                d['domain'] = u.hostname
+                if u.port:
+                    d['port'] = u.port
+                else:
+                    d['port'] = 80
+                if u.path == '':
+                    d['path'] = '/'
+                else:
+                    d['path'] = u.path
+                ulist.append(d)
+
+            with open(template_file) as f:
+                tmpl = Template(f.read())
+            output = tmpl.render(
+                num_threads=r.num_threads,
+                url_names=unames,
+                url_methods=umethods,
+                ramp_time=r.ramp_time,
+                loops=r.loops,
+                url_list=ulist
+            )
+            with open(out_file, "wb") as fh:
+                fh.write(output)
+            msg = 'Generated test plan'
+        except Exception as e:
+            logging.debug(e)
+            msg = "Failed to generate test plan"
+        return jmeter_pb2.JmeterReply(message=msg)
+
+    def StartTest(self, r, context):
+        try:
+            if r.num_slaves == '0':
+                self.jmeter = subprocess.Popen(
+                 ["jmeter", "-n", "-t", "tests/test.jmx", "-l",
+                            "default.jtl"], shell=False)
+            else:
+                slave_arg = "-R" + r.slave_ips
+                self.jmeter = subprocess.Popen(
+                 ["jmeter", "-n", "-t", "tests/test.jmx", slave_arg, "-l",
+                            "default.jtl"], shell=False)
+            msg = "Started jmeter on pid: {}".format(self.jmeter.pid)
+        except Exception as e:
+            logging.debug(e)
+            msg = e
+        return jmeter_pb2.JmeterReply(message=msg)
+
+    def GetResults(self, r, context):
+        try:
+            if r.r_file == 'log':
+                r_file = 'jmeter.log'
+            else:
+                r_file = 'default.jtl'
+            f = open(r_file, 'r')
+            msg = "Retrieved all results\n" + f.read()
+        except Exception as e:
+            logging.debug(e)
+            msg = "Failed to retrieve results"
+        return jmeter_pb2.JmeterReply(message=msg)
+
+
+def serve(init_jmeter):
+    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
+    jmeter_pb2_grpc.add_ControllerServicer_to_server(
+                    Controller(init_jmeter), server)
+    server.add_insecure_port(GRPC_PORT)
+    server.start()
+    try:
+        while True:
+            time.sleep(_ONE_DAY_IN_SECONDS)
+    except KeyboardInterrupt:
+        server.stop(0)
+
+
+if __name__ == '__main__':
+    serve(sys.argv[1])
diff --git a/clover/tools/jmeter/jmeter-master/process/grpc_process.sh b/clover/tools/jmeter/jmeter-master/process/grpc_process.sh
new file mode 100755 (executable)
index 0000000..0f14d7c
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+python grpc/jmeter_server.py test1
diff --git a/clover/tools/jmeter/jmeter-master/tests/jmx.template b/clover/tools/jmeter/jmeter-master/tests/jmx.template
new file mode 100644 (file)
index 0000000..1a6fa95
--- /dev/null
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jmeterTestPlan version="1.2" properties="4.0" jmeter="4.0 r1823414">
+  <hashTree>
+    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
+      <stringProp name="TestPlan.comments"></stringProp>
+      <boolProp name="TestPlan.functional_mode">false</boolProp>
+      <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
+      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
+      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments"/>
+      </elementProp>
+      <stringProp name="TestPlan.user_define_classpath"></stringProp>
+    </TestPlan>
+    <hashTree>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="ThreadGroup" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="ThreadGroup" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <intProp name="LoopController.loops">{{ loops }}</intProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">{{ num_threads }}</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">{{ ramp_time }}</stringProp>
+        <longProp name="ThreadGroup.start_time">1385457190000</longProp>
+        <longProp name="ThreadGroup.end_time">1385457190000</longProp>
+        <boolProp name="ThreadGroup.scheduler">true</boolProp>
+        <stringProp name="ThreadGroup.duration">60</stringProp>
+        <stringProp name="ThreadGroup.delay"/>
+        <boolProp name="ThreadGroup.delayedStart">true</boolProp>
+      </ThreadGroup>
+      <hashTree>
+        {%- for u in url_list %}
+        <HTTPSampler guiclass="HttpTestSampleGui" testclass="HTTPSampler" testname="{{ url_names[loop.index0] }}" enabled="true">
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="{{ url_names[loop.index0] }}" enabled="true">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+          <stringProp name="HTTPSampler.domain">{{ u['domain'] }}</stringProp>
+          <stringProp name="HTTPSampler.port">{{ u['port'] }}</stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"/>
+          <stringProp name="HTTPSampler.response_timeout"/>
+          <stringProp name="HTTPSampler.protocol">http</stringProp>
+          <stringProp name="HTTPSampler.contentEncoding"/>
+          <stringProp name="HTTPSampler.path">{{ u['path'] }}</stringProp>
+          <stringProp name="HTTPSampler.method">{{ url_methods[loop.index0] }}</stringProp>
+          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+          <boolProp name="HTTPSampler.monitor">false</boolProp>
+          <stringProp name="HTTPSampler.embedded_url_re"/>
+          <stringProp name="HTTPSampler.implementation"/>
+        </HTTPSampler>
+        <hashTree/>
+        {%- endfor %}
+
+
+          </hashTree>
+          <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true">
+        <boolProp name="ResultCollector.error_logging">false</boolProp>
+        <objProp>
+          <name>saveConfig</name>
+          <value class="SampleSaveConfiguration">
+            <time>true</time>
+            <latency>true</latency>
+            <timestamp>true</timestamp>
+            <success>true</success>
+            <label>true</label>
+            <code>true</code>
+            <message>true</message>
+            <threadName>true</threadName>
+            <dataType>true</dataType>
+            <encoding>false</encoding>
+            <assertions>true</assertions>
+            <subresults>true</subresults>
+            <responseData>false</responseData>
+            <samplerData>false</samplerData>
+            <xml>false</xml>
+            <fieldNames>true</fieldNames>
+            <responseHeaders>false</responseHeaders>
+            <requestHeaders>false</requestHeaders>
+            <responseDataOnError>false</responseDataOnError>
+            <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+            <assertionsResultsToSave>0</assertionsResultsToSave>
+            <bytes>true</bytes>
+            <sentBytes>true</sentBytes>
+            <threadCounts>true</threadCounts>
+            <idleTime>true</idleTime>
+            <connectTime>true</connectTime>
+          </value>
+        </objProp>
+        <stringProp name="filename">/jmeter/bin/results2</stringProp>
+      </ResultCollector>
+      <hashTree/>
+
+
+      <ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="Aggregate Report" enabled="true">
+        <boolProp name="ResultCollector.error_logging">false</boolProp>
+        <objProp>
+          <name>saveConfig</name>
+          <value class="SampleSaveConfiguration">
+            <time>true</time>
+            <latency>true</latency>
+            <timestamp>true</timestamp>
+            <success>true</success>
+            <label>true</label>
+            <code>true</code>
+            <message>true</message>
+            <threadName>true</threadName>
+            <dataType>true</dataType>
+            <encoding>false</encoding>
+            <assertions>true</assertions>
+            <subresults>true</subresults>
+            <responseData>false</responseData>
+            <samplerData>false</samplerData>
+            <xml>false</xml>
+            <fieldNames>true</fieldNames>
+            <responseHeaders>false</responseHeaders>
+            <requestHeaders>false</requestHeaders>
+            <responseDataOnError>false</responseDataOnError>
+            <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+            <assertionsResultsToSave>0</assertionsResultsToSave>
+            <bytes>true</bytes>
+            <sentBytes>true</sentBytes>
+            <threadCounts>true</threadCounts>
+            <idleTime>true</idleTime>
+            <connectTime>true</connectTime>
+          </value>
+        </objProp>
+        <stringProp name="filename">/jmeter/bin/results_aggregate</stringProp>
+      </ResultCollector>
+      <hashTree/>
+
+
+    </hashTree>
+  </hashTree>
+</jmeterTestPlan>
diff --git a/clover/tools/jmeter/jmeter-slave/Dockerfile b/clover/tools/jmeter/jmeter-slave/Dockerfile
new file mode 100644 (file)
index 0000000..b5ccbcd
--- /dev/null
@@ -0,0 +1,27 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+FROM java:8
+
+RUN wget http://apache.mirrors.hoobly.com//jmeter/binaries/apache-jmeter-4.0.tgz
+RUN tar -xvzf apache-jmeter-4.0.tgz
+RUN rm apache-jmeter-4.0.tgz
+RUN mv apache-jmeter-4.0 /jmeter
+ENV JMETER_HOME /jmeter
+ENV PATH $JMETER_HOME/bin:$PATH
+RUN mkdir /share
+COPY rmi_keystore.jks $JMETER_HOME/bin
+
+WORKDIR $JMETER_HOME
+# Ports to be exposed from the container for JMeter Master
+RUN mkdir scripts
+
+EXPOSE 1099
+
+WORKDIR /jmeter/bin
+
+CMD ./jmeter-server
diff --git a/clover/tools/jmeter/rmi_keystore.jks b/clover/tools/jmeter/rmi_keystore.jks
new file mode 100644 (file)
index 0000000..f503361
Binary files /dev/null and b/clover/tools/jmeter/rmi_keystore.jks differ
diff --git a/clover/tools/jmeter/yaml/manifest.template b/clover/tools/jmeter/yaml/manifest.template
new file mode 100644 (file)
index 0000000..01a4595
--- /dev/null
@@ -0,0 +1,45 @@
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  name: {{ deploy_name }}
+  labels:
+    app: {{ deploy_name }}
+spec:
+{%- if replica_count %}
+  replicas: 3{% endif %}
+  template:
+    metadata:
+      labels:
+        app: {{ deploy_name }}
+    spec:
+      containers:
+        - name: {{ deploy_name }}
+          image: {{ image_path }}/{{ image_name }}:{{ image_tag }}
+          ports:
+{%- if grpc_port %}
+           - containerPort: {{ grpc_port }}{% endif %}
+           - containerPort: {{ rmi_port }}
+           - containerPort: {{ http_port }}
+           - containerPort: {{ ssl_port }}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ deploy_name }}
+  labels:
+    app: {{ deploy_name }}
+spec:
+  ports:
+{%- if grpc_port %}
+  - port: {{ grpc_port }}
+    name: grpc{% endif %}
+  - port: {{ rmi_port }}
+    name: rmi
+  - port: {{ http_port }}
+    name: http
+  - port: {{ ssl_port }}
+    name: https
+  selector:
+    app: {{ deploy_name }}
+---
diff --git a/clover/tools/jmeter/yaml/render_master.py b/clover/tools/jmeter/yaml/render_master.py
new file mode 100644 (file)
index 0000000..884ee81
--- /dev/null
@@ -0,0 +1,67 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+import argparse
+
+from jinja2 import Template
+
+
+def render_yaml(args):
+    template_file = 'manifest.template'
+    out_file = args['deploy_name'] + '.yaml'
+
+    try:
+        with open(template_file) as f:
+            tmpl = Template(f.read())
+        output = tmpl.render(
+            image_path=args['image_path'],
+            image_name=args['image_name'],
+            image_tag=args['image_tag'],
+            deploy_name=args['deploy_name'],
+            grpc_port=args['grpc_port'],
+            ssl_port=args['ssl_port'],
+            rmi_port=args['rmi_port'],
+            http_port=args['http_port']
+        )
+        with open(out_file, "wb") as fh:
+            fh.write(output)
+        return "Generated manifest for {}".format(args['deploy_name'])
+    except Exception as e:
+        print(e)
+        return "Unable to generate manifest for {}".format(
+                                        args['deploy_name'])
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+            '--image_name', default='clover-jmeter-master',
+            help='The image name to use')
+    parser.add_argument(
+            '--image_path', default='localhost:5000',
+            help='The path to the image to use')
+    parser.add_argument(
+            '--image_tag', default='latest',
+            help='The image tag to use')
+    parser.add_argument(
+            '--deploy_name', default='clover-jmeter-master',
+            help='The k8s deploy name to use')
+    parser.add_argument(
+            '--rmi_port', default='1099',
+            help='The master-slave remote method invocation port')
+    parser.add_argument(
+            '--http_port', default='80',
+            help='HTTP data-plane traffic')
+    parser.add_argument(
+            '--grpc_port', default='50054',
+            help='The GRPC server port for management')
+    parser.add_argument(
+            '--ssl_port', default='443',
+            help='HTTPS data-plane traffic')
+
+    args = parser.parse_args()
+    print(render_yaml(vars(args)))
diff --git a/clover/tools/jmeter/yaml/render_slave.py b/clover/tools/jmeter/yaml/render_slave.py
new file mode 100644 (file)
index 0000000..cd9fd91
--- /dev/null
@@ -0,0 +1,67 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+import argparse
+
+from jinja2 import Template
+
+
+def render_yaml(args):
+    template_file = 'manifest.template'
+    out_file = args['deploy_name'] + '.yaml'
+
+    try:
+        with open(template_file) as f:
+            tmpl = Template(f.read())
+        output = tmpl.render(
+            image_path=args['image_path'],
+            image_name=args['image_name'],
+            image_tag=args['image_tag'],
+            deploy_name=args['deploy_name'],
+            replica_count=args['replica_count'],
+            ssl_port=args['ssl_port'],
+            rmi_port=args['rmi_port'],
+            http_port=args['http_port']
+        )
+        with open(out_file, "wb") as fh:
+            fh.write(output)
+        return "Generated manifest for {}".format(args['deploy_name'])
+    except Exception as e:
+        print(e)
+        return "Unable to generate manifest for {}".format(
+                                        args['deploy_name'])
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+            '--image_name', default='clover-jmeter-slave',
+            help='The image name to use')
+    parser.add_argument(
+            '--image_path', default='localhost:5000',
+            help='The path to the image to use')
+    parser.add_argument(
+            '--image_tag', default='latest',
+            help='The image tag to use')
+    parser.add_argument(
+            '--deploy_name', default='clover-jmeter-slave',
+            help='The k8s deploy name to use')
+    parser.add_argument(
+            '--rmi_port', default='1099',
+            help='The master-slave remote method invocation port')
+    parser.add_argument(
+            '--http_port', default='80',
+            help='HTTP data-plane traffic')
+    parser.add_argument(
+            '--replica_count', default='3',
+            help='Number of replicas in slave deployment')
+    parser.add_argument(
+            '--ssl_port', default='443',
+            help='HTTPS data-plane traffic')
+
+    args = parser.parse_args()
+    print(render_yaml(vars(args)))