Repository: emissary-ingress/emissary

Issue:
Ambassador mustn't generate cluster names > 60 characters
Envoy has a 60-character cluster name limit. Sigh.

```
ambassador-7cb98fd58d-tnr6x ambassador [2018-01-10 16:56:16.107][36][critical][main] source/server/config_validation/server.cc:48] error initializing configuration '/etc/ambassador-config-3-envoy.json': Invalid cluster name: Length of cluster_demo_auth_dev_ambassador_demo_services_svc_cluster_local (64) exceeds allowed maximum length (60)
```



Discussion:

Resolving patch:
diff --git a/ambassador/ambassador/config.py b/ambassador/ambassador/config.py
--- a/ambassador/ambassador/config.py
+++ b/ambassador/ambassador/config.py
@@ -464,7 +464,7 @@ def process_object(self, obj):
         # Make sure it has a source: use what's in the object if present, 
         # otherwise use self.source.
         self.sources[source_key]['_source'] = obj.get('source', self.source)
-        self.logger.debug("source for %s is %s" % (source_key, self.sources[source_key]['_source']))
+        # self.logger.debug("source for %s is %s" % (source_key, self.sources[source_key]['_source']))
 
         source_map = self.source_map.setdefault(self.filename, {})
         source_map[source_key] = True
@@ -477,9 +477,9 @@ def process_object(self, obj):
             handler = self.save_object
             self.logger.warning("%s[%d]: no handler for %s, just saving" %
                                 (self.filename, self.ocount, obj_kind))
-        else:
-            self.logger.debug("%s[%d]: handling %s..." %
-                              (self.filename, self.ocount, obj_kind))
+        # else:
+        #     self.logger.debug("%s[%d]: handling %s..." %
+        #                       (self.filename, self.ocount, obj_kind))
 
         try:
             handler(source_key, obj, obj_name, obj_kind, obj_version)
@@ -557,7 +557,7 @@ def save_object(self, source_key, obj, obj_name, obj_kind, obj_version):
     def handle_pragma(self, source_key, obj):
         keylist = sorted([x for x in sorted(obj.keys()) if ((x != 'apiVersion') and (x != 'kind'))])
 
-        self.logger.debug("PRAGMA: %s" % keylist)
+        # self.logger.debug("PRAGMA: %s" % keylist)
 
         for key in keylist:
             if key == 'source':
@@ -570,9 +570,9 @@ def handle_pragma(self, source_key, obj):
                 override = self.source_overrides.setdefault(self.filename, {})
                 override['ocount_delta'] = -1
 
-                self.logger.debug("PRAGMA: autogenerated, setting ocount_delta to -1")
-            else:
-                self.logger.debug("PRAGMA: skip %s" % key)
+            #     self.logger.debug("PRAGMA: autogenerated, setting ocount_delta to -1")
+            # else:
+            #     self.logger.debug("PRAGMA: skip %s" % key)
 
         return RichStatus.OK(msg="handled pragma object")
 
@@ -788,7 +788,7 @@ def generate_intermediate_config(self):
 
         # ...TLS config, if necessary...
         if self.ambassador_module['tls_config']:
-            self.logger.debug("USING TLS")
+            # self.logger.debug("USING TLS")
             self.envoy_config['tls'] = self.ambassador_module['tls_config']
 
         # ...and probes, if configured.
@@ -817,7 +817,7 @@ def generate_intermediate_config(self):
                     service=service
                 )
 
-                self.logger.debug("PROBE %s: %s -> %s%s" % (name, prefix, service, rewrite))
+                # self.logger.debug("PROBE %s: %s -> %s%s" % (name, prefix, service, rewrite))
 
         # OK! We have all the mappings we need. Process them (don't worry about sorting
         # yet, we'll do that on routes).
@@ -908,13 +908,47 @@ def generate_intermediate_config(self):
         #     'cluster_diagnostics'
         # )
 
+        # OK. Walk the set of clusters and normalize names...
+        collisions = {}
+        mangled = {}
+
+        for name in sorted(self.envoy_clusters.keys()):
+            if len(name) > 60:
+                # Too long.
+                short_name = name[0:40]
+
+                collision_list = collisions.setdefault(short_name, [])
+                collision_list.append(name)
+
+        for short_name in sorted(collisions.keys()):
+            name_list = collisions[short_name]
+
+            i = 0
+
+            for name in sorted(name_list):
+                mangled_name = "%s-%d" % (short_name, i)
+                i += 1
+
+                self.logger.info("%s => %s" % (name, mangled_name))
+
+                mangled[name] = mangled_name
+                self.envoy_clusters[name]['name'] = mangled_name
+
         # We need to default any unspecified weights and renormalize to 100
         for group_id, route in self.envoy_routes.items():
             clusters = route["clusters"]
+
             total = 0.0
             unspecified = 0
 
             for c in clusters:
