passenger 5.3.4 → 5.3.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +13 -0
- data/CODE_OF_CONDUCT.md +1 -1
- data/build/cxx_tests.rb +12 -1
- data/build/misc.rb +2 -1
- data/build/packaging.rb +2 -0
- data/build/support/cplusplus.rb +2 -2
- data/build/support/cxx_dependency_map.rb +653 -383
- data/dev/configkit-schemas/index.json +105 -3
- data/dev/show-latest-crashlog-dir +27 -0
- data/resources/templates/standalone/http.erb +2 -0
- data/src/agent/Core/AdminPanelConnector.h +2 -2
- data/src/agent/Core/ApplicationPool/Context.h +5 -1
- data/src/agent/Core/ApplicationPool/Group.h +2 -0
- data/src/agent/Core/ApplicationPool/Group/LifetimeAndBasics.cpp +5 -0
- data/src/agent/Core/ApplicationPool/Group/Miscellaneous.cpp +2 -1
- data/src/agent/Core/ApplicationPool/Group/ProcessListManagement.cpp +1 -1
- data/src/agent/Core/ApplicationPool/Group/StateInspection.cpp +12 -19
- data/src/agent/Core/ApplicationPool/Options.h +35 -31
- data/src/agent/Core/ApplicationPool/Pool/GroupUtils.cpp +2 -1
- data/src/agent/Core/ApplicationPool/Socket.h +1 -1
- data/src/agent/Core/Config.h +38 -7
- data/src/agent/Core/ConfigChange.cpp +13 -1
- data/src/agent/Core/Controller.h +3 -1
- data/src/agent/Core/Controller/Config.h +14 -11
- data/src/agent/Core/Controller/InitRequest.cpp +6 -5
- data/src/agent/Core/Controller/InitializationAndShutdown.cpp +3 -0
- data/src/agent/Core/CoreMain.cpp +149 -34
- data/src/agent/Core/OptionParser.h +12 -1
- data/src/agent/Core/SpawningKit/Config.h +1 -1
- data/src/agent/Core/SpawningKit/Context.h +7 -1
- data/src/agent/Core/SpawningKit/Exceptions.h +15 -12
- data/src/agent/Core/SpawningKit/README.md +34 -17
- data/src/agent/Core/SpawningKit/Spawner.h +5 -3
- data/src/agent/Core/SpawningKit/UserSwitchingRules.h +5 -2
- data/src/agent/Core/TelemetryCollector.h +674 -0
- data/src/agent/Shared/Fundamentals/AbortHandler.cpp +309 -83
- data/src/agent/Shared/Fundamentals/AbortHandler.h +18 -3
- data/src/agent/Watchdog/Config.h +21 -4
- data/src/agent/Watchdog/WatchdogMain.cpp +4 -1
- data/src/apache2_module/ConfigGeneral/AutoGeneratedDefinitions.cpp +10 -0
- data/src/apache2_module/ConfigGeneral/AutoGeneratedManifestDefaultsInitialization.cpp +5 -0
- data/src/apache2_module/ConfigGeneral/AutoGeneratedSetterFuncs.cpp +30 -0
- data/src/apache2_module/DirectoryMapper.h +24 -36
- data/src/apache2_module/Hooks.cpp +13 -5
- data/src/apache2_module/ServerConfig/AutoGeneratedManifestGeneration.cpp +20 -0
- data/src/apache2_module/ServerConfig/AutoGeneratedStruct.h +24 -0
- data/src/cxx_supportlib/AppTypeDetector/CBindings.cpp +136 -0
- data/src/cxx_supportlib/AppTypeDetector/CBindings.h +73 -0
- data/src/cxx_supportlib/{AppTypes.h → AppTypeDetector/Detector.h} +59 -132
- data/src/cxx_supportlib/ConfigKit/README.md +90 -2
- data/src/cxx_supportlib/ConfigKit/Schema.h +58 -13
- data/src/cxx_supportlib/ConfigKit/Store.h +128 -4
- data/src/cxx_supportlib/Constants.h +1 -1
- data/src/cxx_supportlib/ProcessManagement/Ruby.cpp +3 -3
- data/src/cxx_supportlib/ProcessManagement/Ruby.h +7 -2
- data/src/cxx_supportlib/ProcessManagement/Spawn.cpp +14 -7
- data/src/cxx_supportlib/ProcessManagement/Spawn.h +21 -2
- data/src/cxx_supportlib/ResourceLocator.h +1 -1
- data/src/cxx_supportlib/ServerKit/ClientRef.h +17 -7
- data/src/cxx_supportlib/ServerKit/HttpRequestRef.h +17 -7
- data/src/cxx_supportlib/Utils/IOUtils.cpp +2 -1
- data/src/cxx_supportlib/Utils/ProcessMetricsCollector.h +9 -6
- data/src/cxx_supportlib/WrapperRegistry/CBindings.cpp +85 -0
- data/src/cxx_supportlib/WrapperRegistry/CBindings.h +56 -0
- data/src/cxx_supportlib/WrapperRegistry/Entry.h +112 -0
- data/src/cxx_supportlib/WrapperRegistry/README.md +37 -0
- data/src/cxx_supportlib/WrapperRegistry/Registry.h +309 -0
- data/src/helper-scripts/download_binaries/extconf.rb +6 -2
- data/src/nginx_module/ConfigGeneral/AutoGeneratedDefinitions.c +16 -0
- data/src/nginx_module/ConfigGeneral/AutoGeneratedManifestDefaultsInitialization.c +6 -0
- data/src/nginx_module/ConfigGeneral/AutoGeneratedSetterFuncs.c +24 -0
- data/src/nginx_module/ContentHandler.c +34 -13
- data/src/nginx_module/ContentHandler.h +3 -3
- data/src/nginx_module/MainConfig/AutoGeneratedCreateFunction.c +11 -0
- data/src/nginx_module/MainConfig/AutoGeneratedManifestGeneration.c +23 -0
- data/src/nginx_module/MainConfig/AutoGeneratedStruct.h +8 -0
- data/src/nginx_module/config +2 -1
- data/src/nginx_module/ngx_http_passenger_module.c +9 -3
- data/src/nginx_module/ngx_http_passenger_module.h +4 -2
- data/src/ruby_supportlib/phusion_passenger.rb +2 -1
- data/src/ruby_supportlib/phusion_passenger/apache2/config_options.rb +13 -0
- data/src/ruby_supportlib/phusion_passenger/common_library.rb +8 -5
- data/src/ruby_supportlib/phusion_passenger/config/download_agent_command.rb +6 -2
- data/src/ruby_supportlib/phusion_passenger/config/download_nginx_engine_command.rb +6 -2
- data/src/ruby_supportlib/phusion_passenger/native_support.rb +7 -3
- data/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb +15 -0
- data/src/ruby_supportlib/phusion_passenger/standalone/config_options_list.rb +11 -1
- data/src/ruby_supportlib/phusion_passenger/standalone/start_command/builtin_engine.rb +3 -1
- metadata +12 -4
- data/src/cxx_supportlib/AppTypes.cpp +0 -109
@@ -94,7 +94,12 @@ coreUsage() {
|
|
94
94
|
printf(" Disable the periodic check and notice about\n");
|
95
95
|
printf(" important security updates\n");
|
96
96
|
printf(" --security-update-check-proxy PROXY\n");
|
97
|
-
printf(" Use
|
97
|
+
printf(" Use HTTP/SOCKS proxy for the security update check:\n");
|
98
|
+
printf(" scheme://user:password@proxy_host:proxy_port\n");
|
99
|
+
printf(" --disable-anonymous-telemetry\n");
|
100
|
+
printf(" Disable anonymous telemetry collection\n");
|
101
|
+
printf(" --anonymous-telemetry-proxy PROXY\n");
|
102
|
+
printf(" Use HTTP/SOCKS proxy for anonymous telemetry sending:\n");
|
98
103
|
printf(" scheme://user:password@proxy_host:proxy_port\n");
|
99
104
|
printf("\n");
|
100
105
|
printf("Application serving options (optional):\n");
|
@@ -282,6 +287,12 @@ parseCoreOption(int argc, const char *argv[], int &i, Json::Value &updates) {
|
|
282
287
|
} else if (p.isValueFlag(argc, i, argv[i], '\0', "--security-update-check-proxy")) {
|
283
288
|
updates["security_update_checker_proxy_url"] = argv[i + 1];
|
284
289
|
i += 2;
|
290
|
+
} else if (p.isFlag(argv[i], '\0', "--disable-anonymous-telemetry")) {
|
291
|
+
updates["telemetry_collector_disabled"] = true;
|
292
|
+
i++;
|
293
|
+
} else if (p.isValueFlag(argc, i, argv[i], '\0', "--anonymous-telemetry-proxy")) {
|
294
|
+
updates["telemetry_collector_proxy_url"] = argv[i + 1];
|
295
|
+
i += 2;
|
285
296
|
} else if (p.isValueFlag(argc, i, argv[i], '\0', "--max-pool-size")) {
|
286
297
|
updates["max_pool_size"] = atoi(argv[i + 1]);
|
287
298
|
i += 2;
|
@@ -221,7 +221,7 @@ public:
|
|
221
221
|
StaticString processTitle;
|
222
222
|
|
223
223
|
/**
|
224
|
-
* An application type name, e.g. "
|
224
|
+
* An application type name, e.g. "ruby" or "nodejs". The only use for this
|
225
225
|
* in SpawningKit is to better format error messages.
|
226
226
|
*
|
227
227
|
* @hinted_parseable
|
@@ -36,6 +36,7 @@
|
|
36
36
|
#include <ResourceLocator.h>
|
37
37
|
#include <RandomGenerator.h>
|
38
38
|
#include <Exceptions.h>
|
39
|
+
#include <WrapperRegistry/Registry.h>
|
39
40
|
#include <Utils/JsonUtils.h>
|
40
41
|
#include <ConfigKit/Store.h>
|
41
42
|
|
@@ -132,7 +133,8 @@ private:
|
|
132
133
|
public:
|
133
134
|
/****** Dependencies ******/
|
134
135
|
|
135
|
-
ResourceLocator *resourceLocator;
|
136
|
+
const ResourceLocator *resourceLocator;
|
137
|
+
const WrapperRegistry::Registry *wrapperRegistry;
|
136
138
|
RandomGeneratorPtr randomGenerator;
|
137
139
|
string integrationMode;
|
138
140
|
string instanceDir;
|
@@ -147,6 +149,7 @@ public:
|
|
147
149
|
nextPort(0),
|
148
150
|
|
149
151
|
resourceLocator(NULL),
|
152
|
+
wrapperRegistry(NULL),
|
150
153
|
debugSupport(NULL)
|
151
154
|
{
|
152
155
|
vector<ConfigKit::Error> errors;
|
@@ -185,6 +188,9 @@ public:
|
|
185
188
|
if (resourceLocator == NULL) {
|
186
189
|
throw RuntimeException("ResourceLocator not initialized");
|
187
190
|
}
|
191
|
+
if (wrapperRegistry == NULL) {
|
192
|
+
throw RuntimeException("WrapperRegistry not initialized");
|
193
|
+
}
|
188
194
|
if (randomGenerator == NULL) {
|
189
195
|
randomGenerator = boost::make_shared<RandomGenerator>();
|
190
196
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/*
|
2
2
|
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
-
* Copyright (c) 2011-
|
3
|
+
* Copyright (c) 2011-2018 Phusion Holding B.V.
|
4
4
|
*
|
5
5
|
* "Passenger", "Phusion Passenger" and "Union Station" are registered
|
6
6
|
* trademarks of Phusion Holding B.V.
|
@@ -29,6 +29,7 @@
|
|
29
29
|
#include <oxt/tracable_exception.hpp>
|
30
30
|
#include <string>
|
31
31
|
#include <stdexcept>
|
32
|
+
#include <limits>
|
32
33
|
|
33
34
|
#include <Constants.h>
|
34
35
|
#include <Exceptions.h>
|
@@ -602,7 +603,7 @@ private:
|
|
602
603
|
" scripts.</p>");
|
603
604
|
break;
|
604
605
|
case SUBPROCESS_APP_LOAD_OR_EXEC:
|
605
|
-
if (config.appType == "
|
606
|
+
if (config.appType == "nodejs") {
|
606
607
|
message.append(
|
607
608
|
"<h3>Check whether the application calls <code>http.Server.listen()</code></h3>"
|
608
609
|
"<p>" SHORT_PROGRAM_NAME " requires that the application calls"
|
@@ -736,12 +737,13 @@ private:
|
|
736
737
|
const char *command[] = { "/bin/sh", "-c", "ulimit -a", NULL };
|
737
738
|
try {
|
738
739
|
SubprocessInfo info;
|
739
|
-
|
740
|
-
runCommandAndCaptureOutput(command, info,
|
741
|
-
|
742
|
-
|
740
|
+
SubprocessOutput output;
|
741
|
+
runCommandAndCaptureOutput(command, info, output,
|
742
|
+
std::numeric_limits<size_t>::max());
|
743
|
+
if (output.data.empty()) {
|
744
|
+
output.data.assign("Error: command 'ulimit -a' failed");
|
743
745
|
}
|
744
|
-
return
|
746
|
+
return output.data;
|
745
747
|
} catch (const SystemException &e) {
|
746
748
|
return P_STATIC_STRING("Error: command 'ulimit -a' failed: ") + e.what();
|
747
749
|
}
|
@@ -751,12 +753,13 @@ private:
|
|
751
753
|
const char *command[] = { "id", "-a", NULL };
|
752
754
|
try {
|
753
755
|
SubprocessInfo info;
|
754
|
-
|
755
|
-
runCommandAndCaptureOutput(command, info,
|
756
|
-
|
757
|
-
|
756
|
+
SubprocessOutput output;
|
757
|
+
runCommandAndCaptureOutput(command, info, output,
|
758
|
+
std::numeric_limits<size_t>::max());
|
759
|
+
if (output.data.empty()) {
|
760
|
+
output.data.assign("Error: command 'id -a' failed");
|
758
761
|
}
|
759
|
-
return
|
762
|
+
return output.data;
|
760
763
|
} catch (const SystemException &e) {
|
761
764
|
return P_STATIC_STRING("Error: command 'id -a' failed: ") + e.what();
|
762
765
|
}
|
@@ -6,7 +6,7 @@ Spawning an application process is complex, involving many steps and with many f
|
|
6
6
|
|
7
7
|
Here is how SpawningKit is used. The caller supplies various parameters such as where the application is located, what language it's written in, what environment variables to apply, etc. SpawningKit then spawns the application process, checks whether the application spawned properly or whether it encountered an error, and then either returns an object that describes the resulting process or throws an exception that describes the failure.
|
8
8
|
|
9
|
-
Reliability and visibility are core features
|
9
|
+
Reliability and visibility are core features of SpawningKit. When SpawningKit returns, you know for sure whether the process started correctly or not. If the application did not start correctly, then the resulting exception describes the failure in a detailed enough manner that allows users to pinpoint the source of the problem. SpawningKit also enforces timeouts everywhere so that stuck processes are handled as well.
|
10
10
|
|
11
11
|
**Table of contents**:
|
12
12
|
|
@@ -17,6 +17,13 @@ Reliability and visibility are core features in SpawningKit. When SpawningKit re
|
|
17
17
|
- The start command
|
18
18
|
- Summary with examples
|
19
19
|
* API and implementation highlights
|
20
|
+
- Context
|
21
|
+
- Spawners (high-level API)
|
22
|
+
- HandshakePrepare and HandshakePerform (low-level API)
|
23
|
+
- Configuration object
|
24
|
+
- Exception object
|
25
|
+
- Journey
|
26
|
+
- ErrorRenderer
|
20
27
|
* Overview of the spawning journey
|
21
28
|
- When spawning a process without a preloader
|
22
29
|
- When starting a preloader
|
@@ -42,19 +49,39 @@ Reliability and visibility are core features in SpawningKit. When SpawningKit re
|
|
42
49
|
|
43
50
|
### Generic vs SpawningKit-enabled applications
|
44
51
|
|
52
|
+
All applications
|
53
|
+
|
|
54
|
+
+-- Generic applications
|
55
|
+
| (without explicit SpawningKit support; `genericApp = true`)
|
56
|
+
|
|
57
|
+
+-- SpawningKit-enabled applications
|
58
|
+
(with explicit SpawningKit support; `genericApp = false`)
|
59
|
+
|
|
60
|
+
+-- Applications with SpawningKit support automatically injected
|
61
|
+
| through a "wrapper"; no manual modifications
|
62
|
+
| (`startsUsingWrapper = true`)
|
63
|
+
|
|
64
|
+
+-- Applications manually modified with SpawningKit support
|
65
|
+
(`startsUsingWrapper = false`)
|
66
|
+
|
45
67
|
SpawningKit can be used to spawn any web application, both those with and without explicit SpawningKit support.
|
46
68
|
|
69
|
+
> A generic application corresponds to setting the SpawningKit config `genericApp = true`.
|
70
|
+
|
47
71
|
When SpawningKit is used to spawn a generic application (without explicit SpawningKit support), the only requirement is that the application can be instructed to start and to listen on a specific TCP port on localhost. The user needs to specify a command string that tells SpawningKit how that is to be done. SpawningKit then looks for a free port that the application may use and executes the application using the supplied command string, telling it to listen on that specific port. (This approach is inspired by Heroku's Procfile system.) SpawningKit waits until the application is up by pinging the port. If the application fails (e.g. by terminating early or by not responding to pings in time) then SpawningKit will abort, reporting the application's stdout and stderr output.
|
48
72
|
|
73
|
+
> A SpawningKit-enabled application corresponds to setting the SpawningKit config `genericApp = false`.
|
74
|
+
|
49
75
|
Applications can also be modified with explicit SpawningKit support. Such applications can improve performance by telling SpawningKit that it wishes to listen on a Unix domain socket instead of a TCP socket; and they can provide more feedback about any spawning failures, such as with HTML-formatted error messages or by providing more information about where internally in the application or web framework the failure occurred.
|
50
76
|
|
51
77
|
### Wrappers
|
52
78
|
|
53
|
-
|
79
|
+
As we said, apps with explicit SpawningKit support is preferred (nicer experience, better performance). There are two ways to add SpawningKit support to an app:
|
54
80
|
|
55
|
-
|
81
|
+
1. By manually modifying the application's code to add SpawningKit support.
|
82
|
+
2. By automatically injecting SpawningKit support into the app, without any manual code modifications.
|
56
83
|
|
57
|
-
|
84
|
+
Option 2 is the most desirable, and is available to apps written in interpreted languages. This works by executing the application through a *wrapper* instead of directly. The wrapper, which is typically written in the same language as the app, loads the application and injects SpawningKit support.
|
58
85
|
|
59
86
|
Passenger comes with a few wrappers for specific languages, but SpawningKit itself is more generic and requires the caller to specify which wrapper to use (if at all).
|
60
87
|
|
@@ -86,15 +113,7 @@ Using the preforking technique through SpawningKit requires either application c
|
|
86
113
|
|
87
114
|
### The start command
|
88
115
|
|
89
|
-
Regardless of whether SpawningKit is used to spawn an application with or without explicit SpawningKit support, and regardless of whether a wrapper is used and whether the application/wrapper can function as a preloader, SpawningKit asks the caller to supply a "start command" that tells it how to execute the wrapper or the application. SpawningKit then uses the handshaking procedure (see: "Overview of the spawning journey") to communicate with the wrapper/application whether it should start in preloader mode or not.
|
90
|
-
|
91
|
-
### Summary with examples
|
92
|
-
|
93
|
-
To help you better understand the concepts, the following summarizes some of the above concepts and how they map to supportable languages.
|
94
|
-
|
95
|
-
SpawningKit-enabled wrappers are included in Passenger for these languages: Ruby, Python, Node.js, Meteor, and Perl.
|
96
|
-
|
97
|
-
Any existing app that accepts http requests can be used by writing a wrapper, and any new app can be written with SpawningKit compatibility to avoid the need for a wrapper.
|
116
|
+
Regardless of whether SpawningKit is used to spawn an application directly with or without explicit SpawningKit support, and regardless of whether a wrapper is used and whether the application/wrapper can function as a preloader, SpawningKit asks the caller to supply a "start command" that tells it how to execute the wrapper or the application. SpawningKit then uses the handshaking procedure (see: "Overview of the spawning journey") to communicate with the wrapper/application whether it should start in preloader mode or not.
|
98
117
|
|
99
118
|
|
100
119
|
## API and implementation highlights
|
@@ -114,7 +133,7 @@ context.integrationMode = "standalone";
|
|
114
133
|
context.finalize();
|
115
134
|
~~~
|
116
135
|
|
117
|
-
### Spawners
|
136
|
+
### Spawners (high-level API)
|
118
137
|
|
119
138
|
Use Spawners to spawn application processes. There are two main types of Spawners:
|
120
139
|
|
@@ -144,7 +163,7 @@ P_WARN("Application process spawned, PID is " << result.pid);
|
|
144
163
|
|
145
164
|
There is also a DummySpawner class, which is only used during unit tests.
|
146
165
|
|
147
|
-
### HandshakePrepare and HandshakePerform
|
166
|
+
### HandshakePrepare and HandshakePerform (low-level API)
|
148
167
|
|
149
168
|
Inside SmartSpawner and DirectSpawner, HandshakePrepare and HandshakePerform are used to perform a lot of the heavy lifting. See "Overview of the spawning journey" -- HandshakePrepare and HandshakePerform are responsible for most of the stuff described there.
|
150
169
|
|
@@ -152,8 +171,6 @@ In fact, DirectSpawner is just a thin wrapper around HandshakePrepare and Handsh
|
|
152
171
|
|
153
172
|
SmartSpawner is a bit bigger because it needs to implement the whole preloading mechanism (see section "Preloaders"), but it still uses HandshakePrepare and HandshakePerform to spawn the preloader, and to negotiate with the subprocess created by the preloader.
|
154
173
|
|
155
|
-
Here are some simplified interaction diagrams.
|
156
|
-
|
157
174
|
### Configuration object
|
158
175
|
|
159
176
|
HandshakePrepare and HandshakePerform do not accept an ApplicationPool::Options object, but a SpawningKit::Config object. It contains the configuration that HandshakePrepare/Perform need to perform a single spawn. SmartSpawner and DirectSpawner internally convert an ApplicationPool::Options into a SpawningKit::Config.
|
@@ -79,7 +79,8 @@ protected:
|
|
79
79
|
void setConfigFromAppPoolOptions(Config *config, Json::Value &extraArgs,
|
80
80
|
const AppPoolOptions &options)
|
81
81
|
{
|
82
|
-
string startCommand = options.getStartCommand(*context->resourceLocator
|
82
|
+
string startCommand = options.getStartCommand(*context->resourceLocator,
|
83
|
+
*context->wrapperRegistry);
|
83
84
|
string envvarsData;
|
84
85
|
try {
|
85
86
|
envvarsData = modp::b64_decode(options.environmentVariables.data(),
|
@@ -99,7 +100,7 @@ protected:
|
|
99
100
|
config->findFreePort = false;
|
100
101
|
config->loadShellEnvvars = options.loadShellEnvvars;
|
101
102
|
config->startCommand = startCommand;
|
102
|
-
config->startupFile = options.getStartupFile();
|
103
|
+
config->startupFile = options.getStartupFile(*context->wrapperRegistry);
|
103
104
|
config->appType = options.appType;
|
104
105
|
config->appEnv = options.environment;
|
105
106
|
config->baseURI = options.baseURI;
|
@@ -112,7 +113,8 @@ protected:
|
|
112
113
|
config->fileDescriptorUlimit = options.fileDescriptorUlimit;
|
113
114
|
config->startTimeoutMsec = options.startTimeout;
|
114
115
|
|
115
|
-
UserSwitchingInfo info = prepareUserSwitching(options
|
116
|
+
UserSwitchingInfo info = prepareUserSwitching(options,
|
117
|
+
*context->wrapperRegistry);
|
116
118
|
config->user = info.username;
|
117
119
|
config->group = info.groupname;
|
118
120
|
|
@@ -35,6 +35,7 @@
|
|
35
35
|
#include <boost/shared_array.hpp>
|
36
36
|
#include <oxt/backtrace.hpp>
|
37
37
|
#include <oxt/system_calls.hpp>
|
38
|
+
#include <WrapperRegistry/Registry.h>
|
38
39
|
#include <Exceptions.h>
|
39
40
|
#include <Utils.h>
|
40
41
|
#include <Core/SpawningKit/Context.h>
|
@@ -61,7 +62,9 @@ struct UserSwitchingInfo {
|
|
61
62
|
};
|
62
63
|
|
63
64
|
inline UserSwitchingInfo
|
64
|
-
prepareUserSwitching(const AppPoolOptions &options
|
65
|
+
prepareUserSwitching(const AppPoolOptions &options,
|
66
|
+
const WrapperRegistry::Registry &wrapperRegistry)
|
67
|
+
{
|
65
68
|
TRACE_POINT();
|
66
69
|
UserSwitchingInfo info;
|
67
70
|
|
@@ -95,7 +98,7 @@ prepareUserSwitching(const AppPoolOptions &options) {
|
|
95
98
|
|
96
99
|
UPDATE_TRACE_POINT();
|
97
100
|
string defaultGroup;
|
98
|
-
string startupFile = absolutizePath(options.getStartupFile(),
|
101
|
+
string startupFile = absolutizePath(options.getStartupFile(wrapperRegistry),
|
99
102
|
absolutizePath(options.appRoot));
|
100
103
|
struct passwd &pwd = info.lveUserPwd;
|
101
104
|
boost::shared_array<char> &pwdBuf = info.lveUserPwdStrBuf;
|
@@ -0,0 +1,674 @@
|
|
1
|
+
/*
|
2
|
+
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
+
* Copyright (c) 2018 Phusion Holding B.V.
|
4
|
+
*
|
5
|
+
* "Passenger", "Phusion Passenger" and "Union Station" are registered
|
6
|
+
* trademarks of Phusion Holding B.V.
|
7
|
+
*
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
* of this software and associated documentation files (the "Software"), to deal
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
13
|
+
* furnished to do so, subject to the following conditions:
|
14
|
+
*
|
15
|
+
* The above copyright notice and this permission notice shall be included in
|
16
|
+
* all copies or substantial portions of the Software.
|
17
|
+
*
|
18
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
24
|
+
* THE SOFTWARE.
|
25
|
+
*/
|
26
|
+
#ifndef _PASSENGER_TELEMETRY_COLLECTOR_H_
|
27
|
+
#define _PASSENGER_TELEMETRY_COLLECTOR_H_
|
28
|
+
|
29
|
+
#include <string>
|
30
|
+
#include <vector>
|
31
|
+
#include <limits>
|
32
|
+
#include <cstddef>
|
33
|
+
#include <cstdlib>
|
34
|
+
#include <cassert>
|
35
|
+
|
36
|
+
#include <boost/cstdint.hpp>
|
37
|
+
#include <boost/bind.hpp>
|
38
|
+
#include <oxt/thread.hpp>
|
39
|
+
#include <oxt/backtrace.hpp>
|
40
|
+
|
41
|
+
#include <curl/curl.h>
|
42
|
+
|
43
|
+
#include <Constants.h>
|
44
|
+
#include <Exceptions.h>
|
45
|
+
#include <Core/Controller.h>
|
46
|
+
#include <LoggingKit/LoggingKit.h>
|
47
|
+
#include <ConfigKit/ConfigKit.h>
|
48
|
+
#include <Utils/Curl.h>
|
49
|
+
#include <Utils/StrIntUtils.h>
|
50
|
+
|
51
|
+
namespace Passenger {
|
52
|
+
namespace Core {
|
53
|
+
|
54
|
+
using namespace std;
|
55
|
+
|
56
|
+
|
57
|
+
class TelemetryCollector {
|
58
|
+
public:
|
59
|
+
/*
|
60
|
+
* BEGIN ConfigKit schema: Passenger::TelemetryCollector::Schema
|
61
|
+
* (do not edit: following text is automatically generated
|
62
|
+
* by 'rake configkit_schemas_inline_comments')
|
63
|
+
*
|
64
|
+
* (unknown: Passenger::TelemetryCollector::Schema not in dev/configkit-schemas/index.json
|
65
|
+
* Please run:
|
66
|
+
* touch src/schema_printer/SchemaPrinterMain.cpp.cxxcodebuilder
|
67
|
+
* rake configkit_schemas_inline_comments
|
68
|
+
* )
|
69
|
+
*
|
70
|
+
* END
|
71
|
+
*/
|
72
|
+
class Schema: public ConfigKit::Schema {
|
73
|
+
private:
|
74
|
+
static void validateProxyUrl(const ConfigKit::Store &config, vector<ConfigKit::Error> &errors) {
|
75
|
+
if (config["proxy_url"].isNull()) {
|
76
|
+
return;
|
77
|
+
}
|
78
|
+
if (config["proxy_url"].asString().empty()) {
|
79
|
+
errors.push_back(ConfigKit::Error("'{{proxy_url}}', if specified, may not be empty"));
|
80
|
+
return;
|
81
|
+
}
|
82
|
+
|
83
|
+
try {
|
84
|
+
prepareCurlProxy(config["proxy_url"].asString());
|
85
|
+
} catch (const ArgumentException &e) {
|
86
|
+
errors.push_back(ConfigKit::Error(
|
87
|
+
P_STATIC_STRING("'{{proxy_url}}': ")
|
88
|
+
+ e.what()));
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
public:
|
93
|
+
Schema() {
|
94
|
+
using namespace ConfigKit;
|
95
|
+
|
96
|
+
add("disabled", BOOL_TYPE, OPTIONAL, false);
|
97
|
+
add("url", STRING_TYPE, OPTIONAL, "https://anontelemetry.phusionpassenger.com/v1/collect.json");
|
98
|
+
// Should be in the form: scheme://user:password@proxy_host:proxy_port
|
99
|
+
add("proxy_url", STRING_TYPE, OPTIONAL);
|
100
|
+
add("ca_certificate_path", STRING_TYPE, OPTIONAL);
|
101
|
+
add("verify_server", BOOL_TYPE, OPTIONAL, true);
|
102
|
+
add("first_interval", UINT_TYPE, OPTIONAL, 2 * 60 * 60);
|
103
|
+
add("interval", UINT_TYPE, OPTIONAL, 6 * 60 * 60);
|
104
|
+
add("interval_jitter", UINT_TYPE, OPTIONAL, 2 * 60 * 60);
|
105
|
+
add("debug_curl", BOOL_TYPE, OPTIONAL, false);
|
106
|
+
add("timeout", UINT_TYPE, OPTIONAL, 180);
|
107
|
+
add("final_run_timeout", UINT_TYPE, OPTIONAL, 5);
|
108
|
+
|
109
|
+
addValidator(validateProxyUrl);
|
110
|
+
|
111
|
+
finalize();
|
112
|
+
}
|
113
|
+
};
|
114
|
+
|
115
|
+
struct ConfigRealization {
|
116
|
+
CurlProxyInfo proxyInfo;
|
117
|
+
string url;
|
118
|
+
string caCertificatePath;
|
119
|
+
|
120
|
+
ConfigRealization(const ConfigKit::Store &config)
|
121
|
+
: proxyInfo(prepareCurlProxy(config["proxy_url"].asString())),
|
122
|
+
url(config["url"].asString()),
|
123
|
+
caCertificatePath(config["ca_certificate_path"].asString())
|
124
|
+
{ }
|
125
|
+
|
126
|
+
void swap(ConfigRealization &other) BOOST_NOEXCEPT_OR_NOTHROW {
|
127
|
+
proxyInfo.swap(other.proxyInfo);
|
128
|
+
url.swap(other.url);
|
129
|
+
caCertificatePath.swap(other.caCertificatePath);
|
130
|
+
}
|
131
|
+
};
|
132
|
+
|
133
|
+
struct ConfigChangeRequest {
|
134
|
+
boost::scoped_ptr<ConfigKit::Store> config;
|
135
|
+
boost::scoped_ptr<ConfigRealization> configRlz;
|
136
|
+
};
|
137
|
+
|
138
|
+
struct TelemetryData {
|
139
|
+
vector<boost::uint64_t> requestsHandled;
|
140
|
+
MonotonicTimeUsec timestamp;
|
141
|
+
};
|
142
|
+
|
143
|
+
private:
|
144
|
+
/*
|
145
|
+
* Since the telemetry collector runs in a separate thread,
|
146
|
+
* and the configuration can change while the collector is active,
|
147
|
+
* we make a copy of the current configuration at the beginning
|
148
|
+
* of each collection cycle.
|
149
|
+
*/
|
150
|
+
struct SessionState {
|
151
|
+
ConfigKit::Store config;
|
152
|
+
ConfigRealization configRlz;
|
153
|
+
|
154
|
+
SessionState(const ConfigKit::Store ¤tConfig,
|
155
|
+
const ConfigRealization ¤tConfigRlz)
|
156
|
+
: config(currentConfig),
|
157
|
+
configRlz(currentConfigRlz)
|
158
|
+
{ }
|
159
|
+
};
|
160
|
+
|
161
|
+
mutable boost::mutex configSyncher;
|
162
|
+
ConfigKit::Store config;
|
163
|
+
ConfigRealization configRlz;
|
164
|
+
TelemetryData lastTelemetryData;
|
165
|
+
oxt::thread *collectorThread;
|
166
|
+
|
167
|
+
void threadMain() {
|
168
|
+
TRACE_POINT();
|
169
|
+
|
170
|
+
{
|
171
|
+
// Sleep for a short while to allow interruption during the Apache integration
|
172
|
+
// double startup procedure, this prevents running the update check twice
|
173
|
+
boost::unique_lock<boost::mutex> l(configSyncher);
|
174
|
+
ConfigKit::Store config(this->config);
|
175
|
+
l.unlock();
|
176
|
+
|
177
|
+
unsigned int backoffSec = config["first_interval"].asUInt()
|
178
|
+
+ calculateIntervalJitter(config);
|
179
|
+
P_DEBUG("Next anonymous telemetry collection in " <<
|
180
|
+
distanceOfTimeInWords(SystemTime::get() + backoffSec));
|
181
|
+
boost::this_thread::sleep_for(boost::chrono::seconds(backoffSec));
|
182
|
+
}
|
183
|
+
|
184
|
+
while (!boost::this_thread::interruption_requested()) {
|
185
|
+
UPDATE_TRACE_POINT();
|
186
|
+
unsigned int backoffSec = 0;
|
187
|
+
try {
|
188
|
+
backoffSec = runOneCycle();
|
189
|
+
} catch (const oxt::tracable_exception &e) {
|
190
|
+
P_ERROR(e.what() << "\n" << e.backtrace());
|
191
|
+
}
|
192
|
+
|
193
|
+
if (backoffSec == 0) {
|
194
|
+
boost::unique_lock<boost::mutex> l(configSyncher);
|
195
|
+
backoffSec = config["interval"].asUInt()
|
196
|
+
+ calculateIntervalJitter(config);
|
197
|
+
}
|
198
|
+
|
199
|
+
UPDATE_TRACE_POINT();
|
200
|
+
P_DEBUG("Next anonymous telemetry collection in "
|
201
|
+
<< distanceOfTimeInWords(SystemTime::get() + backoffSec));
|
202
|
+
boost::this_thread::sleep_for(boost::chrono::seconds(backoffSec));
|
203
|
+
}
|
204
|
+
}
|
205
|
+
|
206
|
+
static unsigned int calculateIntervalJitter(const ConfigKit::Store &config) {
|
207
|
+
unsigned int jitter = config["interval_jitter"].asUInt();
|
208
|
+
if (jitter == 0) {
|
209
|
+
return 0;
|
210
|
+
} else {
|
211
|
+
return std::rand() % jitter;
|
212
|
+
}
|
213
|
+
}
|
214
|
+
|
215
|
+
// Virtual to allow mocking in unit tests.
|
216
|
+
virtual TelemetryData collectTelemetryData(bool isFinalRun) const {
|
217
|
+
TRACE_POINT();
|
218
|
+
TelemetryData tmData;
|
219
|
+
unsigned int counter = 0;
|
220
|
+
boost::mutex syncher;
|
221
|
+
boost::condition_variable cond;
|
222
|
+
|
223
|
+
tmData.requestsHandled.resize(controllers.size(), 0);
|
224
|
+
|
225
|
+
UPDATE_TRACE_POINT();
|
226
|
+
for (unsigned int i = 0; i < controllers.size(); i++) {
|
227
|
+
if (isFinalRun) {
|
228
|
+
inspectController(&tmData, controllers[i], i, &counter,
|
229
|
+
&syncher, &cond);
|
230
|
+
} else {
|
231
|
+
controllers[i]->getContext()->libev->runLater(boost::bind(
|
232
|
+
&TelemetryCollector::inspectController, this, &tmData,
|
233
|
+
controllers[i], i, &counter, &syncher, &cond));
|
234
|
+
}
|
235
|
+
}
|
236
|
+
|
237
|
+
UPDATE_TRACE_POINT();
|
238
|
+
{
|
239
|
+
boost::unique_lock<boost::mutex> l(syncher);
|
240
|
+
while (counter != controllers.size()) {
|
241
|
+
cond.wait(l);
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
tmData.timestamp = SystemTime::getMonotonicUsecWithGranularity
|
246
|
+
<SystemTime::GRAN_1SEC>();
|
247
|
+
return tmData;
|
248
|
+
}
|
249
|
+
|
250
|
+
void inspectController(TelemetryData *tmData, Controller *controller,
|
251
|
+
unsigned int index, unsigned int *counter, boost::mutex *syncher,
|
252
|
+
boost::condition_variable *cond) const
|
253
|
+
{
|
254
|
+
boost::unique_lock<boost::mutex> l(*syncher);
|
255
|
+
tmData->requestsHandled[index] = controller->totalRequestsBegun;
|
256
|
+
(*counter)++;
|
257
|
+
cond->notify_one();
|
258
|
+
}
|
259
|
+
|
260
|
+
string createRequestBody(const TelemetryData &tmData) const {
|
261
|
+
Json::Value doc;
|
262
|
+
boost::uint64_t totalRequestsHandled = 0;
|
263
|
+
|
264
|
+
P_ASSERT_EQ(tmData.requestsHandled.size(),
|
265
|
+
lastTelemetryData.requestsHandled.size());
|
266
|
+
|
267
|
+
for (unsigned int i = 0; i < tmData.requestsHandled.size(); i++) {
|
268
|
+
if (tmData.requestsHandled[i] >= lastTelemetryData.requestsHandled[i]) {
|
269
|
+
totalRequestsHandled += tmData.requestsHandled[i]
|
270
|
+
- lastTelemetryData.requestsHandled[i];
|
271
|
+
} else {
|
272
|
+
// Counter overflowed
|
273
|
+
totalRequestsHandled += std::numeric_limits<boost::uint64_t>::max()
|
274
|
+
- lastTelemetryData.requestsHandled[i]
|
275
|
+
+ 1
|
276
|
+
+ tmData.requestsHandled[i];
|
277
|
+
}
|
278
|
+
}
|
279
|
+
|
280
|
+
doc["requests_handled"] = (Json::UInt64) totalRequestsHandled;
|
281
|
+
doc["begin_time"] = (Json::UInt64) monoTimeToRealTime(
|
282
|
+
lastTelemetryData.timestamp);
|
283
|
+
doc["end_time"] = (Json::UInt64) monoTimeToRealTime(
|
284
|
+
tmData.timestamp);
|
285
|
+
#ifdef PASSENGER_IS_ENTERPRISE
|
286
|
+
doc["edition"] = "enterprise";
|
287
|
+
#else
|
288
|
+
doc["edition"] = "oss";
|
289
|
+
#endif
|
290
|
+
|
291
|
+
return doc.toStyledString();
|
292
|
+
}
|
293
|
+
|
294
|
+
static time_t monoTimeToRealTime(MonotonicTimeUsec monoTime) {
|
295
|
+
MonotonicTimeUsec monoNow = SystemTime::getMonotonicUsecWithGranularity
|
296
|
+
<SystemTime::GRAN_1SEC>();
|
297
|
+
unsigned long long realNow = SystemTime::getUsec();
|
298
|
+
MonotonicTimeUsec diff;
|
299
|
+
|
300
|
+
if (monoNow >= monoTime) {
|
301
|
+
diff = monoNow - monoTime;
|
302
|
+
return (realNow - diff) / 1000000;
|
303
|
+
} else {
|
304
|
+
diff = monoTime - monoNow;
|
305
|
+
return (realNow + diff) / 1000000;
|
306
|
+
}
|
307
|
+
}
|
308
|
+
|
309
|
+
static CURL *prepareCurlRequest(SessionState &sessionState, bool isFinalRun,
|
310
|
+
struct curl_slist **headers, char *lastErrorMessage,
|
311
|
+
const string &requestBody, string &responseData)
|
312
|
+
{
|
313
|
+
CURL *curl;
|
314
|
+
CURLcode code;
|
315
|
+
|
316
|
+
curl = curl_easy_init();
|
317
|
+
if (curl == NULL) {
|
318
|
+
P_ERROR("Error initializing libcurl");
|
319
|
+
return NULL;
|
320
|
+
}
|
321
|
+
|
322
|
+
code = curl_easy_setopt(curl, CURLOPT_VERBOSE,
|
323
|
+
sessionState.config["debug_curl"].asBool() ? 1L : 0L);
|
324
|
+
if (code != CURLE_OK) {
|
325
|
+
goto error;
|
326
|
+
}
|
327
|
+
|
328
|
+
code = setCurlDefaultCaInfo(curl);
|
329
|
+
if (code != CURLE_OK) {
|
330
|
+
goto error;
|
331
|
+
}
|
332
|
+
|
333
|
+
code = setCurlProxy(curl, sessionState.configRlz.proxyInfo);
|
334
|
+
if (code != CURLE_OK) {
|
335
|
+
goto error;
|
336
|
+
}
|
337
|
+
|
338
|
+
code = curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
|
339
|
+
if (code != CURLE_OK) {
|
340
|
+
goto error;
|
341
|
+
}
|
342
|
+
|
343
|
+
code = curl_easy_setopt(curl, CURLOPT_URL,
|
344
|
+
sessionState.configRlz.url.c_str());
|
345
|
+
if (code != CURLE_OK) {
|
346
|
+
goto error;
|
347
|
+
}
|
348
|
+
|
349
|
+
code = curl_easy_setopt(curl, CURLOPT_HTTPGET, 0);
|
350
|
+
if (code != CURLE_OK) {
|
351
|
+
goto error;
|
352
|
+
}
|
353
|
+
|
354
|
+
code = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, requestBody.c_str());
|
355
|
+
if (code != CURLE_OK) {
|
356
|
+
goto error;
|
357
|
+
}
|
358
|
+
|
359
|
+
code = curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, requestBody.length());
|
360
|
+
if (code != CURLE_OK) {
|
361
|
+
goto error;
|
362
|
+
}
|
363
|
+
|
364
|
+
*headers = curl_slist_append(NULL, "Content-Type: application/json");
|
365
|
+
code = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, *headers);
|
366
|
+
if (code != CURLE_OK) {
|
367
|
+
goto error;
|
368
|
+
}
|
369
|
+
|
370
|
+
if (!sessionState.configRlz.caCertificatePath.empty()) {
|
371
|
+
code = curl_easy_setopt(curl, CURLOPT_CAINFO,
|
372
|
+
sessionState.configRlz.caCertificatePath.c_str());
|
373
|
+
if (code != CURLE_OK) {
|
374
|
+
goto error;
|
375
|
+
}
|
376
|
+
}
|
377
|
+
|
378
|
+
if (sessionState.config["verify_server"].asBool()) {
|
379
|
+
// These should be on by default, but make sure.
|
380
|
+
code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
|
381
|
+
if (code != CURLE_OK) {
|
382
|
+
goto error;
|
383
|
+
}
|
384
|
+
code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
|
385
|
+
if (code != CURLE_OK) {
|
386
|
+
goto error;
|
387
|
+
}
|
388
|
+
} else {
|
389
|
+
code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
390
|
+
if (code != CURLE_OK) {
|
391
|
+
goto error;
|
392
|
+
}
|
393
|
+
code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
394
|
+
if (code != CURLE_OK) {
|
395
|
+
goto error;
|
396
|
+
}
|
397
|
+
}
|
398
|
+
|
399
|
+
code = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, lastErrorMessage);
|
400
|
+
if (code != CURLE_OK) {
|
401
|
+
goto error;
|
402
|
+
}
|
403
|
+
code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveResponseBytes);
|
404
|
+
if (code != CURLE_OK) {
|
405
|
+
goto error;
|
406
|
+
}
|
407
|
+
code = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseData);
|
408
|
+
if (code != CURLE_OK) {
|
409
|
+
goto error;
|
410
|
+
}
|
411
|
+
|
412
|
+
// setopt failure(s) below don't abort the check.
|
413
|
+
if (isFinalRun) {
|
414
|
+
curl_easy_setopt(curl, CURLOPT_TIMEOUT,
|
415
|
+
sessionState.config["final_run_timeout"].asUInt());
|
416
|
+
} else {
|
417
|
+
curl_easy_setopt(curl, CURLOPT_TIMEOUT,
|
418
|
+
sessionState.config["timeout"].asUInt());
|
419
|
+
}
|
420
|
+
|
421
|
+
return curl;
|
422
|
+
|
423
|
+
error:
|
424
|
+
curl_easy_cleanup(curl);
|
425
|
+
curl_slist_free_all(*headers);
|
426
|
+
P_ERROR("Error setting libcurl handle parameters: " << curl_easy_strerror(code));
|
427
|
+
return NULL;
|
428
|
+
}
|
429
|
+
|
430
|
+
static size_t receiveResponseBytes(void *buffer, size_t size,
|
431
|
+
size_t nmemb, void *userData)
|
432
|
+
{
|
433
|
+
string *responseData = (string *) userData;
|
434
|
+
responseData->append((const char *) buffer, size * nmemb);
|
435
|
+
return size * nmemb;
|
436
|
+
}
|
437
|
+
|
438
|
+
// Virtual to allow mocking in unit tests.
|
439
|
+
virtual CURLcode performCurlAction(CURL *curl, const char *lastErrorMessage,
|
440
|
+
const string &_requestBody, // only used by unit tests
|
441
|
+
string &_responseData, // only used by unit tests
|
442
|
+
long &responseCode)
|
443
|
+
{
|
444
|
+
TRACE_POINT();
|
445
|
+
CURLcode code = curl_easy_perform(curl);
|
446
|
+
if (code != CURLE_OK) {
|
447
|
+
P_ERROR("Error contacting anonymous telemetry server: "
|
448
|
+
<< lastErrorMessage);
|
449
|
+
return code;
|
450
|
+
}
|
451
|
+
|
452
|
+
code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
|
453
|
+
if (code != CURLE_OK) {
|
454
|
+
P_ERROR("Error querying libcurl handle for HTTP response code: "
|
455
|
+
<< curl_easy_strerror(code));
|
456
|
+
return code;
|
457
|
+
}
|
458
|
+
|
459
|
+
return CURLE_OK;
|
460
|
+
}
|
461
|
+
|
462
|
+
static bool responseCodeSupported(long code) {
|
463
|
+
return code == 200 || code == 400 || code == 422 || code == 500;
|
464
|
+
}
|
465
|
+
|
466
|
+
static bool parseResponseBody(const string &responseData, Json::Value &jsonBody) {
|
467
|
+
Json::Reader reader;
|
468
|
+
if (reader.parse(responseData, jsonBody, false)) {
|
469
|
+
return true;
|
470
|
+
} else {
|
471
|
+
P_ERROR("Error in anonymous telemetry server response:"
|
472
|
+
" JSON response parse error: " << reader.getFormattedErrorMessages()
|
473
|
+
<< "; data: \"" << cEscapeString(responseData) << "\"");
|
474
|
+
return false;
|
475
|
+
}
|
476
|
+
}
|
477
|
+
|
478
|
+
static bool validateResponseBody(const Json::Value &jsonBody) {
|
479
|
+
if (!jsonBody.isObject()) {
|
480
|
+
P_ERROR("Error in anonymous telemetry server response:"
|
481
|
+
" JSON response is not an object (data: "
|
482
|
+
<< stringifyJson(jsonBody) << ")");
|
483
|
+
return false;
|
484
|
+
}
|
485
|
+
if (!jsonBody.isMember("data_processed")) {
|
486
|
+
P_ERROR("Error in anonymous telemetry server response:"
|
487
|
+
" JSON response must contain a 'data_processed' field (data: "
|
488
|
+
<< stringifyJson(jsonBody) << ")");
|
489
|
+
return false;
|
490
|
+
}
|
491
|
+
if (!jsonBody["data_processed"].isBool()) {
|
492
|
+
P_ERROR("Error in anonymous telemetry server response:"
|
493
|
+
" 'data_processed' field must be a boolean (data: "
|
494
|
+
<< stringifyJson(jsonBody) << ")");
|
495
|
+
return false;
|
496
|
+
}
|
497
|
+
if (jsonBody.isMember("backoff") && !jsonBody["backoff"].isUInt()) {
|
498
|
+
P_ERROR("Error in anonymous telemetry server response:"
|
499
|
+
" 'backoff' field must be an unsigned integer (data: "
|
500
|
+
<< stringifyJson(jsonBody) << ")");
|
501
|
+
return false;
|
502
|
+
}
|
503
|
+
if (jsonBody.isMember("log_message") && !jsonBody["log_message"].isString()) {
|
504
|
+
P_ERROR("Error in anonymous telemetry server response:"
|
505
|
+
" 'log_message' field must be a string (data: "
|
506
|
+
<< stringifyJson(jsonBody) << ")");
|
507
|
+
return false;
|
508
|
+
}
|
509
|
+
return true;
|
510
|
+
}
|
511
|
+
|
512
|
+
unsigned int handleResponseBody(const TelemetryData &tmData,
|
513
|
+
const Json::Value &jsonBody)
|
514
|
+
{
|
515
|
+
unsigned int backoffSec = 0;
|
516
|
+
|
517
|
+
if (jsonBody["data_processed"].asBool()) {
|
518
|
+
lastTelemetryData = tmData;
|
519
|
+
}
|
520
|
+
if (jsonBody.isMember("backoff")) {
|
521
|
+
backoffSec = jsonBody["backoff"].asUInt();
|
522
|
+
}
|
523
|
+
if (jsonBody.isMember("log_message")) {
|
524
|
+
P_NOTICE("Message from " PROGRAM_AUTHOR ": " << jsonBody["log_message"].asString());
|
525
|
+
}
|
526
|
+
|
527
|
+
return backoffSec;
|
528
|
+
}
|
529
|
+
|
530
|
+
public:
|
531
|
+
// Dependencies
|
532
|
+
vector<Controller *> controllers;
|
533
|
+
|
534
|
+
TelemetryCollector(const Schema &schema,
|
535
|
+
const Json::Value &initialConfig = Json::Value(),
|
536
|
+
const ConfigKit::Translator &translator = ConfigKit::DummyTranslator())
|
537
|
+
: config(schema, initialConfig, translator),
|
538
|
+
configRlz(config),
|
539
|
+
collectorThread(NULL)
|
540
|
+
{ }
|
541
|
+
|
542
|
+
virtual ~TelemetryCollector() {
|
543
|
+
stop();
|
544
|
+
}
|
545
|
+
|
546
|
+
void initialize() {
|
547
|
+
if (controllers.empty()) {
|
548
|
+
throw RuntimeException("controllers must be initialized");
|
549
|
+
}
|
550
|
+
lastTelemetryData.requestsHandled.resize(controllers.size(), 0);
|
551
|
+
lastTelemetryData.timestamp =
|
552
|
+
SystemTime::getMonotonicUsecWithGranularity<SystemTime::GRAN_1SEC>();
|
553
|
+
}
|
554
|
+
|
555
|
+
void start() {
|
556
|
+
assert(!lastTelemetryData.requestsHandled.empty());
|
557
|
+
collectorThread = new oxt::thread(
|
558
|
+
boost::bind(&TelemetryCollector::threadMain, this),
|
559
|
+
"Telemetry collector",
|
560
|
+
1024 * 512
|
561
|
+
);
|
562
|
+
}
|
563
|
+
|
564
|
+
void stop() {
|
565
|
+
if (collectorThread != NULL) {
|
566
|
+
collectorThread->interrupt_and_join();
|
567
|
+
delete collectorThread;
|
568
|
+
collectorThread = NULL;
|
569
|
+
}
|
570
|
+
}
|
571
|
+
|
572
|
+
unsigned int runOneCycle(bool isFinalRun = false) {
|
573
|
+
TRACE_POINT();
|
574
|
+
boost::unique_lock<boost::mutex> l(configSyncher);
|
575
|
+
SessionState sessionState(config, configRlz);
|
576
|
+
l.unlock();
|
577
|
+
|
578
|
+
if (sessionState.config["disabled"].asBool()) {
|
579
|
+
P_DEBUG("Telemetry collector disabled; not sending anonymous telemetry data");
|
580
|
+
return 0;
|
581
|
+
}
|
582
|
+
|
583
|
+
UPDATE_TRACE_POINT();
|
584
|
+
TelemetryData tmData = collectTelemetryData(isFinalRun);
|
585
|
+
|
586
|
+
UPDATE_TRACE_POINT();
|
587
|
+
CURL *curl = NULL;
|
588
|
+
CURLcode code;
|
589
|
+
struct curl_slist *headers = NULL;
|
590
|
+
string requestBody = createRequestBody(tmData);
|
591
|
+
string responseData;
|
592
|
+
char lastErrorMessage[CURL_ERROR_SIZE] = "unknown error";
|
593
|
+
Json::Value jsonBody;
|
594
|
+
|
595
|
+
curl = prepareCurlRequest(sessionState, isFinalRun, &headers,
|
596
|
+
lastErrorMessage, requestBody, responseData);
|
597
|
+
if (curl == NULL) {
|
598
|
+
// Error message already printed
|
599
|
+
goto error;
|
600
|
+
}
|
601
|
+
|
602
|
+
P_INFO("Sending anonymous telemetry data to " PROGRAM_AUTHOR);
|
603
|
+
P_DEBUG("Telemetry server URL is: " << sessionState.configRlz.url);
|
604
|
+
P_DEBUG("Telemetry data to be sent is: " << requestBody);
|
605
|
+
|
606
|
+
UPDATE_TRACE_POINT();
|
607
|
+
long responseCode;
|
608
|
+
code = performCurlAction(curl, lastErrorMessage, requestBody,
|
609
|
+
responseData, responseCode);
|
610
|
+
if (code != CURLE_OK) {
|
611
|
+
// Error message already printed
|
612
|
+
goto error;
|
613
|
+
}
|
614
|
+
|
615
|
+
UPDATE_TRACE_POINT();
|
616
|
+
P_DEBUG("Response from telemetry server: status=" << responseCode
|
617
|
+
<< ", body=" << responseData);
|
618
|
+
|
619
|
+
if (!responseCodeSupported(responseCode)) {
|
620
|
+
P_ERROR("Error from anonymous telemetry server:"
|
621
|
+
" response status not supported: " << responseCode);
|
622
|
+
goto error;
|
623
|
+
}
|
624
|
+
|
625
|
+
if (!parseResponseBody(responseData, jsonBody)
|
626
|
+
|| !validateResponseBody(jsonBody))
|
627
|
+
{
|
628
|
+
// Error message already printed
|
629
|
+
goto error;
|
630
|
+
}
|
631
|
+
|
632
|
+
curl_slist_free_all(headers);
|
633
|
+
curl_easy_cleanup(curl);
|
634
|
+
|
635
|
+
return handleResponseBody(tmData, jsonBody);
|
636
|
+
|
637
|
+
error:
|
638
|
+
curl_slist_free_all(headers);
|
639
|
+
if (curl != NULL) {
|
640
|
+
curl_easy_cleanup(curl);
|
641
|
+
}
|
642
|
+
return 0;
|
643
|
+
}
|
644
|
+
|
645
|
+
bool prepareConfigChange(const Json::Value &updates,
|
646
|
+
vector<ConfigKit::Error> &errors, ConfigChangeRequest &req)
|
647
|
+
{
|
648
|
+
{
|
649
|
+
boost::lock_guard<boost::mutex> l(configSyncher);
|
650
|
+
req.config.reset(new ConfigKit::Store(config, updates, errors));
|
651
|
+
}
|
652
|
+
if (errors.empty()) {
|
653
|
+
req.configRlz.reset(new ConfigRealization(*req.config));
|
654
|
+
}
|
655
|
+
return errors.empty();
|
656
|
+
}
|
657
|
+
|
658
|
+
void commitConfigChange(ConfigChangeRequest &req) BOOST_NOEXCEPT_OR_NOTHROW {
|
659
|
+
boost::lock_guard<boost::mutex> l(configSyncher);
|
660
|
+
config.swap(*req.config);
|
661
|
+
configRlz.swap(*req.configRlz);
|
662
|
+
}
|
663
|
+
|
664
|
+
Json::Value inspectConfig() const {
|
665
|
+
boost::lock_guard<boost::mutex> l(configSyncher);
|
666
|
+
return config.inspect();
|
667
|
+
}
|
668
|
+
};
|
669
|
+
|
670
|
+
|
671
|
+
} // namespace Core
|
672
|
+
} // namespace Passenger
|
673
|
+
|
674
|
+
#endif /* _PASSENGER_TELEMETRY_COLLECTOR_H_ */
|