diff --git a/doc/06-distributed-monitoring.md b/doc/06-distributed-monitoring.md
index 985e23fd9..8795335ed 100644
--- a/doc/06-distributed-monitoring.md
+++ b/doc/06-distributed-monitoring.md
@@ -116,8 +116,8 @@ you still need a [Host](09-object-types.md#objecttype-host) object.
In case you are using the CLI commands later, you don't have to write
this configuration from scratch in a text editor.
-The [ApiListener](09-object-types.md#objecttype-apilistener)
-object is used to load the SSL certificates and specify restrictions, e.g.
+The [ApiListener](09-object-types.md#objecttype-apilistener) object is
+used to load the TLS certificates and specify restrictions, e.g.
for accepting configuration commands.
It is also used for the [Icinga 2 REST API](12-icinga2-api.md#icinga2-api) which shares
@@ -2793,3 +2793,36 @@ Add the global zone `global-templates` in case it did not exist.
global = true
}
EOF
+
+## Using Multiple Environments
+
+In some cases it can be desired to run multiple Icinga instances on the same host.
+Two potential scenarios include:
+
+* Different versions of the same monitoring configuration (e.g. production and testing)
+* Disparate sets of checks for entirely unrelated monitoring environments (e.g. infrastructure and applications)
+
+The configuration is done with the global constants `ApiBindHost` and `ApiBindPort`
+or the `bind_host` and `bind_port` attributes of the
+[ApiListener](09-object-types.md#objecttype-apilistener) object.
+
+The environment must be set with the global constant `Environment` or as object attribute
+of the [IcingaApplication](#objecttype-icingaapplication) object.
+
+In any case the constant is default value for the attribute and the direct configuration in the objects
+have more precedence. The constants have been added to allow the values being set from the CLI on startup.
+
+When Icinga establishes a TLS connection to another cluster instance it automatically uses the [SNI extension](https://en.wikipedia.org/wiki/Server_Name_Indication)
+to signal which endpoint it is attempting to connect to. On its own this can already be used to position multiple
+Icinga instances behind a load balancer.
+
+SNI example: `icinga2-client1.localdomain`
+
+However, if the environment is configured to `production`, Icinga appends the environment name to the SNI hostname like this:
+
+SNI example with environment: `icinga2-client1.localdomain:production`
+
+Middleware like loadbalancers or TLS proxies can read the SNI header and route the connection to the appropriate target.
+I.e., it uses a single externally-visible TCP port (usually 5665) and forwards connections to one or more Icinga
+instances which are bound to a local TCP port. It does so by inspecting the environment name that is sent as part of the
+SNI extension.
diff --git a/doc/09-object-types.md b/doc/09-object-types.md
index 904e31b23..d04f78156 100644
--- a/doc/09-object-types.md
+++ b/doc/09-object-types.md
@@ -67,6 +67,7 @@ Configuration Attributes:
access\_control\_allow\_credentials | Boolean | **Deprecated.** Indicates whether or not the actual request can be made using credentials. Defaults to `true`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Credentials)
access\_control\_allow\_headers | String | **Deprecated.** Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request. Defaults to `Authorization`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Headers)
access\_control\_allow\_methods | String | **Deprecated.** Used in response to a preflight request to indicate which HTTP methods can be used when making the actual request. Defaults to `GET, POST, PUT, DELETE`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Methods)
+ environment | String | **Optional.** Used as suffix in TLS SNI extension name; default from constant `ApiEnvironment`, which is empty.
The attributes `access_control_allow_credentials`, `access_control_allow_headers` and `access_control_allow_methods`
are controlled by Icinga 2 and are not changeable by config any more.
@@ -816,6 +817,7 @@ Configuration Attributes:
enable\_service\_checks | Boolean | **Optional.** Whether active service checks are globally enabled. Defaults to true.
enable\_perfdata | Boolean | **Optional.** Whether performance data processing is globally enabled. Defaults to true.
vars | Dictionary | **Optional.** A dictionary containing custom attributes that are available globally.
+ environment | String | **Optional.** Specify the Icinga environment. This overrides the `Environment` constant specified in the configuration or on the CLI with `--define`. Defaults to empty.
## IdoMySqlConnection
diff --git a/doc/16-upgrading-icinga-2.md b/doc/16-upgrading-icinga-2.md
index cce8cd916..4e4db7c7f 100644
--- a/doc/16-upgrading-icinga-2.md
+++ b/doc/16-upgrading-icinga-2.md
@@ -41,6 +41,7 @@ has been removed and this setting has no effect.
New [Icinga constants](17-language-reference.md#icinga-constants) have been added in this release.
+* `Environment` for specifying the Icinga environment. Defaults to not set.
* `ApiBindHost` and `ApiBindPort` to allow overriding the default ApiListener values. This will be used for an Icinga addon only.
## Upgrading to v2.9
diff --git a/doc/17-language-reference.md b/doc/17-language-reference.md
index de94ba4e8..f7fae4695 100644
--- a/doc/17-language-reference.md
+++ b/doc/17-language-reference.md
@@ -407,10 +407,10 @@ Constant | Description
--------------------|-------------------
Vars |**Read-write.** Contains a dictionary with global custom attributes. Not set by default.
NodeName |**Read-write.** Contains the cluster node name. Set to the local hostname by default.
+Environment |**Read-write.** The name of the Icinga environment. Included in the SNI host name for outbound connections. Not set by default.
RunAsUser |**Read-write.** Defines the user the Icinga 2 daemon is running as. Set in the Icinga 2 sysconfig.
RunAsGroup |**Read-write.** Defines the group the Icinga 2 daemon is running as. Set in the Icinga 2 sysconfig.
MaxConcurrentChecks |**Read-write.** The number of max checks run simultaneously. Defaults to `512`.
-Environment |**Read-write.** The name of the Icinga environment. Included in the SNI host name when making outbound connections. Defaults to `production`.
ApiBindHost |**Read-write.** Overrides the default value for the ApiListener `bind_host` attribute. Not set by default.
ApiBindPort |**Read-write.** Overrides the default value for the ApiListener `bind_port` attribute. Not set by default.
@@ -454,7 +454,6 @@ SysconfDir |**Read-only.** Contains the path of the sysconf directory.
LocalStateDir |**Read-only.** Contains the path of the local state directory. Defaults to `PrefixDir + "/var"`.
RunDir |**Read-only.** Contains the path of the run directory. Defaults to `LocalStateDir + "/run"`.
-
Advanced runtime constants. Please only use them if advised by support or developers.
Variable | Description
diff --git a/icinga-app/icinga.cpp b/icinga-app/icinga.cpp
index 3849ca658..26ed4cf69 100644
--- a/icinga-app/icinga.cpp
+++ b/icinga-app/icinga.cpp
@@ -297,8 +297,6 @@ static int Main()
Application::DeclareConst("Concurrency", std::thread::hardware_concurrency());
Application::DeclareConst("MaxConcurrentChecks", Application::GetDefaultMaxConcurrentChecks());
- ScriptGlobal::Set("Environment", "production");
-
ScriptGlobal::Set("AttachDebugger", false);
ScriptGlobal::Set("PlatformKernel", Utility::GetPlatformKernel());
diff --git a/lib/base/CMakeLists.txt b/lib/base/CMakeLists.txt
index c16cda028..e784a3957 100644
--- a/lib/base/CMakeLists.txt
+++ b/lib/base/CMakeLists.txt
@@ -27,7 +27,7 @@ mkclass_target(sysloglogger.ti sysloglogger-ti.cpp sysloglogger-ti.hpp)
set(base_SOURCES
i2-base.hpp
- application.cpp application.hpp application-ti.hpp application-version.cpp
+ application.cpp application.hpp application-ti.hpp application-version.cpp application-environment.cpp
array.cpp array.hpp array-script.cpp
base64.cpp base64.hpp
boolean.cpp boolean.hpp boolean-script.cpp
diff --git a/lib/base/application-environment.cpp b/lib/base/application-environment.cpp
new file mode 100644
index 000000000..a70ea886f
--- /dev/null
+++ b/lib/base/application-environment.cpp
@@ -0,0 +1,34 @@
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License *
+ * as published by the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software Foundation *
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#include "base/application.hpp"
+#include "base/scriptglobal.hpp"
+
+using namespace icinga;
+
+String Application::GetAppEnvironment()
+{
+ Value defaultValue = Empty;
+ return ScriptGlobal::Get("Environment", &defaultValue);
+}
+
+void Application::SetAppEnvironment(const String& name)
+{
+ ScriptGlobal::Set("Environment", name);
+}
diff --git a/lib/base/application.hpp b/lib/base/application.hpp
index dbb14c38d..88fc51ab6 100644
--- a/lib/base/application.hpp
+++ b/lib/base/application.hpp
@@ -107,6 +107,9 @@ public:
static String GetAppVersion();
static String GetAppSpecVersion();
+ static String GetAppEnvironment();
+ static void SetAppEnvironment(const String& name);
+
static double GetStartTime();
static void SetStartTime(double ts);
diff --git a/lib/icinga/icingaapplication.cpp b/lib/icinga/icingaapplication.cpp
index 6ac00aa60..4db75d852 100644
--- a/lib/icinga/icingaapplication.cpp
+++ b/lib/icinga/icingaapplication.cpp
@@ -79,10 +79,10 @@ void IcingaApplication::StatsFunc(const Dictionary::Ptr& status, const Array::Pt
{ "enable_host_checks", icingaapplication->GetEnableHostChecks() },
{ "enable_service_checks", icingaapplication->GetEnableServiceChecks() },
{ "enable_perfdata", icingaapplication->GetEnablePerfdata() },
+ { "environment", icingaapplication->GetEnvironment() },
{ "pid", Utility::GetPid() },
{ "program_start", Application::GetStartTime() },
- { "version", Application::GetAppVersion() },
- { "environment", ScriptGlobal::Get("Environment", &Empty) }
+ { "version", Application::GetAppVersion() }
}));
}
@@ -294,6 +294,16 @@ String IcingaApplication::GetNodeName() const
return ScriptGlobal::Get("NodeName");
}
+String IcingaApplication::GetEnvironment() const
+{
+ return Application::GetAppEnvironment();
+}
+
+void IcingaApplication::SetEnvironment(const String& value, bool suppress_events, const Value& cookie)
+{
+ Application::SetAppEnvironment(value);
+}
+
void IcingaApplication::ValidateVars(const Lazy& lvalue, const ValidationUtils& utils)
{
MacroProcessor::ValidateCustomVars(this, lvalue());
diff --git a/lib/icinga/icingaapplication.hpp b/lib/icinga/icingaapplication.hpp
index eac7f5903..ba8b77cf3 100644
--- a/lib/icinga/icingaapplication.hpp
+++ b/lib/icinga/icingaapplication.hpp
@@ -50,6 +50,9 @@ public:
String GetNodeName() const;
+ String GetEnvironment() const override;
+ void SetEnvironment(const String& value, bool suppress_events = false, const Value& cookie = Empty) override;
+
void ValidateVars(const Lazy& lvalue, const ValidationUtils& utils) override;
private:
diff --git a/lib/icinga/icingaapplication.ti b/lib/icinga/icingaapplication.ti
index 61d4ecd4c..b9f0381b3 100644
--- a/lib/icinga/icingaapplication.ti
+++ b/lib/icinga/icingaapplication.ti
@@ -26,6 +26,12 @@ namespace icinga
class IcingaApplication : Application
{
+ [config, no_storage, virtual] String environment {
+ get;
+ set;
+ default {{{ return Application::GetAppEnvironment(); }}}
+ };
+
[config] bool enable_notifications {
default {{{ return true; }}}
};
diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp
index 428fb2df7..8c0abf570 100644
--- a/lib/remote/apilistener.cpp
+++ b/lib/remote/apilistener.cpp
@@ -388,16 +388,10 @@ void ApiListener::AddConnection(const Endpoint::Ptr& endpoint)
TcpSocket::Ptr client = new TcpSocket();
- String serverName = endpoint->GetName();
-
- String env = ScriptGlobal::Get("Environment", &Empty);
- if (env != "" && env != "production")
- serverName += ":" + env;
-
try {
endpoint->SetConnecting(true);
client->Connect(host, port);
- NewClientHandler(client, serverName, RoleClient);
+ NewClientHandler(client, endpoint->GetName(), RoleClient);
endpoint->SetConnecting(false);
} catch (const std::exception& ex) {
endpoint->SetConnecting(false);
@@ -447,10 +441,17 @@ void ApiListener::NewClientHandlerInternal(const Socket::Ptr& client, const Stri
TlsStream::Ptr tlsStream;
+ String environmentName = Application::GetAppEnvironment();
+
+ String serverName = hostname;
+
+ if (!environmentName.IsEmpty())
+ serverName += ":" + environmentName;
+
{
ObjectLock olock(this);
try {
- tlsStream = new TlsStream(client, hostname, role, m_SSLContext);
+ tlsStream = new TlsStream(client, serverName, role, m_SSLContext);
} catch (const std::exception&) {
Log(LogCritical, "ApiListener")
<< "Cannot create TLS stream from client connection (" << conninfo << ")";
diff --git a/tools/mkclass/classcompiler.cpp b/tools/mkclass/classcompiler.cpp
index 374c54d1c..0e1ee8954 100644
--- a/tools/mkclass/classcompiler.cpp
+++ b/tools/mkclass/classcompiler.cpp
@@ -568,7 +568,8 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&)
<< "{" << std::endl;
for (const Field& field : klass.Fields) {
- m_Impl << "\t" << "Set" << field.GetFriendlyName() << "(" << "GetDefault" << field.GetFriendlyName() << "(), true);" << std::endl;
+ if (!field.PureSetAccessor)
+ m_Impl << "\t" << "Set" << field.GetFriendlyName() << "(" << "GetDefault" << field.GetFriendlyName() << "(), true);" << std::endl;
}
m_Impl << "}" << std::endl << std::endl;