+                # Mangle the name, if need be.
+                c_name = c["name"]
+
+                if c_name in mangled:
+                    c["name"] = mangled[c_name]
+                    # self.logger.info("%s: mangling cluster %s to %s" % (group_id, c_name, c["name"]))
+
                 if c["weight"] is None:
                     unspecified += 1
                 else:
@@ -935,7 +969,7 @@ def generate_intermediate_config(self):
 
         # ...then map clusters back into a list...
         self.envoy_config['clusters'] = [
-            self.envoy_clusters[name] for name in sorted(self.envoy_clusters.keys())
+            self.envoy_clusters[cluster_key] for cluster_key in sorted(self.envoy_clusters.keys())
         ]
 
         # ...and finally repeat for breakers and outliers, but copy them in the process so we
@@ -1085,7 +1119,7 @@ def tls_config_helper(self, name, amod, tmod):
 
             context = tmod[context_name]
 
-            self.logger.debug("context %s -- %s" % (context_name, json.dumps(context)))
+            # self.logger.debug("context %s -- %s" % (context_name, json.dumps(context)))
 
             if context.get('enabled', True):
                 if context_name == 'server':


Test patch:
diff --git a/ambassador/tests/001-broader-v0/config/mapping-long.yaml b/ambassador/tests/001-broader-v0/config/mapping-long.yaml
new file mode 100644
--- /dev/null
+++ b/ambassador/tests/001-broader-v0/config/mapping-long.yaml
@@ -0,0 +1,20 @@
+---
+apiVersion: ambassador/v0.1
+kind: Pragma
+source: test source
+autogenerated: true
+---
+apiVersion: ambassador/v0
+kind: Mapping
+name: long_get_mapping
+prefix: /long/
+method: GET
+service: really-really-really-long-no-i-mean-really-really-really-really-long-service
+---
+apiVersion: ambassador/v0
+kind: Mapping
+name: long_put_mapping
+prefix: /long/
+rewrite: /qotm/quote/
+method: PUT
+service: really-really-really-long-no-i-mean-really-really-really-really-long-service-two
diff --git a/ambassador/tests/001-broader-v0/gold.intermediate.json b/ambassador/tests/001-broader-v0/gold.intermediate.json
--- a/ambassador/tests/001-broader-v0/gold.intermediate.json
+++ b/ambassador/tests/001-broader-v0/gold.intermediate.json
@@ -164,6 +164,32 @@
                     "tcp://qotm.tspace:80"
                 ]
             },
