Repository: StackStorm/st2

Issue:
WebUI or Mistral - pick which won't work, when external IP not reachable from inside the box
Reported by @tonybaloney: 

he got internal and external IP address, and NAS. If mistral connects to internal api address as set up by . If so, WebUI can't connect to auth and API. And vice versa: if the API url is set to external IP address, Mistral can't connect to it because it's behind the NAT and not routable from within the box. 

In short, mistral needs to connect via internal ip, UI needs to connect to external IP/address, and we only have one URL parameter, the `api_url` in the `[auth]` section of `/etc/st2/st2.conf`

Temp workaround: via local /etc/hosts. 

@tonybaloney please add details if I missed smth. 

[stackstorm-community.slack.com link](https://stackstorm-community.slack.com/archives/community/p1447383434000310)



Discussion:
I'm +1 for having a special setting for API URL which is used by Mistral when talking back to the API.

In the past this was a big mess since we just used API listen IP and port and then we improved it by using a single value (public URL of the API endpoint).

Using public URL won't always work / is not always desired so having another setting is useful and makes sense.

We just need to make sure the setting has a good self explanatory name and we have good docs on it (`api_url` under `mistral` section seems reasonable to me).


Resolving patch:
diff --git a/st2actions/st2actions/runners/mistral/v2.py b/st2actions/st2actions/runners/mistral/v2.py
--- a/st2actions/st2actions/runners/mistral/v2.py
+++ b/st2actions/st2actions/runners/mistral/v2.py
@@ -29,6 +29,7 @@
 from st2common.util.workflow import mistral as utils
 from st2common.util.url import get_url_without_trailing_slash
 from st2common.util.api import get_full_public_api_url
+from st2common.util.api import get_mistral_api_url
 
 
 LOG = logging.getLogger(__name__)
@@ -148,9 +149,14 @@ def run(self, action_parameters):
         inputs = self.runner_parameters.get('context', dict())
         inputs.update(action_parameters)
 
-        api_url = get_full_public_api_url()
+        # This URL is used by Mistral to talk back to the API
+        api_url = get_mistral_api_url()
         endpoint = api_url + '/actionexecutions'
 
+        # This URL is available in the context and can be used by the users inside a workflow,
+        # similar to "ST2_ACTION_API_URL" environment variable available to actions
+        public_api_url = get_full_public_api_url()
+
         # Build context with additional information
         parent_context = {
             'execution_id': self.execution_id
@@ -177,6 +183,7 @@ def run(self, action_parameters):
             'env': {
                 'st2_execution_id': self.execution_id,
                 'st2_liveaction_id': self.liveaction_id,
+                'st2_action_api_url': public_api_url,
                 '__actions': {
                     'st2.action': {
                         'st2_context': st2_execution_context
diff --git a/st2common/st2common/config.py b/st2common/st2common/config.py
--- a/st2common/st2common/config.py
+++ b/st2common/st2common/config.py
@@ -172,7 +172,11 @@ def register_opts(ignore_errors=False):
         cfg.StrOpt('keystone_username', default=None, help='Username for authentication.'),
         cfg.StrOpt('keystone_password', default=None, help='Password for authentication.'),
         cfg.StrOpt('keystone_project_name', default=None, help='OpenStack project scope.'),
-        cfg.StrOpt('keystone_auth_url', default=None, help='Auth endpoint for Keystone.')
+        cfg.StrOpt('keystone_auth_url', default=None, help='Auth endpoint for Keystone.'),
+
+        cfg.StrOpt('api_url', default=None, help=('URL Mistral uses to talk back to the API.'
+            'If not provided it defaults to public API URL. Note: This needs to be a base '
+            'URL without API version (e.g. http://127.0.0.1:9101)'))
     ]
     do_register_opts(mistral_opts, group='mistral', ignore_errors=ignore_errors)
 
diff --git a/st2common/st2common/util/api.py b/st2common/st2common/util/api.py
--- a/st2common/st2common/util/api.py
+++ b/st2common/st2common/util/api.py
@@ -21,7 +21,9 @@
 
 __all__ = [
     'get_base_public_api_url',
-    'get_full_public_api_url'
+    'get_full_public_api_url',
+
+    'get_mistral_api_url'
 ]
 
 LOG = logging.getLogger(__name__)
@@ -53,3 +55,19 @@ def get_full_public_api_url(api_version=DEFAULT_API_VERSION):
     api_url = get_base_public_api_url()
     api_url = '%s/%s' % (api_url, api_version)
     return api_url
+
+
+def get_mistral_api_url(api_version=DEFAULT_API_VERSION):
+    """
+    Return a URL which Mistral uses to talk back to the StackStorm API.
+
+    Note: If not provided it defaults to the public API url.
+    """
+    if cfg.CONF.mistral.api_url:
+        api_url = get_url_without_trailing_slash(cfg.CONF.mistral.api_url)
+        api_url = '%s/%s' % (api_url, api_version)
+    else:
+        LOG.warn('"mistral.api_url" not set, using auth.api_url')
+        api_url = get_full_public_api_url(api_version=api_version)
+
+    return api_url


Test patch:
diff --git a/st2actions/tests/unit/test_mistral_v2.py b/st2actions/tests/unit/test_mistral_v2.py
--- a/st2actions/tests/unit/test_mistral_v2.py
+++ b/st2actions/tests/unit/test_mistral_v2.py
@@ -195,6 +195,7 @@ def test_launch_workflow(self):
         env = {
             'st2_execution_id': str(execution.id),
             'st2_liveaction_id': str(liveaction.id),
+            'st2_action_api_url': 'http://0.0.0.0:9101/v1',
             '__actions': {
                 'st2.action': {
                     'st2_context': {
@@ -244,6 +245,7 @@ def test_launch_workflow_with_st2_https(self):
         env = {
             'st2_execution_id': str(execution.id),
             'st2_liveaction_id': str(liveaction.id),
+            'st2_action_api_url': 'https://0.0.0.0:9101/v1',
             '__actions': {
                 'st2.action': {
                     'st2_context': {
@@ -294,6 +296,7 @@ def test_launch_workflow_with_notifications(self):
         env = {
             'st2_execution_id': str(execution.id),
             'st2_liveaction_id': str(liveaction.id),
+            'st2_action_api_url': 'http://0.0.0.0:9101/v1',
             '__actions': {
                 'st2.action': {
                     'st2_context': {
diff --git a/st2actions/tests/unit/test_mistral_v2_auth.py b/st2actions/tests/unit/test_mistral_v2_auth.py
--- a/st2actions/tests/unit/test_mistral_v2_auth.py
+++ b/st2actions/tests/unit/test_mistral_v2_auth.py
@@ -152,6 +152,7 @@ def test_launch_workflow_with_st2_auth(self):
         env = {
             'st2_execution_id': str(execution.id),
             'st2_liveaction_id': str(liveaction.id),
+            'st2_action_api_url': 'http://0.0.0.0:9101/v1',
             '__actions': {
                 'st2.action': {
                     'st2_context': {
@@ -209,6 +210,7 @@ def test_launch_workflow_with_mistral_auth(self):
         env = {
             'st2_execution_id': str(execution.id),
             'st2_liveaction_id': str(liveaction.id),
+            'st2_action_api_url': 'http://0.0.0.0:9101/v1',
             '__actions': {
                 'st2.action': {
                     'st2_context': {
diff --git a/st2common/tests/unit/test_util_api.py b/st2common/tests/unit/test_util_api.py
--- a/st2common/tests/unit/test_util_api.py
+++ b/st2common/tests/unit/test_util_api.py
@@ -20,6 +20,7 @@
 from st2common.constants.api import DEFAULT_API_VERSION
 from st2common.util.api import get_base_public_api_url
 from st2common.util.api import get_full_public_api_url
+from st2common.util.api import get_mistral_api_url
 from st2tests.config import parse_args
 parse_args()
 
@@ -66,3 +67,17 @@ def test_get_full_public_api_url(self):
             cfg.CONF.auth.api_url = mock_value
             actual = get_full_public_api_url()
             self.assertEqual(actual, expected_result)
+
+    def test_get_mistral_api_url(self):
+        cfg.CONF.set_override(name='api_url', override='http://localhost:9999', group='auth')
+        cfg.CONF.set_override(name='api_url', override=None, group='mistral')
+
+        # No URL set, should fall back to auth.api_url
+        result = get_mistral_api_url()
+        self.assertEqual(result, 'http://localhost:9999/' + DEFAULT_API_VERSION)
+
+        # mistral.api_url provided, should use that
+        cfg.CONF.set_override(name='api_url', override='http://10.0.0.0:9999', group='mistral')
+
+        result = get_mistral_api_url()
+        self.assertEqual(result, 'http://10.0.0.0:9999/' + DEFAULT_API_VERSION)