Introduce Gerrit Inspector: interactive Jython shell
Adds "-s" option to com.google.gerrit.pgm.Daemon to start an interactive Jython shell for inspection and troubleshooting of live data of the Gerrit instance. Gerrit Inspector is an interactive scriptable environment to inspect and modify internal state of the system. This environment is available on the system console after the system starts. Leaving the Inspector will shutdown the Gerrit instance. The environment allows interactive work as well as running of Python scripts for troubleshooting. Accessing Java Virtual Machine objects and calling Java methods is possible. Gerrit Inspect can be started by adding -s option to the command used to launch the daemon: java -jar buck-out/gen/gerrit.war daemon -d ../test_site -s For more information on this facility please see: Documentation/dev-inspector.txt Implementation remark: Jython needs to examine available .jar files on startup. It does so currently by reading contents of the WAR archive unpacked by GerritLauncher in the temporary directory. Jython is able to store this information persistently, but it does not currently work because we unpack WAR file every time on startup. Currently no attempt is made to introspect Guice bindings. Change-Id: I47ffc8383fd50bc6ec12ba31edb6d7d614e97bd5
This commit is contained in:
committed by
Shawn Pearce
parent
aebbe03e4f
commit
ed612fb44a
302
Documentation/dev-inspector.txt
Normal file
302
Documentation/dev-inspector.txt
Normal file
@@ -0,0 +1,302 @@
|
||||
Gerrit Inspector
|
||||
================
|
||||
|
||||
NAME
|
||||
----
|
||||
Gerrit Inspector - Interactive Jython environment for Gerrit
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'java' -jar gerrit.war 'daemon'
|
||||
-d <SITE_PATH>
|
||||
[\--enable-httpd | \--disable-httpd]
|
||||
[\--enable-sshd | \--disable-sshd]
|
||||
[\--console-log]
|
||||
[\--slave]
|
||||
-s
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
Runs the Gerrit network daemon on the local system as described
|
||||
in the link:pgm-daemon.html[Daemon documentation], additionally
|
||||
starting an interactive Jython shell for inspection
|
||||
and troubleshooting of live data of the Gerrit instance.
|
||||
|
||||
CAUTION: Gerrit Inspector works directly on instances of Java Virtual
|
||||
Machine objects and it is possible to read and write instance
|
||||
members as well as invoke Java functions. Access is granted
|
||||
also to 'private' and 'protected' members. Therefore it is possible
|
||||
to introduce changes to the internal state of the system in
|
||||
an inconsistent way. Care must be taken not to break the running system
|
||||
and/or destroy the data.
|
||||
|
||||
INSTALLATION
|
||||
------------
|
||||
|
||||
Gerrit Inspector requires Jython library ('jython.jar') to be installed
|
||||
in the '$site_path/lib' directory. Jython, a Python interpreter for
|
||||
the Java Virtual Machine, can be obtained from the http://www.jython.org/
|
||||
website. Only 'jython.jar' file is needed, installation of Jython libraries
|
||||
is optional. Gerrit Inspector has been tested with Jython 2.5.2 but
|
||||
might work an earlier version.
|
||||
|
||||
STARTUP
|
||||
-------
|
||||
|
||||
During startup Jython examines Java libraries found on the classpath.
|
||||
While libraries are inspected a large amount of messages is displayed on the console:
|
||||
|
||||
----
|
||||
*sys-package-mgr*: processing new jar, '/home/user/.gerritcodereview/tmp/gerrit_4890671371398741854_app/sshd-core-0.5.1-r1095809.jar'
|
||||
----
|
||||
|
||||
After this a system-wide embedded initialization script is started. This script
|
||||
is contained in the gerrit's WAR archive. This script produces output similar to
|
||||
the following on the console:
|
||||
|
||||
----
|
||||
"Shell" is "com.google.gerrit.pgm.shell.JythonShell@61644f2d"
|
||||
"m" is "com.google.gerrit.lifecycle.LifecycleManager@6f03b248"
|
||||
"ds" is "com.google.gerrit.server.schema.DataSourceProvider@6b3592c"
|
||||
"schk" is "com.google.gerrit.server.schema.SchemaVersionCheck@5e8cb9bd"
|
||||
|
||||
Welcome to the Gerrit Inspector
|
||||
Enter help() to see the above again, EOF to quit and stop Gerrit
|
||||
----
|
||||
|
||||
Then an optional user startup script is processed. It should be
|
||||
located in the gerrit user home directory as '.gerritcodereview/Startup.py'.
|
||||
|
||||
This script can access all variables defined in the system (such
|
||||
as the ones displayed by the initialization script as shown above).
|
||||
Variables and functions defined by the startup scripts are available for
|
||||
the interactive interpreter.
|
||||
|
||||
When interactive interpreter exits (by issuing EOF on the command line),
|
||||
a whole Gerrit instance is shut down gracefully.
|
||||
|
||||
USING THE INTERPRETER
|
||||
---------------------
|
||||
|
||||
Gerrit Inspector launches Jython interpreter in the context of the Gerrit
|
||||
Java Virtual Machine. All core facilities of the Jython (and Python)
|
||||
language are available to the user.
|
||||
|
||||
Additional facilities can be provided, for example a 'Lib' directory from the
|
||||
Jython distribution can be installed under '$site_path/lib/Lib' to provide
|
||||
access to many standard Python modules. Jython can also use additional Java
|
||||
classes and libraries and most of the Python modules and scripts.
|
||||
|
||||
The Inspector has by default access to classes and object instances available
|
||||
in the Java Virtual Machine. Objects are introspected and *private* and *protected*
|
||||
members are also available.
|
||||
|
||||
For more information on using Jython, especially with regards to its limitations
|
||||
in interfacing to the Java Virtual Machine, please refer to the
|
||||
http://www.jython.org/[Jython documentation].
|
||||
|
||||
After sucessful initialization it is possible to examine components of
|
||||
Java packages, classes and live instances.
|
||||
|
||||
----
|
||||
>>> import com.google.inject
|
||||
>>> dir(com.google.inject)
|
||||
['AbstractModule', 'Binder', 'Binding', 'BindingAnnotation', 'ConfigurationException', 'CreationException', 'Exposed', 'Guice', 'ImplementedBy', 'Inject', 'Injector', 'Key', 'MembersInjector', 'Module', 'OutOfScopeException', 'PrivateBinder', 'PrivateModule', 'ProvidedBy', 'Provider', 'Provides', 'ProvisionException', 'Scope', 'ScopeAnnotation', 'Scopes', 'Singleton', 'Stage', 'TypeLiteral', '__name__', 'assistedinject', 'binder', 'internal', 'matcher', 'name', 'servlet', 'spi', 'util']
|
||||
>>> type(com.google.inject)
|
||||
<type 'javapackage'>
|
||||
>>> dir(com.google.inject.Guice)
|
||||
['__class__', '__copy__', '__deepcopy__', '__delattr__', '__doc__',
|
||||
'__eq__', '__getattribute__', '__hash__', '__init__', '__ne__',
|
||||
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
|
||||
'__str__', '__unicode__', 'class', 'clone', 'createInjector',
|
||||
'equals', 'finalize', 'getClass', 'hashCode', 'notify', 'notifyAll',
|
||||
'registerNatives', 'toString', 'wait']
|
||||
----
|
||||
|
||||
Startup script provides some convenient variables to access some global Gerrit components,
|
||||
for example a connection to the review database is kept open:
|
||||
|
||||
----
|
||||
>>> ds
|
||||
org.apache.commons.dbcp.BasicDataSource@61db2215
|
||||
>>> ds.driverClassName
|
||||
u'org.postgresql.Driver'
|
||||
>>> ds.dataSource
|
||||
org.apache.commons.dbcp.PoolingDataSource@23226fe1
|
||||
>>> ds.dataSource.connection
|
||||
jdbc:postgresql://localhost/reviewdb, UserName=rv, PostgreSQL Native Driver
|
||||
----
|
||||
|
||||
It is also possible to interact with the ORM layer:
|
||||
|
||||
----
|
||||
>>> db = schk.schema.open()
|
||||
>>> db
|
||||
com.google.gerrit.reviewdb.server.ReviewDb_Schema_GwtOrm$$28@24cbbdf3
|
||||
>>> db.getDialect()
|
||||
com.google.gwtorm.schema.sql.DialectPostgreSQL@4de07d3e
|
||||
>>> for x in db.patchSets().iterateAllEntities():
|
||||
... print x
|
||||
...
|
||||
[PatchSet 1,1]
|
||||
[PatchSet 2,1]
|
||||
[PatchSet 3,1]
|
||||
[PatchSet 4,1]
|
||||
[PatchSet 5,1]
|
||||
[PatchSet 6,1]
|
||||
[PatchSet 7,1]
|
||||
[PatchSet 8,1]
|
||||
[PatchSet 6,2]
|
||||
>>> for x in db.patchComments().iterateAllEntities():
|
||||
... print x
|
||||
com.google.gerrit.reviewdb.client.PatchLineComment@5381298a
|
||||
com.google.gerrit.reviewdb.client.PatchLineComment@44ce4dda
|
||||
com.google.gerrit.reviewdb.client.PatchLineComment@44594680
|
||||
>>> dir(com.google.gerrit.reviewdb.client.PatchLineComment)
|
||||
['Key', 'STATUS_DRAFT', 'STATUS_PUBLISHED', 'Status', '__class__',
|
||||
'__copy__', '__deepcopy__', '__delattr__', '__doc__', '__eq__',
|
||||
'__getattribute__', '__hash__', '__init__', '__ne__', '__new__',
|
||||
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__',
|
||||
'__unicode__', 'author', 'class', 'clone', 'equals', 'finalize',
|
||||
'getAuthor', 'getClass', 'getKey', 'getLine', 'getMessage',
|
||||
'getParentUuid', 'getSide', 'getStatus', 'getWrittenOn', 'hashCode',
|
||||
'key', 'line', 'lineNbr', 'message', 'notify', 'notifyAll',
|
||||
'parentUuid', 'registerNatives', 'setMessage', 'setSide', 'setStatus',
|
||||
'side', 'status', 'toString', 'updated', 'wait', 'writtenOn']
|
||||
>>> for x in db.patchComments().iterateAllEntities():
|
||||
... print x.status, x.line, x.message
|
||||
...
|
||||
P 2 I like it!
|
||||
P 2 more
|
||||
P 1 better
|
||||
----
|
||||
|
||||
A built-in *help()* function provides values of global variables
|
||||
defined in the interpreter:
|
||||
|
||||
----
|
||||
>>> help()
|
||||
"schk" is "com.google.gerrit.server.schema.SchemaVersionCheck@5e8cb9bd"
|
||||
"ds" is "com.google.gerrit.server.schema.DataSourceProvider@6b3592c"
|
||||
"m" is "com.google.gerrit.lifecycle.LifecycleManager@6f03b248"
|
||||
"q" is "com.google.gerrit.reviewdb.server.ReviewDb_Schema_GwtOrm$$23@56d3384d"
|
||||
"Shell" is "com.google.gerrit.pgm.shell.JythonShell@61644f2d"
|
||||
"x" is "com.google.gerrit.reviewdb.client.PatchLineComment@518acfa7"
|
||||
"z" is "com.google.gwtorm.jdbc.Database@668dfd9b"
|
||||
"db" is "com.google.gerrit.reviewdb.server.ReviewDb_Schema_GwtOrm$$23@6a3ccb09"
|
||||
|
||||
Welcome to the Gerrit Inspector
|
||||
Enter help() to see the above again, EOF to quit and stop Gerrit
|
||||
----
|
||||
|
||||
Java and Python exceptions are intercepted by the Inspector:
|
||||
----
|
||||
>>> import java.lang.RuntimeException
|
||||
>>> raise java.lang.RuntimeException("Exiting")
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
|
||||
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
|
||||
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
|
||||
at java.lang.reflect.Constructor.newInstance(Constructor.java:532)
|
||||
at org.python.core.PyReflectedConstructor.constructProxy(PyReflectedConstructor.java:210)
|
||||
|
||||
java.lang.RuntimeException: java.lang.RuntimeException: Exiting
|
||||
>>>
|
||||
----
|
||||
|
||||
To exit the interpreter, use EOF character (Ctrl-D on Unix systems, Ctrl-Z on Windows).
|
||||
|
||||
It is also possible to shut down the JVM by using *System.exit()*
|
||||
|
||||
----
|
||||
>>> import java.lang.System
|
||||
>>> java.lang.System.exit(1)
|
||||
----
|
||||
|
||||
And Gerrit should shut down all its subsystems and exit:
|
||||
|
||||
----
|
||||
[2012-04-17 15:31:08,458] INFO com.google.gerrit.pgm.Daemon : caught shutdown, cleaning up
|
||||
----
|
||||
|
||||
TROUBLESHOOTING
|
||||
---------------
|
||||
|
||||
Gerrit Inspector is logging to the Gerrit error log.
|
||||
|
||||
A successful startup is indicated in the logfile:
|
||||
|
||||
----
|
||||
[2012-04-17 13:43:44,888] INFO com.google.gerrit.pgm.shell.JythonShell : Jython shell instance created.
|
||||
----
|
||||
|
||||
If 'jython.jar' library is not available, Gerrit refuses to start when given *-s* option:
|
||||
|
||||
----
|
||||
[2012-04-17 13:57:29,611] ERROR com.google.gerrit.pgm.Daemon : Unable to start daemon
|
||||
com.google.inject.ProvisionException: Guice provision errors:
|
||||
|
||||
1) Error injecting constructor, java.lang.UnsupportedOperationException: Cannot create Jython shell: Class org.python.util.InteractiveConsole not found
|
||||
(You might need to install jython.jar in the lib directory)
|
||||
at com.google.gerrit.pgm.shell.JythonShell.<init>(JythonShell.java:47)
|
||||
while locating com.google.gerrit.pgm.shell.JythonShell
|
||||
while locating com.google.gerrit.pgm.shell.InteractiveShell
|
||||
----
|
||||
|
||||
Errors during processing of the startup script, 'Startup.py', are logged
|
||||
to the error log:
|
||||
|
||||
----
|
||||
[2012-04-17 14:20:30,558] INFO com.google.gerrit.pgm.shell.JythonShell : Jython shell instance created.
|
||||
[2012-04-17 14:20:38,005] ERROR com.google.gerrit.pgm.shell.JythonShell : Exception occured while loading file Startup.py :
|
||||
java.lang.reflect.InvocationTargetException
|
||||
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
||||
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
|
||||
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
||||
at java.lang.reflect.Method.invoke(Method.java:616)
|
||||
at com.google.gerrit.pgm.shell.JythonShell.runMethod0(JythonShell.java:112)
|
||||
at com.google.gerrit.pgm.shell.JythonShell.execFile(JythonShell.java:194)
|
||||
at com.google.gerrit.pgm.shell.JythonShell.reload(JythonShell.java:178)
|
||||
at com.google.gerrit.pgm.shell.JythonShell.run(JythonShell.java:152)
|
||||
at com.google.gerrit.pgm.Daemon.run(Daemon.java:190)
|
||||
at com.google.gerrit.pgm.util.AbstractProgram.main(AbstractProgram.java:67)
|
||||
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
||||
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
|
||||
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
||||
at java.lang.reflect.Method.invoke(Method.java:616)
|
||||
at com.google.gerrit.launcher.GerritLauncher.invokeProgram(GerritLauncher.java:167)
|
||||
at com.google.gerrit.launcher.GerritLauncher.mainImpl(GerritLauncher.java:91)
|
||||
at com.google.gerrit.launcher.GerritLauncher.main(GerritLauncher.java:49)
|
||||
at Main.main(Main.java:25)
|
||||
Caused by: Traceback (most recent call last):
|
||||
File "/home/user/.gerritcodereview/Startup.py", line 1, in <module>
|
||||
Test
|
||||
NameError: name 'Test' is not defined
|
||||
----
|
||||
|
||||
Those errors are non-fatal. System and user scripts can be loaded again
|
||||
by issuing the following command in the Gerrit Inspector console:
|
||||
|
||||
----
|
||||
Shell.reload()
|
||||
----
|
||||
|
||||
LOGGING
|
||||
-------
|
||||
Error and warning messages from the server are automatically written
|
||||
to the log file under '$site_path/logs/error_log'.
|
||||
|
||||
Output and error messages (including Java and Python exceptions)
|
||||
resulting from interactive work are logged to the console.
|
||||
|
||||
KNOWN ISSUES
|
||||
------------
|
||||
The Inspector does not yet recognize Google Guice bindings.
|
||||
|
||||
IMPORTANT: Using the Inspector may void your warranty.
|
||||
|
||||
GERRIT
|
||||
------
|
||||
Part of link:index.html[Gerrit Code Review]
|
||||
@@ -106,6 +106,43 @@ copying to the test site:
|
||||
java -jar buck-out/gen/gerrit.war daemon -d ../test_site
|
||||
----
|
||||
|
||||
Running the Daemon with Gerrit Inspector
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
link:dev-inspector.html[Gerrit Inspector] is an interactive scriptable
|
||||
environment to inspect and modify internal state of the system.
|
||||
|
||||
This environment is available on the system console after
|
||||
the system starts. Leaving the Inspector will shutdown the Gerrit
|
||||
instance.
|
||||
|
||||
The environment allows interactive work as well as running of
|
||||
Python scripts for troubleshooting.
|
||||
|
||||
Gerrit Inspect can be started by adding '-s' option to the
|
||||
command used to launch the daemon:
|
||||
|
||||
----
|
||||
java -jar buck-out/gen/gerrit.war daemon -d ../test_site -s
|
||||
----
|
||||
|
||||
Gerrit Inspector examines Java libraries first, then loads
|
||||
its initialization scripts and then starts a command line
|
||||
prompt on the console:
|
||||
|
||||
----
|
||||
Welcome to the Gerrit Inspector
|
||||
Enter help() to see the above again, EOF to quit and stop Gerrit
|
||||
Jython 2.5.2 (Release_2_5_2:7206, Mar 2 2011, 23:12:06)
|
||||
[OpenJDK 64-Bit Server VM (Sun Microsystems Inc.)] on java1.6.0 running for Gerrit 2.3-rc0-163-g01967ef
|
||||
>>>
|
||||
----
|
||||
|
||||
With the Inspector enabled Gerrit can be used normally and all
|
||||
interfaces (HTTP, SSH etc.) are available.
|
||||
|
||||
Care must be taken not to modify internal state of the system
|
||||
when using the Inspector.
|
||||
|
||||
Querying the Database
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -16,6 +16,7 @@ SYNOPSIS
|
||||
[\--slave]
|
||||
[\--headless]
|
||||
[\--init]
|
||||
[-s]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
@@ -68,6 +69,14 @@ This option automatically implies '\--disable-httpd \--enable-sshd'.
|
||||
Run init before starting the daemon. This will create a new site or
|
||||
upgrade an existing site.
|
||||
|
||||
\--s::
|
||||
Start link:dev-inspector.html[Gerrit Inspector] on the console, a
|
||||
built-in interactive inspection environment to assist debugging and
|
||||
troubleshooting of Gerrit code.
|
||||
+
|
||||
This options requires 'jython.jar' from the http://www.jython.org[Jython distribution]
|
||||
to be present in '$site_path/lib' directory.
|
||||
|
||||
CONTEXT
|
||||
-------
|
||||
This command can only be run on a server which has direct
|
||||
|
||||
@@ -300,6 +300,7 @@ public final class GerritLauncher {
|
||||
}
|
||||
|
||||
private volatile static File myArchive;
|
||||
private static File myHome;
|
||||
|
||||
/**
|
||||
* Locate the JAR/WAR file we were launched from.
|
||||
@@ -457,42 +458,26 @@ public final class GerritLauncher {
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide path to a working directory
|
||||
*
|
||||
* @return local path of the working directory or null if cannot be determined
|
||||
*/
|
||||
public static File getHomeDirectory() {
|
||||
if (myHome == null) {
|
||||
myHome = locateHomeDirectory();
|
||||
}
|
||||
return myHome;
|
||||
}
|
||||
|
||||
|
||||
private static File tmproot() {
|
||||
File tmp;
|
||||
String gerritTemp = System.getenv("GERRIT_TMP");
|
||||
if (gerritTemp != null && gerritTemp.length() > 0) {
|
||||
tmp = new File(gerritTemp);
|
||||
} else {
|
||||
// Try to find the user's home directory. If we can't find it
|
||||
// return null so the JVM's default temporary directory is used
|
||||
// instead. This is probably /tmp or /var/tmp.
|
||||
//
|
||||
String userHome = System.getProperty("user.home");
|
||||
if (userHome == null || "".equals(userHome)) {
|
||||
userHome = System.getenv("HOME");
|
||||
if (userHome == null || "".equals(userHome)) {
|
||||
System.err.println("warning: cannot determine home directory");
|
||||
System.err.println("warning: using system temporary directory instead");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the home directory exists. If it doesn't, try to make it.
|
||||
//
|
||||
final File home = new File(userHome);
|
||||
if (!home.exists()) {
|
||||
if (home.mkdirs()) {
|
||||
System.err.println("warning: created " + home.getAbsolutePath());
|
||||
} else {
|
||||
System.err.println("warning: " + home.getAbsolutePath() + " not found");
|
||||
System.err.println("warning: using system temporary directory instead");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Use $HOME/.gerritcodereview/tmp for our temporary file area.
|
||||
//
|
||||
tmp = new File(new File(home, ".gerritcodereview"), "tmp");
|
||||
tmp = new File(getHomeDirectory(), "tmp");
|
||||
}
|
||||
if (!tmp.exists() && !tmp.mkdirs()) {
|
||||
System.err.println("warning: cannot create " + tmp.getAbsolutePath());
|
||||
@@ -525,6 +510,49 @@ public final class GerritLauncher {
|
||||
}
|
||||
}
|
||||
|
||||
private static File locateHomeDirectory() {
|
||||
// Try to find the user's home directory. If we can't find it
|
||||
// return null so the JVM's default temporary directory is used
|
||||
// instead. This is probably /tmp or /var/tmp.
|
||||
//
|
||||
String userHome = System.getProperty("user.home");
|
||||
if (userHome == null || "".equals(userHome)) {
|
||||
userHome = System.getenv("HOME");
|
||||
if (userHome == null || "".equals(userHome)) {
|
||||
System.err.println("warning: cannot determine home directory");
|
||||
System.err.println("warning: using system temporary directory instead");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the home directory exists. If it doesn't, try to make it.
|
||||
//
|
||||
final File home = new File(userHome);
|
||||
if (!home.exists()) {
|
||||
if (home.mkdirs()) {
|
||||
System.err.println("warning: created " + home.getAbsolutePath());
|
||||
} else {
|
||||
System.err.println("warning: " + home.getAbsolutePath() + " not found");
|
||||
System.err.println("warning: using system temporary directory instead");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Use $HOME/.gerritcodereview/tmp for our temporary file area.
|
||||
//
|
||||
final File gerrithome = new File(home, ".gerritcodereview");
|
||||
if (!gerrithome.exists() && !gerrithome.mkdirs()) {
|
||||
System.err.println("warning: cannot create " + gerrithome.getAbsolutePath());
|
||||
System.err.println("warning: using system temporary directory instead");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return gerrithome.getCanonicalFile();
|
||||
} catch (IOException e) {
|
||||
return gerrithome;
|
||||
}
|
||||
}
|
||||
|
||||
private GerritLauncher() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.google.gerrit.pgm.http.jetty.GetUserFilter;
|
||||
import com.google.gerrit.pgm.http.jetty.JettyEnv;
|
||||
import com.google.gerrit.pgm.http.jetty.JettyModule;
|
||||
import com.google.gerrit.pgm.http.jetty.ProjectQoSFilter;
|
||||
import com.google.gerrit.pgm.shell.JythonShell;
|
||||
import com.google.gerrit.pgm.util.ErrorLogFile;
|
||||
import com.google.gerrit.pgm.util.GarbageCollectionLogFile;
|
||||
import com.google.gerrit.pgm.util.LogFileCompressor;
|
||||
@@ -57,6 +58,7 @@ import com.google.gerrit.server.mail.SmtpEmailSender;
|
||||
import com.google.gerrit.server.patch.IntraLineWorkerPool;
|
||||
import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
|
||||
import com.google.gerrit.server.plugins.PluginRestApiModule;
|
||||
import com.google.gerrit.server.schema.DataSourceProvider;
|
||||
import com.google.gerrit.server.schema.SchemaVersionCheck;
|
||||
import com.google.gerrit.server.ssh.NoSshKeyCache;
|
||||
import com.google.gerrit.server.ssh.NoSshModule;
|
||||
@@ -109,6 +111,9 @@ public class Daemon extends SiteProgram {
|
||||
@Option(name = "--console-log", usage = "Log to console (not $site_path/logs)")
|
||||
private boolean consoleLog;
|
||||
|
||||
@Option(name = "-s", usage = "Start interactive shell")
|
||||
private boolean inspector;
|
||||
|
||||
@Option(name = "--run-id", usage = "Cookie to store in $site_path/logs/gerrit.run")
|
||||
private String runId;
|
||||
|
||||
@@ -224,7 +229,15 @@ public class Daemon extends SiteProgram {
|
||||
serverStarted.run();
|
||||
}
|
||||
|
||||
RuntimeShutdown.waitFor();
|
||||
if (inspector) {
|
||||
JythonShell shell = new JythonShell();
|
||||
shell.set("m", manager);
|
||||
shell.set("ds", dbInjector.getInstance(DataSourceProvider.class));
|
||||
shell.set("schk", dbInjector.getInstance(SchemaVersionCheck.class));
|
||||
shell.run();
|
||||
} else {
|
||||
RuntimeShutdown.waitFor();
|
||||
}
|
||||
return 0;
|
||||
} catch (Throwable err) {
|
||||
log.error("Unable to start daemon", err);
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
package com.google.gerrit.pgm.shell;
|
||||
|
||||
import com.google.gerrit.launcher.GerritLauncher;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Properties;
|
||||
|
||||
public class JythonShell {
|
||||
private static final Logger log = LoggerFactory.getLogger(JythonShell.class);
|
||||
private static final String STARTUP_RESOURCE = "com/google/gerrit/pgm/Startup.py";
|
||||
private static final String STARTUP_FILE = "Startup.py";
|
||||
|
||||
private Class<?> console;
|
||||
private Class<?> pyObject;
|
||||
private Class<?> pySystemState;
|
||||
private Object shell;
|
||||
private ArrayList <String> injectedVariables;
|
||||
|
||||
public JythonShell() {
|
||||
Properties env = new Properties();
|
||||
// Let us inspect private class members
|
||||
env.setProperty("python.security.respectJavaAccessibility", "false");
|
||||
|
||||
File home = GerritLauncher.getHomeDirectory();
|
||||
if (home != null) {
|
||||
env.setProperty("python.cachedir", new File(home, "jythoncache").getPath());
|
||||
}
|
||||
|
||||
// For package introspection and "import com.google" to work,
|
||||
// Jython needs to inspect actual .jar files (not just classloader)
|
||||
StringBuilder classPath = new StringBuilder();
|
||||
final ClassLoader cl = getClass().getClassLoader();
|
||||
if (cl instanceof java.net.URLClassLoader) {
|
||||
URLClassLoader ucl = (URLClassLoader) cl;
|
||||
for (URL u : ucl.getURLs()) {
|
||||
if ("file".equals(u.getProtocol())) {
|
||||
if (classPath.length() > 0) {
|
||||
classPath.append(java.io.File.pathSeparatorChar);
|
||||
}
|
||||
classPath.append(u.getFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
env.setProperty("java.class.path", classPath.toString());
|
||||
|
||||
console = findClass("org.python.util.InteractiveConsole");
|
||||
pyObject = findClass("org.python.core.PyObject");
|
||||
pySystemState = findClass("org.python.core.PySystemState");
|
||||
|
||||
runMethod(pySystemState, pySystemState, "initialize",
|
||||
new Class[] { Properties.class, Properties.class },
|
||||
new Object[] { null, env }
|
||||
);
|
||||
|
||||
try {
|
||||
shell = console.newInstance();
|
||||
log.info("Jython shell instance created.");
|
||||
} catch (InstantiationException e) {
|
||||
throw noInterpreter(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw noInterpreter(e);
|
||||
}
|
||||
injectedVariables = new ArrayList<String>();
|
||||
set("Shell", this);
|
||||
}
|
||||
|
||||
protected Object runMethod0(Class<?> klazz, Object instance,
|
||||
String name, Class<?>[] sig, Object[] args)
|
||||
throws InvocationTargetException {
|
||||
try {
|
||||
Method m;
|
||||
m = klazz.getMethod(name, sig);
|
||||
return m.invoke(instance, args);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw cannotStart(e);
|
||||
} catch (SecurityException e) {
|
||||
throw cannotStart(e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw cannotStart(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw cannotStart(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected Object runMethod(Class<?> klazz, Object instance,
|
||||
String name, Class<?>[] sig, Object[] args) {
|
||||
try {
|
||||
return runMethod0(klazz, instance, name, sig, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw cannotStart(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected Object runInterpreter(String name, Class<?>[] sig, Object[] args) {
|
||||
return runMethod(console, shell, name, sig, args);
|
||||
}
|
||||
|
||||
protected String getDefaultBanner() {
|
||||
return (String)runInterpreter("getDefaultBanner",
|
||||
new Class[] { }, new Object[] { });
|
||||
}
|
||||
|
||||
protected void printInjectedVariable(String id) {
|
||||
runInterpreter("exec",
|
||||
new Class[] { String.class },
|
||||
new Object[] { "print '\"%s\" is \"%s\"' % (\"" + id + "\", " + id + ")" }
|
||||
);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
for (String key : injectedVariables) {
|
||||
printInjectedVariable(key);
|
||||
}
|
||||
reload();
|
||||
runInterpreter("interact",
|
||||
new Class[] { String.class, pyObject },
|
||||
new Object[] { getDefaultBanner() +
|
||||
" running for Gerrit " + com.google.gerrit.common.Version.getVersion(),
|
||||
null });
|
||||
}
|
||||
|
||||
public void set(String key, Object content) {
|
||||
runInterpreter("set",
|
||||
new Class[] { String.class, Object.class },
|
||||
new Object[] { key, content }
|
||||
);
|
||||
injectedVariables.add(key);
|
||||
}
|
||||
|
||||
private static Class<?> findClass(String klazzname) {
|
||||
try {
|
||||
return Class.forName(klazzname);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw noShell("Class " + klazzname + " not found", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
execResource(STARTUP_RESOURCE);
|
||||
execFile(GerritLauncher.getHomeDirectory(), STARTUP_FILE);
|
||||
}
|
||||
|
||||
protected void execResource(final String p) {
|
||||
InputStream in = JythonShell.class.getClassLoader().getResourceAsStream(p);
|
||||
if (in != null) {
|
||||
execStream(in, "resource " + p);
|
||||
} else {
|
||||
log.error("Cannot load resource " + p);
|
||||
}
|
||||
}
|
||||
|
||||
protected void execFile(final File parent, final String p) {
|
||||
try {
|
||||
File script = new File(parent, p);
|
||||
if (script.canExecute()) {
|
||||
runMethod0(console, shell, "execfile",
|
||||
new Class[] { String.class },
|
||||
new Object[] { script.getAbsolutePath() }
|
||||
);
|
||||
} else {
|
||||
log.info("User initialization file "
|
||||
+ script.getAbsolutePath()
|
||||
+ " is not found or not executable");
|
||||
}
|
||||
} catch (InvocationTargetException e) {
|
||||
log.error("Exception occured while loading file " + p + " : ", e);
|
||||
} catch (SecurityException e) {
|
||||
log.error("SecurityException occured while loading file " + p + " : ", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void execStream(final InputStream in, final String p) {
|
||||
try {
|
||||
runMethod0(console, shell, "execfile",
|
||||
new Class[] { InputStream.class, String.class },
|
||||
new Object[] { in, p }
|
||||
);
|
||||
} catch (InvocationTargetException e) {
|
||||
log.error("Exception occured while loading " + p + " : ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static UnsupportedOperationException noShell(final String m, Throwable why) {
|
||||
final String prefix = "Cannot create Jython shell: ";
|
||||
final String postfix = "\n (You might need to install jython.jar in the lib directory)";
|
||||
return new UnsupportedOperationException(prefix + m + postfix, why);
|
||||
}
|
||||
|
||||
private static UnsupportedOperationException noInterpreter(Throwable why) {
|
||||
final String msg = "Cannot create Python interpreter";
|
||||
return noShell(msg, why);
|
||||
}
|
||||
|
||||
private static UnsupportedOperationException cannotStart(Throwable why) {
|
||||
final String msg = "Cannot start Jython shell";
|
||||
return new UnsupportedOperationException(msg, why);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# Copyright (C) 2013 The Android Open Source Project
|
||||
#
|
||||
# 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.
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Startup script for Gerrit Inspector - a Jython introspector
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
import sys
|
||||
|
||||
def help():
|
||||
for (n, v) in vars(sys.modules['__main__']).items():
|
||||
if not n.startswith("__") and not n in ['help', 'reload'] \
|
||||
and str(type(v)) != "<type 'javapackage'>" \
|
||||
and not str(v).startswith("<module"):
|
||||
print "\"%s\" is \"%s\"" % (n, v)
|
||||
print
|
||||
print "Welcome to the Gerrit Inspector"
|
||||
print "Enter help() to see the above again, EOF to quit and stop Gerrit"
|
||||
|
||||
help()
|
||||
Reference in New Issue
Block a user