+            {
+                "_referenced_by": [
+                    "mapping-long.yaml.1"
+                ],
+                "_service": "really-really-really-long-no-i-mean-really-really-really-really-long-service",
+                "_source": "mapping-long.yaml.1",
+                "lb_type": "round_robin",
+                "name": "cluster_really_really_really_long_no_i_m-0",
+                "type": "strict_dns",
+                "urls": [
+                    "tcp://really-really-really-long-no-i-mean-really-really-really-really-long-service:80"
+                ]
+            },
+            {
+                "_referenced_by": [
+                    "mapping-long.yaml.2"
+                ],
+                "_service": "really-really-really-long-no-i-mean-really-really-really-really-long-service-two",
+                "_source": "mapping-long.yaml.2",
+                "lb_type": "round_robin",
+                "name": "cluster_really_really_really_long_no_i_m-1",
+                "type": "strict_dns",
+                "urls": [
+                    "tcp://really-really-really-long-no-i-mean-really-really-really-really-long-service-two:80"
+                ]
+            },
             {
                 "_referenced_by": [
                     "mapping-websocket.yaml.1"
@@ -277,6 +303,29 @@
                 "prefix": "/grpc/",
                 "prefix_rewrite": "/"
             },
+            {
+                "_group_id": "30f39d42f27d9bfc42f090a4398d77cb7c33cb3a",
+                "_method": "PUT",
+                "_referenced_by": [
+                    "mapping-long.yaml.2"
+                ],
+                "_source": "mapping-long.yaml.2",
+                "clusters": [
+                    {
+                        "name": "cluster_really_really_really_long_no_i_m-1",
+                        "weight": 100.0
+                    }
+                ],
+                "headers": [
+                    {
+                        "name": ":method",
+                        "regex": false,
+                        "value": "PUT"
+                    }
+                ],
+                "prefix": "/long/",
+                "prefix_rewrite": "/qotm/quote/"
+            },
             {
                 "_group_id": "6151893fbc2f1f730a1bb946b2eefac391f0d2ec",
                 "_method": "GET",
@@ -293,6 +342,29 @@
                 "prefix": "/ambassador/v0/check_alive",
                 "prefix_rewrite": "/ambassador/v0/check_alive"
             },
+            {
+                "_group_id": "6f6f65520e4e9992c6dd345d2028ea7c979a082e",
+                "_method": "GET",
+                "_referenced_by": [
+                    "mapping-long.yaml.1"
+                ],
+                "_source": "mapping-long.yaml.1",
+                "clusters": [
+                    {
+                        "name": "cluster_really_really_really_long_no_i_m-0",
+                        "weight": 100.0
+                    }
+                ],
+                "headers": [
+                    {
+                        "name": ":method",
+                        "regex": false,
+                        "value": "GET"
+                    }
+                ],
+                "prefix": "/long/",
+                "prefix_rewrite": "/"
+            },
             {
                 "_group_id": "88fe5d48949dbfac1404d01d1a044c97f43e840a",
                 "_method": "PUT",
@@ -477,6 +549,10 @@
         "mapping-httpbin.yaml": {
             "mapping-httpbin.yaml.1": true
         },
+        "mapping-long.yaml": {
+            "mapping-long.yaml.1": true,
+            "mapping-long.yaml.2": true
+        },
         "mapping-qotm.yaml": {
             "mapping-qotm.yaml.1": true,
             "mapping-qotm.yaml.2": true,
@@ -587,6 +663,24 @@
             "version": "ambassador/v0",
             "yaml": "apiVersion: ambassador/v0\nhost_rewrite: httpbin.org\nkind: Mapping\nname: httpbin_mapping\nprefix: /httpbin/\nservice: http://httpbin.org\n"
         },
+        "mapping-long.yaml.1": {
+            "_source": "test source",
+            "filename": "mapping-long.yaml",
+            "index": 1,
+            "kind": "Mapping",
+            "name": "long_get_mapping",
+            "version": "ambassador/v0",
+            "yaml": "apiVersion: ambassador/v0\nkind: Mapping\nmethod: GET\nname: long_get_mapping\nprefix: /long/\nservice: really-really-really-long-no-i-mean-really-really-really-really-long-service\n"
+        },
+        "mapping-long.yaml.2": {
+            "_source": "test source",
+            "filename": "mapping-long.yaml",
+            "index": 2,
+            "kind": "Mapping",
+            "name": "long_put_mapping",
+            "version": "ambassador/v0",
+            "yaml": "apiVersion: ambassador/v0\nkind: Mapping\nmethod: PUT\nname: long_put_mapping\nprefix: /long/\nrewrite: /qotm/quote/\nservice: really-really-really-long-no-i-mean-really-really-really-really-long-service-two\n"
+        },
         "mapping-qotm.yaml.1": {
             "_source": "mapping-qotm.yaml",
             "filename": "mapping-qotm.yaml",
diff --git a/ambassador/tests/001-broader-v0/gold.json b/ambassador/tests/001-broader-v0/gold.json
--- a/ambassador/tests/001-broader-v0/gold.json
+++ b/ambassador/tests/001-broader-v0/gold.json
@@ -141,6 +141,32 @@
                     }
                     ,
                     
+                    {
+                      "timeout_ms": 10000,"prefix": "/long/","prefix_rewrite": "/qotm/quote/","headers": [{"name": ":method", "regex": false, "value": "PUT"}],
+                      "weighted_clusters": {
+                          "clusters": [
+                              
+                                 { "name": "cluster_really_really_really_long_no_i_m-1", "weight": 100.0 }
+                              
+                          ]
+                      }
+                      
+                    }
+                    ,
+                    
+                    {
+                      "timeout_ms": 10000,"prefix": "/long/","prefix_rewrite": "/","headers": [{"name": ":method", "regex": false, "value": "GET"}],
+                      "weighted_clusters": {
+                          "clusters": [
+                              
+                                 { "name": "cluster_really_really_really_long_no_i_m-0", "weight": 100.0 }
+                              
+                          ]
+                      }
+                      
+                    }
+                    ,
+                    
                     {
                       "timeout_ms": 10000,"prefix": "/evil/","prefix_rewrite": "/qotm/quote/","headers": [{"name": ":method", "regex": false, "value": "PUT"}],
                       "weighted_clusters": {
@@ -350,6 +376,28 @@
             "url": "tcp://qotm.tspace:80"
           }
           
+        ]},
+      {
+        "name": "cluster_really_really_really_long_no_i_m-0",
+        "connect_timeout_ms": 3000,
+        "type": "strict_dns",
+        "lb_type": "round_robin",        
+        "hosts": [
+          {
+            "url": "tcp://really-really-really-long-no-i-mean-really-really-really-really-long-service:80"
+          }
+          
+        ]},
+      {
+        "name": "cluster_really_really_really_long_no_i_m-1",
+        "connect_timeout_ms": 3000,
+        "type": "strict_dns",
+        "lb_type": "round_robin",        
+        "hosts": [
+          {
+            "url": "tcp://really-really-really-long-no-i-mean-really-really-really-really-long-service-two:80"
+          }
+          
         ]},
       {
         "name": "cluster_slack_com",