def build(self) -> Tuple[IR, EnvoyConfig]: # Do a build, return IR & econf, but also stash them in self.builds. yaml_data = yaml.safe_dump_all(self.resources.values()) aconf = Config() fetcher = ResourceFetcher(logger, aconf) fetcher.parse_yaml(yaml_data, k8s=True) aconf.load_all(fetcher.sorted()) ir = IR(aconf, cache=self.cache, file_checker=lambda path: True, secret_handler=self.secret_handler) assert ir, "could not create an IR" econf = EnvoyConfig.generate(ir, "V2", cache=self.cache) assert econf, "could not create an econf" self.builds.append((ir, econf)) return ir, econf
def test_knative_counters(): aconf = Config() fetcher = ResourceFetcher(logger, aconf) fetcher.parse_yaml(knative_ingress_example, k8s=True) aconf.load_all(fetcher.sorted()) secret_handler = NullSecretHandler(logger, None, None, "0") ir = IR(aconf, secret_handler=secret_handler) feats = ir.features() assert feats['knative_ingress_count'] == 1, f"Expected a Knative ingress, did not find one" assert feats['cluster_ingress_count'] == 0, f"Expected no Knative cluster ingresses, found at least one"
def _test_errorresponse(yaml, expectations, expect_fail=False): aconf = Config() fetcher = ResourceFetcher(logger, aconf) fetcher.parse_yaml(yaml) aconf.load_all(fetcher.sorted()) secret_handler = NullSecretHandler(logger, None, None, "0") ir = IR(aconf, file_checker=lambda path: True, secret_handler=secret_handler) error_response = IRErrorResponse( ir, aconf, ir.ambassador_module.get('error_response_overrides', None), ir.ambassador_module) error_response.setup(ir, aconf) if aconf.errors: print("errors: %s" % repr(aconf.errors)) ir_conf = error_response.config() if expect_fail: assert ir_conf is None return assert ir_conf # There should be no default body format override body_format = ir_conf.get('body_format', None) assert body_format is None mappers = ir_conf.get('mappers', None) assert mappers assert len(mappers) == len(expectations), \ f"unexpected len(mappers) {len(expectations)} != len(expectations) {len(expectations)}" for i in range(len(expectations)): expected_filter, expected_body_format_override = expectations[i] m = mappers[i] print( "checking with expected_body_format_override %s and expected_filter %s" % (expected_body_format_override, expected_filter)) print("checking m: ", m) actual_filter = m['filter'] assert m['filter'] == expected_filter if expected_body_format_override: actual_body_format_override = m['body_format_override'] assert actual_body_format_override == expected_body_format_override
def _get_envoy_config(yaml): aconf = Config() fetcher = ResourceFetcher(logger, aconf) fetcher.parse_yaml(yaml) aconf.load_all(fetcher.sorted()) secret_handler = NullSecretHandler(logger, None, None, "0") ir = IR(aconf, file_checker=lambda path: True, secret_handler=secret_handler) assert ir return EnvoyConfig.generate(ir, "V2")
def test_envvar_expansion(): os.environ["TEST_SERVICE"] = "foo" aconf = Config() fetcher = ResourceFetcher(logger, aconf) fetcher.parse_yaml(yaml) aconf.load_all(fetcher.sorted()) mappings = aconf.config["mappings"] test_mapping = mappings["test_mapping"] assert test_mapping.service == "foo:9999"
def _get_envoy_config(yaml, version='V3'): aconf = Config() fetcher = ResourceFetcher(logger, aconf) fetcher.parse_yaml(default_listener_manifests() + yaml, k8s=True) aconf.load_all(fetcher.sorted()) secret_handler = NullSecretHandler(logger, None, None, "0") ir = IR(aconf, file_checker=lambda path: True, secret_handler=secret_handler) assert ir return EnvoyConfig.generate(ir, version)
def test_shadow_v3(): aconf = Config() yaml = ''' --- apiVersion: getambassador.io/v2 kind: Mapping name: httpbin-mapping service: httpbin prefix: /httpbin/ --- apiVersion: getambassador.io/v2 kind: Mapping name: httpbin-mapping-shadow service: httpbin-shadow prefix: /httpbin/ shadow: true weight: 10 ''' fetcher = ResourceFetcher(logger, aconf) fetcher.parse_yaml(yaml) aconf.load_all(fetcher.sorted()) secret_handler = MockSecretHandler(logger, "mockery", "/tmp/ambassador/snapshots", "v1") ir = IR(aconf, file_checker=lambda path: True, secret_handler=secret_handler) assert ir econf = EnvoyConfig.generate(ir, "V3") bootstrap_config, ads_config, _ = econf.split_config() ads_config.pop('@type', None) mirrored_config = get_mirrored_config(ads_config) assert 'request_mirror_policies' in mirrored_config['route'] assert len(mirrored_config['route']['request_mirror_policies']) == 1 mirror_policy = mirrored_config['route']['request_mirror_policies'][0] assert mirror_policy['cluster'] == 'cluster_shadow_httpbin_shadow_default' assert mirror_policy['runtime_fraction']['default_value'][ 'numerator'] == 10 assert mirror_policy['runtime_fraction']['default_value'][ 'denominator'] == 'HUNDRED' assert_valid_envoy_config(ads_config) assert_valid_envoy_config(bootstrap_config)
def _test_compiler(test_name, envoy_api_version="V2"): test_path = os.path.join(testfolder, test_name) basename = os.path.basename(test_path) with open(os.path.join(test_path, 'bootstrap-ads.json'), 'r') as f: expected_bootstrap = json.loads(f.read()) node_name = expected_bootstrap.get('node', {}).get('cluster', None) assert node_name namespace = node_name.replace(test_name + '-', '', 1) with open(os.path.join(test_path, 'snapshot.yaml'), 'r') as f: watt = f.read() Config.ambassador_id = basename Config.ambassador_namespace = namespace os.environ['AMBASSADOR_ID'] = basename os.environ['AMBASSADOR_NAMESPACE'] = namespace aconf = Config() fetcher = ResourceFetcher(logger, aconf) fetcher.parse_watt(watt) aconf.load_all(fetcher.sorted()) secret_handler = MockSecretHandler(logger, "mockery", "/tmp/ambassador/snapshots", "v1") ir = IR(aconf, file_checker=lambda path: True, secret_handler=secret_handler) expected_econf_file = 'econf.json' if ir.edge_stack_allowed: expected_econf_file = 'econf-aes.json' with open(os.path.join(test_path, expected_econf_file), 'r') as f: expected_econf = json.loads(f.read()) assert ir econf = EnvoyConfig.generate(ir, version=envoy_api_version) bootstrap_config, ads_config, _ = econf.split_config() ads_config.pop('@type', None) assert bootstrap_config == expected_bootstrap assert ads_config == expected_econf assert_valid_envoy_config(ads_config) assert_valid_envoy_config(bootstrap_config) # @pytest.mark.compilertest # @pytest.mark.parametrize("test_name", testdata) # def test_compiler_v3(test_name): # _test_compiler(test_name, envoy_api_version="V3")
def test_resourcefetcher_handle_k8s_service(): aconf = Config() fetcher = ResourceFetcher(logger, aconf) # Test no metadata key svc = {} result = fetcher.handle_k8s_service(svc) assert result is None svc["metadata"] = { "name": "testservice", "annotations": { "foo": "bar" } } # Test no ambassador annotation result = fetcher.handle_k8s_service(svc) assert result == ('testservice.default', []) # Test empty annotation svc["metadata"]["annotations"]["getambassador.io/config"] = {} result = fetcher.handle_k8s_service(svc) assert result == ('testservice.default', []) # Test valid annotation svc["metadata"]["annotations"]["getambassador.io/config"] = """apiVersion: getambassador.io/v1 kind: Mapping name: test_mapping prefix: /test/ service: test:9999""" result = fetcher.handle_k8s_service(svc) expected = { 'apiVersion': 'getambassador.io/v1', 'kind': 'Mapping', 'name': 'test_mapping', 'prefix': '/test/', 'service': 'test:9999', 'namespace': 'default' } assert result == ('testservice.default', [expected])
def config(config_dir_path: Parameter.REQUIRED, output_json_path: Parameter.REQUIRED, *, debug=False, debug_scout=False, check=False, k8s=False, ir=None, aconf=None, exit_on_error=False): """ Generate an Envoy configuration :param config_dir_path: Configuration directory to scan for Ambassador YAML files :param output_json_path: Path to output envoy.json :param debug: If set, generate debugging output :param debug_scout: If set, generate debugging output when talking to Scout :param check: If set, generate configuration only if it doesn't already exist :param k8s: If set, assume configuration files are annotated K8s manifests :param exit_on_error: If set, will exit with status 1 on any configuration error :param ir: Pathname to which to dump the IR (not dumped if not present) :param aconf: Pathname to which to dump the aconf (not dumped if not present) """ if debug: logger.setLevel(logging.DEBUG) if debug_scout: logging.getLogger('ambassador.scout').setLevel(logging.DEBUG) try: logger.debug("CHECK MODE %s" % check) logger.debug("CONFIG DIR %s" % config_dir_path) logger.debug("OUTPUT PATH %s" % output_json_path) dump_aconf: Optional[str] = aconf dump_ir: Optional[str] = ir # Bypass the existence check... output_exists = False if check: # ...oh no wait, they explicitly asked for the existence check! # Assume that the file exists (ie, we'll do nothing) unless we # determine otherwise. output_exists = True try: parse_json(open(output_json_path, "r").read()) except FileNotFoundError: logger.debug("output file does not exist") output_exists = False except OSError: logger.warning("output file is not sane?") output_exists = False except json.decoder.JSONDecodeError: logger.warning("output file is not valid JSON") output_exists = False logger.info("Output file %s" % ("exists" if output_exists else "does not exist")) rc = RichStatus.fromError("impossible error") if not output_exists: # Either we didn't need to check, or the check didn't turn up # a valid config. Regenerate. logger.info("Generating new Envoy configuration...") aconf = Config() fetcher = ResourceFetcher(logger, aconf) fetcher.load_from_filesystem(config_dir_path, k8s=k8s) aconf.load_all(fetcher.sorted()) if dump_aconf: with open(dump_aconf, "w") as output: output.write(aconf.as_json()) output.write("\n") # If exit_on_error is set, log _errors and exit with status 1 if exit_on_error and aconf.errors: raise Exception("errors in: {0}".format(', '.join( aconf.errors.keys()))) secret_handler = NullSecretHandler(logger, config_dir_path, config_dir_path, "0") ir = IR(aconf, file_checker=file_checker, secret_handler=secret_handler) if dump_ir: with open(dump_ir, "w") as output: output.write(ir.as_json()) output.write("\n") # clize considers kwargs with False for default value as flags, # resulting in the logic below. # https://clize.readthedocs.io/en/stable/basics.html#accepting-flags logger.info("Writing envoy V2 configuration") v2config = V2Config(ir) rc = RichStatus.OK(msg="huh_v2") if rc: with open(output_json_path, "w") as output: output.write(v2config.as_json()) output.write("\n") else: logger.error("Could not generate new Envoy configuration: %s" % rc.error) scout = Scout() result = scout.report(action="config", mode="cli") show_notices(result) except Exception as e: handle_exception("EXCEPTION from config", e, config_dir_path=config_dir_path, output_json_path=output_json_path) # This is fatal. sys.exit(1)
def dump(config_dir_path: Parameter.REQUIRED, *, secret_dir_path=None, watt=False, debug=False, debug_scout=False, k8s=False, recurse=False, stats=False, nopretty=False, everything=False, aconf=False, ir=False, v2=False, v3=False, diag=False, features=False, profile=False): """ Dump various forms of an Ambassador configuration for debugging Use --aconf, --ir, and --envoy to control what gets dumped. If none are requested, the IR will be dumped. :param config_dir_path: Configuration directory to scan for Ambassador YAML files :param secret_dir_path: Directory into which to save secrets :param watt: If set, input must be a WATT snapshot :param debug: If set, generate debugging output :param debug_scout: If set, generate debugging output :param k8s: If set, assume configuration files are annotated K8s manifests :param recurse: If set, recurse into directories below config_dir_path :param stats: If set, dump statistics to stderr :param nopretty: If set, do not pretty print the dumped JSON :param aconf: If set, dump the Ambassador config :param ir: If set, dump the IR :param v2: If set, dump the Envoy V2 config :param v3: If set, dump the Envoy V3 config :param diag: If set, dump the Diagnostics overview :param everything: If set, dump everything :param features: If set, dump the feature set :param profile: If set, profile with the cProfile module """ if not secret_dir_path: secret_dir_path = "/tmp/cli-secrets" if not os.path.isdir(secret_dir_path): secret_dir_path = os.path.dirname(secret_dir_path) if debug: logger.setLevel(logging.DEBUG) if debug_scout: logging.getLogger('ambassador.scout').setLevel(logging.DEBUG) if everything: aconf = True ir = True v2 = True v3 = True diag = True features = True elif not (aconf or ir or v2 or v3 or diag or features): aconf = True ir = True v2 = True v3 = False diag = False features = False dump_aconf = aconf dump_ir = ir dump_v2 = v2 dump_v3 = v3 dump_diag = diag dump_features = features od = {} diagconfig: Optional[EnvoyConfig] = None _profile: Optional[cProfile.Profile] = None _rc = 0 if profile: _profile = cProfile.Profile() _profile.enable() try: total_timer = Timer("total") total_timer.start() fetch_timer = Timer("fetch resources") with fetch_timer: aconf = Config() fetcher = ResourceFetcher(logger, aconf) if watt: fetcher.parse_watt(open(config_dir_path, "r").read()) else: fetcher.load_from_filesystem(config_dir_path, k8s=k8s, recurse=True) load_timer = Timer("load fetched resources") with load_timer: aconf.load_all(fetcher.sorted()) # aconf.post_error("Error from string, boo yah") # aconf.post_error(RichStatus.fromError("Error from RichStatus")) irgen_timer = Timer("ir generation") with irgen_timer: secret_handler = NullSecretHandler(logger, config_dir_path, secret_dir_path, "0") ir = IR(aconf, file_checker=file_checker, secret_handler=secret_handler) aconf_timer = Timer("aconf") with aconf_timer: if dump_aconf: od['aconf'] = aconf.as_dict() ir_timer = Timer("ir") with ir_timer: if dump_ir: od['ir'] = ir.as_dict() v2_timer = Timer("v2") with v2_timer: if dump_v2: v2config = V2Config(ir) diagconfig = v2config od['v2'] = v2config.as_dict() v3_timer = Timer("v3") with v3_timer: if dump_v3: v3config = V3Config(ir) diagconfig = v3config od['v3'] = v3config.as_dict() diag_timer = Timer("diag") with diag_timer: if dump_diag: if not diagconfig: diagconfig = V2Config(ir) diagconfigv3 = V3Config(ir) econf = typecast(EnvoyConfig, diagconfig) econfv3 = typecast(EnvoyConfig, diagconfigv3) diag = Diagnostics(ir, econf) diagv3 = Diagnostics(ir, econfv3) od['diag'] = diag.as_dict() od['elements'] = econf.elements od['diagv3'] = diagv3.as_dict() od['elementsv3'] = econfv3.elements features_timer = Timer("features") with features_timer: if dump_features: od['features'] = ir.features() # scout = Scout() # scout_args = {} # # if ir and not os.environ.get("AMBASSADOR_DISABLE_FEATURES", None): # scout_args["features"] = ir.features() # # result = scout.report(action="dump", mode="cli", **scout_args) # show_notices(result) dump_timer = Timer("dump JSON") with dump_timer: js = dump_json(od, pretty=not nopretty) jslen = len(js) write_timer = Timer("write JSON") with write_timer: sys.stdout.write(js) sys.stdout.write("\n") total_timer.stop() route_count = 0 vhost_count = 0 filter_chain_count = 0 filter_count = 0 apiversion = 'v2' if v2 else 'v3' if apiversion in od: for listener in od[apiversion]['static_resources']['listeners']: for fc in listener['filter_chains']: filter_chain_count += 1 for f in fc['filters']: filter_count += 1 for vh in f['typed_config']['route_config'][ 'virtual_hosts']: vhost_count += 1 route_count += len(vh['routes']) if stats: sys.stderr.write("STATS:\n") sys.stderr.write(" config bytes: %d\n" % jslen) sys.stderr.write(" vhosts: %d\n" % vhost_count) sys.stderr.write(" filter chains: %d\n" % filter_chain_count) sys.stderr.write(" filters: %d\n" % filter_count) sys.stderr.write(" routes: %d\n" % route_count) sys.stderr.write(" routes/vhosts: %.3f\n" % float(float(route_count) / float(vhost_count))) sys.stderr.write("TIMERS:\n") sys.stderr.write(" fetch resources: %.3fs\n" % fetch_timer.average) sys.stderr.write(" load resources: %.3fs\n" % load_timer.average) sys.stderr.write(" ir generation: %.3fs\n" % irgen_timer.average) sys.stderr.write(" aconf: %.3fs\n" % aconf_timer.average) sys.stderr.write(" envoy v2: %.3fs\n" % v2_timer.average) sys.stderr.write(" diag: %.3fs\n" % diag_timer.average) sys.stderr.write(" features: %.3fs\n" % features_timer.average) sys.stderr.write(" dump json: %.3fs\n" % dump_timer.average) sys.stderr.write(" write json: %.3fs\n" % write_timer.average) sys.stderr.write(" ----------------------\n") sys.stderr.write(" total: %.3fs\n" % total_timer.average) except Exception as e: handle_exception("EXCEPTION from dump", e, config_dir_path=config_dir_path) _rc = 1 if _profile: _profile.disable() _profile.dump_stats("ambassador.profile") sys.exit(_rc)
def _test_headercaseoverrides(yaml, expectations, expect_norules=False): aconf = Config() yaml = yaml + ''' --- apiVersion: getambassador.io/v2 kind: Mapping name: httpbin-mapping service: httpbin prefix: /httpbin/ ''' fetcher = ResourceFetcher(logger, aconf) fetcher.parse_yaml(yaml) aconf.load_all(fetcher.sorted()) secret_handler = NullSecretHandler(logger, None, None, "0") ir = IR(aconf, file_checker=lambda path: True, secret_handler=secret_handler) assert ir econf = EnvoyConfig.generate(ir, "V2") assert econf, "could not create an econf" found_module_rules = False found_cluster_rules = False conf = econf.as_dict() for listener in conf['static_resources']['listeners']: for filter_chain in listener['filter_chains']: for f in filter_chain['filters']: typed_config = f['typed_config'] if 'http_protocol_options' not in typed_config: continue http_protocol_options = typed_config['http_protocol_options'] if expect_norules: assert 'header_key_format' not in http_protocol_options, \ f"'header_key_format' found unexpected typed_config {typed_config}" continue assert 'header_key_format' in http_protocol_options, \ f"'header_key_format' not found, typed_config {typed_config}" header_key_format = http_protocol_options['header_key_format'] assert 'custom' in header_key_format, \ f"'custom' not found, typed_config {typed_config}" rules = header_key_format['custom']['rules'] assert len(rules) == len(expectations) for e in expectations: hdr = e.lower() assert hdr in rules rule = rules[hdr] assert rule == e, f"unexpected rule {rule} in {rules}" found_module_rules = True for cluster in conf['static_resources']['clusters']: if 'httpbin' not in cluster['name']: continue http_protocol_options = cluster.get('http_protocol_options', None) if not http_protocol_options: if expect_norules: continue assert 'http_protocol_options' in cluster, \ f"'http_protocol_options' missing from cluster: {cluster}" if expect_norules: assert 'header_key_format' not in http_protocol_options, \ f"'header_key_format' found unexpected cluster: {cluster}" continue assert 'header_key_format' in http_protocol_options, \ f"'header_key_format' not found, cluster {cluster}" header_key_format = http_protocol_options['header_key_format'] assert 'custom' in header_key_format, \ f"'custom' not found, cluster {cluster}" rules = header_key_format['custom']['rules'] assert len(rules) == len(expectations) for e in expectations: hdr = e.lower() assert hdr in rules rule = rules[hdr] assert rule == e, f"unexpected rule {rule} in {rules}" found_cluster_rules = True if expect_norules: assert not found_module_rules assert not found_cluster_rules else: assert found_module_rules assert found_cluster_rules
def test_lookup(): aconf = Config() fetcher = ResourceFetcher(logger, aconf) fetcher.parse_yaml(yaml) aconf.load_all(fetcher.sorted()) secret_handler = NullSecretHandler(logger, None, None, "0") ir = IR(aconf, file_checker=lambda path: True, secret_handler=secret_handler) t1 = IRBuffer(ir, aconf, rkey='-foo-', name='buffer', max_request_bytes=4096) t2 = IRTestResource(ir, aconf, rkey='-foo-', name='buffer', max_request_bytes=8192) assert t1.lookup('max_request_bytes') == 4096 assert t1.lookup('max_request_bytes', 57) == 4096 assert t1.lookup('max_request_bytes2', 57) == 57 assert t1.lookup('max_request_words') == 1 assert t1.lookup('max_request_words', 77) == 1 assert t1.lookup('max_request_words', default_key='altered') == 2 assert t1.lookup('max_request_words', 77, default_key='altered') == 2 assert t1.lookup('max_request_words', default_key='altered2') == None assert t1.lookup('max_request_words', 77, default_key='altered2') == 77 assert t1.lookup('max_request_words', default_class='test_resource') == 3 assert t1.lookup('max_request_words', 77, default_class='test_resource') == 3 assert t1.lookup('max_request_words', 77, default_class='test_resource2') == 1 assert t1.lookup('max_request_words', default_key='altered', default_class='test_resource') == 4 assert t1.lookup('max_request_words', 77, default_key='altered', default_class='test_resource') == 4 assert t1.lookup('max_request_words', default_key='altered2', default_class='test_resource') == None assert t1.lookup('max_request_words', 77, default_key='altered2', default_class='test_resource') == 77 assert t1.lookup('funk') == None assert t1.lookup('funk', 77) == 77 assert t1.lookup('funk', default_class='test_resource') == 8 assert t1.lookup('funk', 77, default_class='test_resource') == 8 assert t1.lookup('funk', 77, default_class='test_resource2') == 77 assert t2.lookup('max_request_bytes') == 8192 assert t2.lookup('max_request_bytes', 57) == 8192 assert t2.lookup('max_request_bytes2', 57) == 57 assert t2.lookup('max_request_words') == 3 assert t2.lookup('max_request_words', 77) == 3 assert t2.lookup('max_request_words', default_key='altered') == 4 assert t2.lookup('max_request_words', 77, default_key='altered') == 4 assert t2.lookup('max_request_words', default_key='altered2') == None assert t2.lookup('max_request_words', 77, default_key='altered2') == 77 assert t2.lookup('max_request_words', default_class='/') == 1 assert t2.lookup('max_request_words', 77, default_class='/') == 1 assert t2.lookup('max_request_words', 77, default_class='/2') == 1 assert t2.lookup('max_request_words', default_key='altered', default_class='/') == 2 assert t2.lookup('max_request_words', 77, default_key='altered', default_class='/') == 2 assert t2.lookup('max_request_words', default_key='altered2', default_class='/') == None assert t2.lookup('max_request_words', 77, default_key='altered2', default_class='/') == 77 assert t2.lookup('funk') == 8 assert t2.lookup('funk', 77) == 8 assert t2.lookup('funk', default_class='test_resource') == 8 assert t2.lookup('funk', 77, default_class='test_resource') == 8 assert t2.lookup('funk', 77, default_class='test_resource2') == 77
def load_yaml(self, yaml_stream): self.aconf = Config() fetcher = ResourceFetcher(self.logger, self.aconf, watch_only=True) fetcher.parse_watt(yaml_stream.read()) self.aconf.load_all(fetcher.sorted()) # We can lift mappings straight from the aconf... mappings = self.aconf.get_config('mappings') or {} # ...but we need the fake IR to deal with resolvers and TLS contexts. self.fake = FakeIR(self.aconf, logger=self.logger) self.logger.debug("IR: %s" % self.fake.as_json()) resolvers = self.fake.resolvers contexts = self.fake.tls_contexts self.logger.debug(f'mappings: {len(mappings)}') self.logger.debug(f'resolvers: {len(resolvers)}') self.logger.debug(f'contexts: {len(contexts)}') global_resolver = self.fake.ambassador_module.get('resolver', None) global_label_selector = os.environ.get('AMBASSADOR_LABEL_SELECTOR', '') self.logger.debug('label-selector: %s' % global_label_selector) # watch the AES Secret if the edge stack is running if self.fake.edge_stack_allowed: aes_secret_name = os.getenv(ENV_AES_SECRET_NAME, DEFAULT_AES_SECRET_NAME) aes_secret_namespace = os.getenv(ENV_AES_SECRET_NAMESPACE, Config.ambassador_namespace) self.logger.debug(f'edge stack detected: need secret {aes_secret_name}.{aes_secret_namespace}') self.add_kube_watch(f'Secret {aes_secret_name}', 'secret', namespace=aes_secret_namespace, field_selector=f"metadata.name={aes_secret_name}") # Walk hosts. for host in self.fake.get_hosts(): sel = host.get('selector') or {} match_labels = sel.get('matchLabels') or {} label_selectors: List[str] = [] if global_label_selector: label_selectors.append(global_label_selector) if match_labels: label_selectors += [ f"{l}={v}" for l, v in match_labels.items() ] label_selector = ','.join(label_selectors) if label_selectors else None for wanted_kind in ['service', 'secret']: self.add_kube_watch(f"Host {host.name}", wanted_kind, host.namespace, label_selector=label_selector) for mname, mapping in mappings.items(): res_name = mapping.get('resolver', None) res_source = 'mapping' if not res_name: res_name = global_resolver res_source = 'defaults' ctx_name = mapping.get('tls', None) self.logger.debug( f'Mapping {mname}: resolver {res_name} from {res_source}, service {mapping.service}, tls {ctx_name}') if res_name: resolver = resolvers.get(res_name, None) self.logger.debug(f'-> resolver {resolver}') if resolver: svc = Service(logger, mapping.service, ctx_name) if resolver.kind == 'ConsulResolver': self.logger.debug(f'Mapping {mname} uses Consul resolver {res_name}') # At the moment, we stuff the resolver's datacenter into the association # ID for this watch. The ResourceFetcher relies on that. self.consul_watches.append( { "id": resolver.datacenter, "consul-address": resolver.address, "datacenter": resolver.datacenter, "service-name": svc.hostname } ) elif resolver.kind == 'KubernetesEndpointResolver': host = svc.hostname namespace = Config.ambassador_namespace if not host: # This is really kind of impossible. self.logger.error(f"KubernetesEndpointResolver {res_name} has no 'hostname'") continue if "." in host: (host, namespace) = host.split(".", 2)[0:2] self.logger.debug(f'...kube endpoints: svc {svc.hostname} -> host {host} namespace {namespace}') self.add_kube_watch(f"endpoint", "endpoints", namespace, label_selector=global_label_selector, field_selector=f"metadata.name={host}") for secret_key, secret_info in self.fake.secret_recorder.needed.items(): self.logger.debug(f'need secret {secret_info.name}.{secret_info.namespace}') self.add_kube_watch(f"needed secret", "secret", secret_info.namespace, label_selector=global_label_selector, field_selector=f"metadata.name={secret_info.name}") if self.fake.edge_stack_allowed: # If the edge stack is allowed, make sure we watch for our fallback context. self.add_kube_watch("Fallback TLSContext", "TLSContext", namespace=Config.ambassador_namespace) ambassador_basedir = os.environ.get('AMBASSADOR_CONFIG_BASE_DIR', '/ambassador') if os.path.exists(os.path.join(ambassador_basedir, '.ambassadorinstallations_ok')): self.add_kube_watch("AmbassadorInstallations", "ambassadorinstallations.getambassador.io", Config.ambassador_namespace) ambassador_knative_requested = (os.environ.get("AMBASSADOR_KNATIVE_SUPPORT", "-unset-").lower() == 'true') if ambassador_knative_requested: self.logger.debug('Looking for Knative support...') if os.path.exists(os.path.join(ambassador_basedir, '.knative_clusteringress_ok')): # Watch for clusteringresses.networking.internal.knative.dev in any namespace and with any labels. self.logger.debug('watching for clusteringresses.networking.internal.knative.dev') self.add_kube_watch("Knative clusteringresses", "clusteringresses.networking.internal.knative.dev", None) if os.path.exists(os.path.join(ambassador_basedir, '.knative_ingress_ok')): # Watch for ingresses.networking.internal.knative.dev in any namespace and # with any labels. self.add_kube_watch("Knative ingresses", "ingresses.networking.internal.knative.dev", None) self.watchset = { "kubernetes-watches": self.kube_watches, "consul-watches": self.consul_watches } save_dir = os.environ.get('AMBASSADOR_WATCH_DIR', '/tmp') if save_dir: watchset = dump_json(self.watchset) with open(os.path.join(save_dir, 'watch.json'), "w") as output: output.write(watchset)
# Never cache anything. def cache_secret(self, context: 'IRTLSContext', secret_info: SecretInfo): return SavedSecret(secret_info.name, secret_info.namespace, '-crt-path-', '-key-path-', { 'tls_crt': '-crt-', 'tls_key': '-key-' }) scc = SecretHandler(logger, "test-dump", "ss") yamlpath = sys.argv[1] if len(sys.argv) > 1 else "consul-3.yaml" aconf = Config() fetcher = ResourceFetcher(logger, aconf) fetcher.parse_watt(open(yamlpath, "r").read()) aconf.load_all(fetcher.sorted()) open("test-aconf.json", "w").write(aconf.as_json()) # sys.exit(0) ir = IR(aconf, secret_handler=scc) open("test-ir.json", "w").write(ir.as_json()) econf = V2Config(ir) open("test-v2.json", "w").write(econf.as_json())
def test_qualify_service(): aconf = Config() fetcher = ResourceFetcher(logger, aconf) fetcher.parse_yaml(yaml) aconf.load_all(fetcher.sorted()) secret_handler = NullSecretHandler(logger, None, None, "0") ir = IR(aconf, file_checker=lambda path: True, secret_handler=secret_handler) assert ir, "could not create an IR" assert qualify_service_name(ir, "backoffice", None) == "backoffice" assert qualify_service_name(ir, "backoffice", "default") == "backoffice" assert qualify_service_name(ir, "backoffice", "otherns") == "backoffice.otherns" assert qualify_service_name(ir, "backoffice.otherns", None) == "backoffice.otherns" assert qualify_service_name(ir, "backoffice.otherns", "default") == "backoffice.otherns" assert qualify_service_name(ir, "backoffice.otherns", "otherns") == "backoffice.otherns" assert qualify_service_name(ir, "backoffice:80", None) == "backoffice:80" assert qualify_service_name(ir, "backoffice:80", "default") == "backoffice:80" assert qualify_service_name(ir, "backoffice:80", "otherns") == "backoffice.otherns:80" assert qualify_service_name(ir, "backoffice.otherns:80", None) == "backoffice.otherns:80" assert qualify_service_name(ir, "backoffice.otherns:80", "default") == "backoffice.otherns:80" assert qualify_service_name(ir, "backoffice.otherns:80", "otherns") == "backoffice.otherns:80" assert qualify_service_name(ir, "http://backoffice", None) == "http://backoffice" assert qualify_service_name(ir, "http://backoffice", "default") == "http://backoffice" assert qualify_service_name(ir, "http://backoffice", "otherns") == "http://backoffice.otherns" assert qualify_service_name(ir, "http://backoffice.otherns", None) == "http://backoffice.otherns" assert qualify_service_name(ir, "http://backoffice.otherns", "default") == "http://backoffice.otherns" assert qualify_service_name(ir, "http://backoffice.otherns", "otherns") == "http://backoffice.otherns" assert qualify_service_name(ir, "http://backoffice:80", None) == "http://backoffice:80" assert qualify_service_name(ir, "http://backoffice:80", "default") == "http://backoffice:80" assert qualify_service_name(ir, "http://backoffice:80", "otherns") == "http://backoffice.otherns:80" assert qualify_service_name(ir, "http://backoffice.otherns:80", None) == "http://backoffice.otherns:80" assert qualify_service_name(ir, "http://backoffice.otherns:80", "default") == "http://backoffice.otherns:80" assert qualify_service_name(ir, "http://backoffice.otherns:80", "otherns") == "http://backoffice.otherns:80" assert qualify_service_name(ir, "https://backoffice", None) == "https://backoffice" assert qualify_service_name(ir, "https://backoffice", "default") == "https://backoffice" assert qualify_service_name(ir, "https://backoffice", "otherns") == "https://backoffice.otherns" assert qualify_service_name(ir, "https://backoffice.otherns", None) == "https://backoffice.otherns" assert qualify_service_name(ir, "https://backoffice.otherns", "default") == "https://backoffice.otherns" assert qualify_service_name(ir, "https://backoffice.otherns", "otherns") == "https://backoffice.otherns" assert qualify_service_name(ir, "https://backoffice:443", None) == "https://backoffice:443" assert qualify_service_name(ir, "https://backoffice:443", "default") == "https://backoffice:443" assert qualify_service_name(ir, "https://backoffice:443", "otherns") == "https://backoffice.otherns:443" assert qualify_service_name(ir, "https://backoffice.otherns:443", None) == "https://backoffice.otherns:443" assert qualify_service_name(ir, "https://backoffice.otherns:443", "default") == "https://backoffice.otherns:443" assert qualify_service_name(ir, "https://backoffice.otherns:443", "otherns") == "https://backoffice.otherns:443" assert qualify_service_name(ir, "localhost", None) == "localhost" assert qualify_service_name(ir, "localhost", "default") == "localhost" assert qualify_service_name(ir, "localhost", "otherns") == "localhost" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert qualify_service_name(ir, "localhost.otherns", None) == "localhost.otherns" assert qualify_service_name(ir, "localhost.otherns", "default") == "localhost.otherns" assert qualify_service_name(ir, "localhost.otherns", "otherns") == "localhost.otherns" assert qualify_service_name(ir, "localhost:80", None) == "localhost:80" assert qualify_service_name(ir, "localhost:80", "default") == "localhost:80" assert qualify_service_name(ir, "localhost:80", "otherns") == "localhost:80" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert qualify_service_name(ir, "localhost.otherns:80", None) == "localhost.otherns:80" assert qualify_service_name(ir, "localhost.otherns:80", "default") == "localhost.otherns:80" assert qualify_service_name(ir, "localhost.otherns:80", "otherns") == "localhost.otherns:80" assert qualify_service_name(ir, "http://localhost", None) == "http://localhost" assert qualify_service_name(ir, "http://localhost", "default") == "http://localhost" assert qualify_service_name(ir, "http://localhost", "otherns") == "http://localhost" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert qualify_service_name(ir, "http://localhost.otherns", None) == "http://localhost.otherns" assert qualify_service_name(ir, "http://localhost.otherns", "default") == "http://localhost.otherns" assert qualify_service_name(ir, "http://localhost.otherns", "otherns") == "http://localhost.otherns" assert qualify_service_name(ir, "http://localhost:80", None) == "http://localhost:80" assert qualify_service_name(ir, "http://localhost:80", "default") == "http://localhost:80" assert qualify_service_name(ir, "http://localhost:80", "otherns") == "http://localhost:80" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert qualify_service_name(ir, "http://localhost.otherns:80", None) == "http://localhost.otherns:80" assert qualify_service_name(ir, "http://localhost.otherns:80", "default") == "http://localhost.otherns:80" assert qualify_service_name(ir, "http://localhost.otherns:80", "otherns") == "http://localhost.otherns:80" assert qualify_service_name(ir, "https://localhost", None) == "https://localhost" assert qualify_service_name(ir, "https://localhost", "default") == "https://localhost" assert qualify_service_name(ir, "https://localhost", "otherns") == "https://localhost" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert qualify_service_name(ir, "https://localhost.otherns", None) == "https://localhost.otherns" assert qualify_service_name(ir, "https://localhost.otherns", "default") == "https://localhost.otherns" assert qualify_service_name(ir, "https://localhost.otherns", "otherns") == "https://localhost.otherns" assert qualify_service_name(ir, "https://localhost:443", None) == "https://localhost:443" assert qualify_service_name(ir, "https://localhost:443", "default") == "https://localhost:443" assert qualify_service_name(ir, "https://localhost:443", "otherns") == "https://localhost:443" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert qualify_service_name(ir, "https://localhost.otherns:443", None) == "https://localhost.otherns:443" assert qualify_service_name(ir, "https://localhost.otherns:443", "default") == "https://localhost.otherns:443" assert qualify_service_name(ir, "https://localhost.otherns:443", "otherns") == "https://localhost.otherns:443" assert qualify_service_name(ir, "https://bad-service:443:443", "otherns") == "https://bad-service:443:443" assert qualify_service_name( ir, "https://bad-service:443:443", "otherns", rkey="test-rkey") == "https://bad-service:443:443" errors = ir.aconf.errors assert "-global-" in errors errors = errors["-global-"] assert len(errors) == 2 assert not errors[0]["ok"] assert errors[0][ "error"] == "Malformed service port in https://bad-service:443:443" assert not errors[1]["ok"] assert errors[1][ "error"] == "test-rkey: Malformed service port in https://bad-service:443:443"
def test_shadow_v2(): aconf = Config() yaml = ''' --- apiVersion: x.getambassador.io/v3alpha1 kind: AmbassadorListener metadata: name: ambassador-listener-8080 namespace: default spec: port: 8080 protocol: HTTPS securityModel: XFP hostBinding: namespace: from: ALL --- apiVersion: x.getambassador.io/v3alpha1 kind: AmbassadorMapping metadata: name: httpbin-mapping namespace: default spec: service: httpbin hostname: "*" prefix: /httpbin/ --- apiVersion: x.getambassador.io/v3alpha1 kind: AmbassadorMapping metadata: name: httpbin-mapping-shadow namespace: default spec: service: httpbin-shadow hostname: "*" prefix: /httpbin/ shadow: true weight: 10 ''' fetcher = ResourceFetcher(logger, aconf) fetcher.parse_yaml(yaml, k8s=True) aconf.load_all(fetcher.sorted()) secret_handler = MockSecretHandler(logger, "mockery", "/tmp/ambassador/snapshots", "v1") ir = IR(aconf, file_checker=lambda path: True, secret_handler=secret_handler) assert ir econf = EnvoyConfig.generate(ir, "V2") bootstrap_config, ads_config, _ = econf.split_config() ads_config.pop('@type', None) mirrored_config = get_mirrored_config(ads_config) assert 'request_mirror_policy' in mirrored_config['route'] mirror_policy = mirrored_config['route']['request_mirror_policy'] assert mirror_policy['cluster'] == 'cluster_shadow_httpbin_shadow_default' assert mirror_policy['runtime_fraction']['default_value']['numerator'] == 10 assert mirror_policy['runtime_fraction']['default_value']['denominator'] == 'HUNDRED' assert_valid_envoy_config(ads_config) assert_valid_envoy_config(bootstrap_config)
def dump(config_dir_path: Parameter.REQUIRED, *, secret_dir_path=None, watt=False, debug=False, debug_scout=False, k8s=False, recurse=False, aconf=False, ir=False, v2=False, diag=False, features=False): """ Dump various forms of an Ambassador configuration for debugging Use --aconf, --ir, and --envoy to control what gets dumped. If none are requested, the IR will be dumped. :param config_dir_path: Configuration directory to scan for Ambassador YAML files :param secret_dir_path: Directory into which to save secrets :param watt: If set, input must be a WATT snapshot :param debug: If set, generate debugging output :param debug_scout: If set, generate debugging output :param k8s: If set, assume configuration files are annotated K8s manifests :param recurse: If set, recurse into directories below config_dir_path :param aconf: If set, dump the Ambassador config :param ir: If set, dump the IR :param v2: If set, dump the Envoy V2 config :param diag: If set, dump the Diagnostics overview :param features: If set, dump the feature set """ if not secret_dir_path: secret_dir_path = config_dir_path if not os.path.isdir(secret_dir_path): secret_dir_path = os.path.dirname(secret_dir_path) if debug: logger.setLevel(logging.DEBUG) if debug_scout: logging.getLogger('ambassador.scout').setLevel(logging.DEBUG) if not (aconf or ir or v2 or diag or features): aconf = True ir = True v2 = True diag = False features = False dump_aconf = aconf dump_ir = ir dump_v2 = v2 dump_diag = diag dump_features = features od = {} diagconfig: Optional[EnvoyConfig] = None try: aconf = Config() fetcher = ResourceFetcher(logger, aconf) if watt: fetcher.parse_watt(open(config_dir_path, "r").read()) else: fetcher.load_from_filesystem(config_dir_path, k8s=k8s, recurse=True) aconf.load_all(fetcher.sorted()) # aconf.post_error("Error from string, boo yah") # aconf.post_error(RichStatus.fromError("Error from RichStatus")) if dump_aconf: od['aconf'] = aconf.as_dict() secret_handler = NullSecretHandler(logger, config_dir_path, secret_dir_path, "0") ir = IR(aconf, file_checker=file_checker, secret_handler=secret_handler) if dump_ir: od['ir'] = ir.as_dict() if dump_v2: v2config = V2Config(ir) diagconfig = v2config od['v2'] = v2config.as_dict() if dump_diag: if not diagconfig: diagconfig = V2Config(ir) econf = typecast(EnvoyConfig, diagconfig) diag = Diagnostics(ir, econf) od['diag'] = diag.as_dict() od['elements'] = econf.elements if dump_features: od['features'] = ir.features() # scout = Scout() # scout_args = {} # # if ir and not os.environ.get("AMBASSADOR_DISABLE_FEATURES", None): # scout_args["features"] = ir.features() # # result = scout.report(action="dump", mode="cli", **scout_args) # show_notices(result) json.dump(od, sys.stdout, sort_keys=True, indent=4) sys.stdout.write("\n") except Exception as e: handle_exception("EXCEPTION from dump", e, config_dir_path=config_dir_path) # This is fatal. sys.exit(1)
def main(k8s_yaml_paths: List[str], debug: bool, force_pod_labels: bool, update: bool, source: List[str], labels: List[str], namespace: Optional[str], watch: str, include_ir: bool, include_aconf: bool, diff_path: Optional[str] = None, kat_name: Optional[str] = None) -> None: loglevel = logging.DEBUG if debug else logging.INFO logging.basicConfig( level=loglevel, format="%(asctime)s mockery %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") logger = logging.getLogger('mockery') logger.debug(f"reading from {k8s_yaml_paths}") if not source: source = [ "Host", "service", "ingresses", "AuthService", "LogService", "Mapping", "Module", "RateLimitService", "TCPMapping", "TLSContext", "TracingService", "ConsulResolver", "KubernetesEndpointResolver", "KubernetesServiceResolver" ] if namespace: os.environ['AMBASSADOR_NAMESPACE'] = namespace # Make labels a list, instead of a tuple. labels = list(labels) labels_to_force = {l: True for l in labels or []} if kat_name: logger.debug(f"KAT name {kat_name}") # First set up some labels to force. labels_to_force["scope=AmbassadorTest"] = True labels_to_force[f"service={kat_name}"] = True kat_amb_id_label = f"kat-ambassador-id={kat_name}" if kat_amb_id_label not in labels_to_force: labels_to_force[kat_amb_id_label] = True labels.append(kat_amb_id_label) os.environ['AMBASSADOR_ID'] = kat_name # Forcibly override the cached ambassador_id. Config.ambassador_id = kat_name logger.debug(f"namespace {namespace or '*'}") logger.debug(f"labels to watch {', '.join(labels)}") logger.debug( f"labels to force {', '.join(sorted(labels_to_force.keys()))}") logger.debug(f"watch hook {watch}") logger.debug(f"sources {', '.join(source)}") for key in sorted(os.environ.keys()): if key.startswith('AMBASSADOR'): logger.debug(f"${key}={os.environ[key]}") if force_pod_labels: try: os.makedirs("/tmp/ambassador-pod-info") except OSError as e: if e.errno != errno.EEXIST: raise with open("/tmp/ambassador-pod-info/labels", "w", encoding="utf-8") as outfile: for l in labels_to_force: outfile.write(l) outfile.write("\n") # Pull in the YAML. input_yaml = ''.join([open(x, "r").read() for x in k8s_yaml_paths]) manifest = parse_yaml(input_yaml) w = Mockery(logger, debug, source, ",".join(labels), namespace, watch) iteration = 0 while True: iteration += 1 if iteration > 10: print(f"!!!! Not stable after 10 iterations, failing") logger.error("Not stable after 10 iterations, failing") sys.exit(1) logger.info(f"======== START ITERATION {iteration}") w.load(manifest) logger.info(f"WATT_K8S: {w.snapshot}") hook_ok, any_changes = w.run_hook() if not hook_ok: raise Exception("hook failed") if any_changes: logger.info( f"======== END ITERATION {iteration}: watches changed!") else: logger.info(f"======== END ITERATION {iteration}: stable!") break # Once here, we should be good to go. try: os.makedirs("/tmp/ambassador/snapshots") except OSError as e: if e.errno != errno.EEXIST: raise scc = MockSecretHandler(logger, "mockery", "/tmp/ambassador/snapshots", f"v{iteration}") aconf = Config() logger.debug(f"Config.ambassador_id {Config.ambassador_id}") logger.debug(f"Config.ambassador_namespace {Config.ambassador_namespace}") logger.info(f"STABLE WATT_K8S: {w.snapshot}") fetcher = ResourceFetcher(logger, aconf) fetcher.parse_watt(w.snapshot) aconf.load_all(fetcher.sorted()) open("/tmp/ambassador/snapshots/aconf.json", "w", encoding="utf-8").write(aconf.as_json()) ir = IR(aconf, secret_handler=scc) open("/tmp/ambassador/snapshots/ir.json", "w", encoding="utf-8").write(ir.as_json()) econf = EnvoyConfig.generate(ir, "V2") bootstrap_config, ads_config, clustermap = econf.split_config() ads_config.pop('@type', None) with open("/tmp/ambassador/snapshots/econf.json", "w", encoding="utf-8") as outfile: outfile.write(dump_json(ads_config, pretty=True)) with open("/tmp/ambassador/snapshots/bootstrap.json", "w", encoding="utf-8") as outfile: outfile.write(dump_json(bootstrap_config, pretty=True)) diag = Diagnostics(ir, econf) with open("/tmp/ambassador/snapshots/diag.json", "w", encoding="utf-8") as outfile: outfile.write(dump_json(diag.as_dict(), pretty=True)) if diff_path: diffs = False pairs_to_check = [(os.path.join(diff_path, 'snapshots', 'econf.json'), '/tmp/ambassador/snapshots/econf.json'), (os.path.join(diff_path, 'bootstrap-ads.json'), '/tmp/ambassador/snapshots/bootstrap.json')] if include_ir: pairs_to_check.append( (os.path.join(diff_path, 'snapshots', 'ir.json'), '/tmp/ambassador/snapshots/ir.json')) if include_aconf: pairs_to_check.append((os.path.join(diff_path, 'snapshots', 'aconf.json'), '/tmp/ambassador/snapshots/aconf.json')) for gold_path, check_path in pairs_to_check: if update: logger.info(f"mv {check_path} {gold_path}") shutil.move(check_path, gold_path) elif not filecmp.cmp(gold_path, check_path): diffs = True gold_lines = open(gold_path, "r", encoding="utf-8").readlines() check_lines = open(check_path, "r", encoding="utf-8").readlines() for line in difflib.unified_diff(gold_lines, check_lines, fromfile=gold_path, tofile=check_path): sys.stdout.write(line) if diffs: sys.exit(1)
def test_qualify_service(): aconf = Config() fetcher = ResourceFetcher(logger, aconf) fetcher.parse_yaml(yaml) aconf.load_all(fetcher.sorted()) secret_handler = NullSecretHandler(logger, None, None, "0") ir = IR(aconf, file_checker=lambda path: True, secret_handler=secret_handler) assert ir, "could not create an IR" assert qualify_service_name(ir, "backoffice", None) == "backoffice" assert qualify_service_name(ir, "backoffice", "default") == "backoffice" assert qualify_service_name(ir, "backoffice", "otherns") == "backoffice.otherns" assert qualify_service_name(ir, "backoffice.otherns", None) == "backoffice.otherns" assert qualify_service_name(ir, "backoffice.otherns", "default") == "backoffice.otherns" assert qualify_service_name(ir, "backoffice.otherns", "otherns") == "backoffice.otherns" assert normalize_service_name(ir, "backoffice", None, 'ConsulResolver') == "backoffice" assert normalize_service_name(ir, "backoffice", "default", 'ConsulResolver') == "backoffice" assert normalize_service_name(ir, "backoffice", "otherns", 'ConsulResolver') == "backoffice" assert normalize_service_name(ir, "backoffice.otherns", None, 'ConsulResolver') == "backoffice.otherns" assert normalize_service_name(ir, "backoffice.otherns", "default", 'ConsulResolver') == "backoffice.otherns" assert normalize_service_name(ir, "backoffice.otherns", "otherns", 'ConsulResolver') == "backoffice.otherns" assert qualify_service_name(ir, "backoffice:80", None) == "backoffice:80" assert qualify_service_name(ir, "backoffice:80", "default") == "backoffice:80" assert qualify_service_name(ir, "backoffice:80", "otherns") == "backoffice.otherns:80" assert qualify_service_name(ir, "backoffice.otherns:80", None) == "backoffice.otherns:80" assert qualify_service_name(ir, "backoffice.otherns:80", "default") == "backoffice.otherns:80" assert qualify_service_name(ir, "backoffice.otherns:80", "otherns") == "backoffice.otherns:80" assert normalize_service_name(ir, "backoffice:80", None, 'ConsulResolver') == "backoffice:80" assert normalize_service_name(ir, "backoffice:80", "default", 'ConsulResolver') == "backoffice:80" assert normalize_service_name(ir, "backoffice:80", "otherns", 'ConsulResolver') == "backoffice:80" assert normalize_service_name(ir, "backoffice.otherns:80", None, 'ConsulResolver') == "backoffice.otherns:80" assert normalize_service_name(ir, "backoffice.otherns:80", "default", 'ConsulResolver') == "backoffice.otherns:80" assert normalize_service_name(ir, "backoffice.otherns:80", "otherns", 'ConsulResolver') == "backoffice.otherns:80" assert qualify_service_name(ir, "http://backoffice", None) == "http://backoffice" assert qualify_service_name(ir, "http://backoffice", "default") == "http://backoffice" assert qualify_service_name(ir, "http://backoffice", "otherns") == "http://backoffice.otherns" assert qualify_service_name(ir, "http://backoffice.otherns", None) == "http://backoffice.otherns" assert qualify_service_name(ir, "http://backoffice.otherns", "default") == "http://backoffice.otherns" assert qualify_service_name(ir, "http://backoffice.otherns", "otherns") == "http://backoffice.otherns" assert normalize_service_name(ir, "http://backoffice", None, 'ConsulResolver') == "http://backoffice" assert normalize_service_name(ir, "http://backoffice", "default", 'ConsulResolver') == "http://backoffice" assert normalize_service_name(ir, "http://backoffice", "otherns", 'ConsulResolver') == "http://backoffice" assert normalize_service_name( ir, "http://backoffice.otherns", None, 'ConsulResolver') == "http://backoffice.otherns" assert normalize_service_name( ir, "http://backoffice.otherns", "default", 'ConsulResolver') == "http://backoffice.otherns" assert normalize_service_name( ir, "http://backoffice.otherns", "otherns", 'ConsulResolver') == "http://backoffice.otherns" assert qualify_service_name(ir, "http://backoffice:80", None) == "http://backoffice:80" assert qualify_service_name(ir, "http://backoffice:80", "default") == "http://backoffice:80" assert qualify_service_name(ir, "http://backoffice:80", "otherns") == "http://backoffice.otherns:80" assert qualify_service_name(ir, "http://backoffice.otherns:80", None) == "http://backoffice.otherns:80" assert qualify_service_name(ir, "http://backoffice.otherns:80", "default") == "http://backoffice.otherns:80" assert qualify_service_name(ir, "http://backoffice.otherns:80", "otherns") == "http://backoffice.otherns:80" assert normalize_service_name(ir, "http://backoffice:80", None, 'ConsulResolver') == "http://backoffice:80" assert normalize_service_name(ir, "http://backoffice:80", "default", 'ConsulResolver') == "http://backoffice:80" assert normalize_service_name(ir, "http://backoffice:80", "otherns", 'ConsulResolver') == "http://backoffice:80" assert normalize_service_name( ir, "http://backoffice.otherns:80", None, 'ConsulResolver') == "http://backoffice.otherns:80" assert normalize_service_name( ir, "http://backoffice.otherns:80", "default", 'ConsulResolver') == "http://backoffice.otherns:80" assert normalize_service_name( ir, "http://backoffice.otherns:80", "otherns", 'ConsulResolver') == "http://backoffice.otherns:80" assert qualify_service_name(ir, "https://backoffice", None) == "https://backoffice" assert qualify_service_name(ir, "https://backoffice", "default") == "https://backoffice" assert qualify_service_name(ir, "https://backoffice", "otherns") == "https://backoffice.otherns" assert qualify_service_name(ir, "https://backoffice.otherns", None) == "https://backoffice.otherns" assert qualify_service_name(ir, "https://backoffice.otherns", "default") == "https://backoffice.otherns" assert qualify_service_name(ir, "https://backoffice.otherns", "otherns") == "https://backoffice.otherns" assert normalize_service_name(ir, "https://backoffice", None, 'ConsulResolver') == "https://backoffice" assert normalize_service_name(ir, "https://backoffice", "default", 'ConsulResolver') == "https://backoffice" assert normalize_service_name(ir, "https://backoffice", "otherns", 'ConsulResolver') == "https://backoffice" assert normalize_service_name( ir, "https://backoffice.otherns", None, 'ConsulResolver') == "https://backoffice.otherns" assert normalize_service_name( ir, "https://backoffice.otherns", "default", 'ConsulResolver') == "https://backoffice.otherns" assert normalize_service_name( ir, "https://backoffice.otherns", "otherns", 'ConsulResolver') == "https://backoffice.otherns" assert qualify_service_name(ir, "https://backoffice:443", None) == "https://backoffice:443" assert qualify_service_name(ir, "https://backoffice:443", "default") == "https://backoffice:443" assert qualify_service_name(ir, "https://backoffice:443", "otherns") == "https://backoffice.otherns:443" assert qualify_service_name(ir, "https://backoffice.otherns:443", None) == "https://backoffice.otherns:443" assert qualify_service_name(ir, "https://backoffice.otherns:443", "default") == "https://backoffice.otherns:443" assert qualify_service_name(ir, "https://backoffice.otherns:443", "otherns") == "https://backoffice.otherns:443" assert normalize_service_name(ir, "https://backoffice:443", None, 'ConsulResolver') == "https://backoffice:443" assert normalize_service_name(ir, "https://backoffice:443", "default", 'ConsulResolver') == "https://backoffice:443" assert normalize_service_name(ir, "https://backoffice:443", "otherns", 'ConsulResolver') == "https://backoffice:443" assert normalize_service_name( ir, "https://backoffice.otherns:443", None, 'ConsulResolver') == "https://backoffice.otherns:443" assert normalize_service_name( ir, "https://backoffice.otherns:443", "default", 'ConsulResolver') == "https://backoffice.otherns:443" assert normalize_service_name( ir, "https://backoffice.otherns:443", "otherns", 'ConsulResolver') == "https://backoffice.otherns:443" assert qualify_service_name(ir, "localhost", None) == "localhost" assert qualify_service_name(ir, "localhost", "default") == "localhost" assert qualify_service_name(ir, "localhost", "otherns") == "localhost" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert qualify_service_name(ir, "localhost.otherns", None) == "localhost.otherns" assert qualify_service_name(ir, "localhost.otherns", "default") == "localhost.otherns" assert qualify_service_name(ir, "localhost.otherns", "otherns") == "localhost.otherns" assert normalize_service_name(ir, "localhost", None, 'ConsulResolver') == "localhost" assert normalize_service_name(ir, "localhost", "default", 'ConsulResolver') == "localhost" assert normalize_service_name(ir, "localhost", "otherns", 'ConsulResolver') == "localhost" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert normalize_service_name(ir, "localhost.otherns", None, 'ConsulResolver') == "localhost.otherns" assert normalize_service_name(ir, "localhost.otherns", "default", 'ConsulResolver') == "localhost.otherns" assert normalize_service_name(ir, "localhost.otherns", "otherns", 'ConsulResolver') == "localhost.otherns" assert qualify_service_name(ir, "localhost:80", None) == "localhost:80" assert qualify_service_name(ir, "localhost:80", "default") == "localhost:80" assert qualify_service_name(ir, "localhost:80", "otherns") == "localhost:80" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert qualify_service_name(ir, "localhost.otherns:80", None) == "localhost.otherns:80" assert qualify_service_name(ir, "localhost.otherns:80", "default") == "localhost.otherns:80" assert qualify_service_name(ir, "localhost.otherns:80", "otherns") == "localhost.otherns:80" assert normalize_service_name(ir, "localhost:80", None, 'ConsulResolver') == "localhost:80" assert normalize_service_name(ir, "localhost:80", "default", 'ConsulResolver') == "localhost:80" assert normalize_service_name(ir, "localhost:80", "otherns", 'ConsulResolver') == "localhost:80" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert normalize_service_name(ir, "localhost.otherns:80", None, 'ConsulResolver') == "localhost.otherns:80" assert normalize_service_name(ir, "localhost.otherns:80", "default", 'ConsulResolver') == "localhost.otherns:80" assert normalize_service_name(ir, "localhost.otherns:80", "otherns", 'ConsulResolver') == "localhost.otherns:80" assert qualify_service_name(ir, "http://localhost", None) == "http://localhost" assert qualify_service_name(ir, "http://localhost", "default") == "http://localhost" assert qualify_service_name(ir, "http://localhost", "otherns") == "http://localhost" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert qualify_service_name(ir, "http://localhost.otherns", None) == "http://localhost.otherns" assert qualify_service_name(ir, "http://localhost.otherns", "default") == "http://localhost.otherns" assert qualify_service_name(ir, "http://localhost.otherns", "otherns") == "http://localhost.otherns" assert normalize_service_name(ir, "http://localhost", None, 'ConsulResolver') == "http://localhost" assert normalize_service_name(ir, "http://localhost", "default", 'ConsulResolver') == "http://localhost" assert normalize_service_name(ir, "http://localhost", "otherns", 'ConsulResolver') == "http://localhost" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert normalize_service_name( ir, "http://localhost.otherns", None, 'ConsulResolver') == "http://localhost.otherns" assert normalize_service_name( ir, "http://localhost.otherns", "default", 'ConsulResolver') == "http://localhost.otherns" assert normalize_service_name( ir, "http://localhost.otherns", "otherns", 'ConsulResolver') == "http://localhost.otherns" assert qualify_service_name(ir, "http://localhost:80", None) == "http://localhost:80" assert qualify_service_name(ir, "http://localhost:80", "default") == "http://localhost:80" assert qualify_service_name(ir, "http://localhost:80", "otherns") == "http://localhost:80" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert qualify_service_name(ir, "http://localhost.otherns:80", None) == "http://localhost.otherns:80" assert qualify_service_name(ir, "http://localhost.otherns:80", "default") == "http://localhost.otherns:80" assert qualify_service_name(ir, "http://localhost.otherns:80", "otherns") == "http://localhost.otherns:80" assert normalize_service_name(ir, "http://localhost:80", None, 'ConsulResolver') == "http://localhost:80" assert normalize_service_name(ir, "http://localhost:80", "default", 'ConsulResolver') == "http://localhost:80" assert normalize_service_name(ir, "http://localhost:80", "otherns", 'ConsulResolver') == "http://localhost:80" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert normalize_service_name( ir, "http://localhost.otherns:80", None, 'ConsulResolver') == "http://localhost.otherns:80" assert normalize_service_name( ir, "http://localhost.otherns:80", "default", 'ConsulResolver') == "http://localhost.otherns:80" assert normalize_service_name( ir, "http://localhost.otherns:80", "otherns", 'ConsulResolver') == "http://localhost.otherns:80" assert qualify_service_name(ir, "https://localhost", None) == "https://localhost" assert qualify_service_name(ir, "https://localhost", "default") == "https://localhost" assert qualify_service_name(ir, "https://localhost", "otherns") == "https://localhost" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert qualify_service_name(ir, "https://localhost.otherns", None) == "https://localhost.otherns" assert qualify_service_name(ir, "https://localhost.otherns", "default") == "https://localhost.otherns" assert qualify_service_name(ir, "https://localhost.otherns", "otherns") == "https://localhost.otherns" assert normalize_service_name(ir, "https://localhost", None, 'ConsulResolver') == "https://localhost" assert normalize_service_name(ir, "https://localhost", "default", 'ConsulResolver') == "https://localhost" assert normalize_service_name(ir, "https://localhost", "otherns", 'ConsulResolver') == "https://localhost" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert normalize_service_name( ir, "https://localhost.otherns", None, 'ConsulResolver') == "https://localhost.otherns" assert normalize_service_name( ir, "https://localhost.otherns", "default", 'ConsulResolver') == "https://localhost.otherns" assert normalize_service_name( ir, "https://localhost.otherns", "otherns", 'ConsulResolver') == "https://localhost.otherns" assert qualify_service_name(ir, "https://localhost:443", None) == "https://localhost:443" assert qualify_service_name(ir, "https://localhost:443", "default") == "https://localhost:443" assert qualify_service_name(ir, "https://localhost:443", "otherns") == "https://localhost:443" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert qualify_service_name(ir, "https://localhost.otherns:443", None) == "https://localhost.otherns:443" assert qualify_service_name(ir, "https://localhost.otherns:443", "default") == "https://localhost.otherns:443" assert qualify_service_name(ir, "https://localhost.otherns:443", "otherns") == "https://localhost.otherns:443" assert normalize_service_name(ir, "https://localhost:443", None, 'ConsulResolver') == "https://localhost:443" assert normalize_service_name(ir, "https://localhost:443", "default", 'ConsulResolver') == "https://localhost:443" assert normalize_service_name(ir, "https://localhost:443", "otherns", 'ConsulResolver') == "https://localhost:443" # It's not meaningful to actually say "localhost.otherns", but it should passed through unchanged. assert normalize_service_name( ir, "https://localhost.otherns:443", None, 'ConsulResolver') == "https://localhost.otherns:443" assert normalize_service_name( ir, "https://localhost.otherns:443", "default", 'ConsulResolver') == "https://localhost.otherns:443" assert normalize_service_name( ir, "https://localhost.otherns:443", "otherns", 'ConsulResolver') == "https://localhost.otherns:443" assert qualify_service_name( ir, "ambassador://foo.ns", "otherns" ) == "ambassador://foo.ns" # let's not introduce silly semantics assert qualify_service_name( ir, "//foo.ns:1234", "otherns" ) == "foo.ns:1234" # we tell people "URL-ish", actually support URL-ish assert qualify_service_name(ir, "foo.ns:1234", "otherns") == "foo.ns:1234" assert normalize_service_name( ir, "ambassador://foo.ns", "otherns", 'ConsulResolver' ) == "ambassador://foo.ns" # let's not introduce silly semantics assert normalize_service_name( ir, "//foo.ns:1234", "otherns", 'ConsulResolver' ) == "foo.ns:1234" # we tell people "URL-ish", actually support URL-ish assert normalize_service_name(ir, "foo.ns:1234", "otherns", 'ConsulResolver') == "foo.ns:1234" errors = ir.aconf.errors assert not errors assert qualify_service_name(ir, "https://bad-service:443:443", "otherns") == "https://bad-service:443:443" assert qualify_service_name( ir, "https://bad-service:443:443", "otherns", rkey="test-rkey") == "https://bad-service:443:443" assert qualify_service_name(ir, "bad-service:443:443", "otherns") == "bad-service:443:443" assert qualify_service_name( ir, "https://[fe80::e022:9cff:fecc:c7c4:443", "otherns") == "https://[fe80::e022:9cff:fecc:c7c4:443" assert qualify_service_name( ir, "https://[fe80::e022:9cff:fecc:c7c4", "otherns") == "https://[fe80::e022:9cff:fecc:c7c4" assert qualify_service_name( ir, "https://fe80::e022:9cff:fecc:c7c4", "otherns") == "https://fe80::e022:9cff:fecc:c7c4" assert qualify_service_name(ir, "https://bad-service:-1", "otherns") == "https://bad-service:-1" assert qualify_service_name(ir, "https://bad-service:70000", "otherns") == "https://bad-service:70000" assert normalize_service_name( ir, "https://bad-service:443:443", "otherns", 'ConsulResolver') == "https://bad-service:443:443" assert normalize_service_name( ir, "https://bad-service:443:443", "otherns", 'ConsulResolver', rkey="test-rkey") == "https://bad-service:443:443" assert normalize_service_name(ir, "bad-service:443:443", "otherns", 'ConsulResolver') == "bad-service:443:443" assert normalize_service_name( ir, "https://[fe80::e022:9cff:fecc:c7c4:443", "otherns", 'ConsulResolver') == "https://[fe80::e022:9cff:fecc:c7c4:443" assert normalize_service_name( ir, "https://[fe80::e022:9cff:fecc:c7c4", "otherns", 'ConsulResolver') == "https://[fe80::e022:9cff:fecc:c7c4" assert normalize_service_name( ir, "https://fe80::e022:9cff:fecc:c7c4", "otherns", 'ConsulResolver') == "https://fe80::e022:9cff:fecc:c7c4" assert normalize_service_name(ir, "https://bad-service:-1", "otherns", 'ConsulResolver') == "https://bad-service:-1" assert normalize_service_name( ir, "https://bad-service:70000", "otherns", 'ConsulResolver') == "https://bad-service:70000" errors = ir.aconf.errors assert "-global-" in errors errors = errors["-global-"] assert len(errors) == 16 # Ugg, different versions of Python have different error messages. Let's recognize the "Port could not be cast to # integer value as" to keep pytest working on peoples up-to-date laptops with Python 3.8, and let's recognize # "invalid literal for int() with base 10:" for the Python 3.7 in the builder container. assert not errors[0]["ok"] assert ( errors[0]["error"] == "Malformed service 'https://bad-service:443:443': Port could not be cast to integer value as '443:443'" or errors[0]["error"] == "Malformed service 'https://bad-service:443:443': invalid literal for int() with base 10: '443:443'" ) assert not errors[1]["ok"] assert ( errors[1]["error"] == "test-rkey: Malformed service 'https://bad-service:443:443': Port could not be cast to integer value as '443:443'" or errors[1]["error"] == "test-rkey: Malformed service 'https://bad-service:443:443': invalid literal for int() with base 10: '443:443'" ) assert not errors[2]["ok"] assert ( errors[2]["error"] == "Malformed service 'bad-service:443:443': Port could not be cast to integer value as '443:443'" or errors[2]["error"] == "Malformed service 'bad-service:443:443': invalid literal for int() with base 10: '443:443'" ) assert not errors[3]["ok"] assert errors[3][ "error"] == "Malformed service 'https://[fe80::e022:9cff:fecc:c7c4:443': Invalid IPv6 URL" assert not errors[4]["ok"] assert errors[4][ "error"] == "Malformed service 'https://[fe80::e022:9cff:fecc:c7c4': Invalid IPv6 URL" assert not errors[5]["ok"] assert ( errors[5]["error"] == "Malformed service 'https://fe80::e022:9cff:fecc:c7c4': Port could not be cast to integer value as ':e022:9cff:fecc:c7c4'" or errors[5]["error"] == "Malformed service 'https://fe80::e022:9cff:fecc:c7c4': invalid literal for int() with base 10: ':e022:9cff:fecc:c7c4'" ) assert not errors[6]["ok"] assert errors[6][ "error"] == "Malformed service 'https://bad-service:-1': Port out of range 0-65535" assert not errors[7]["ok"] assert errors[7][ "error"] == "Malformed service 'https://bad-service:70000': Port out of range 0-65535" assert not errors[8]["ok"] assert ( errors[8]["error"] == "Malformed service 'https://bad-service:443:443': Port could not be cast to integer value as '443:443'" or errors[8]["error"] == "Malformed service 'https://bad-service:443:443': invalid literal for int() with base 10: '443:443'" ) assert not errors[9]["ok"] assert ( errors[9]["error"] == "test-rkey: Malformed service 'https://bad-service:443:443': Port could not be cast to integer value as '443:443'" or errors[9]["error"] == "test-rkey: Malformed service 'https://bad-service:443:443': invalid literal for int() with base 10: '443:443'" ) assert not errors[10]["ok"] assert ( errors[10]["error"] == "Malformed service 'bad-service:443:443': Port could not be cast to integer value as '443:443'" or errors[10]["error"] == "Malformed service 'bad-service:443:443': invalid literal for int() with base 10: '443:443'" ) assert not errors[11]["ok"] assert errors[11][ "error"] == "Malformed service 'https://[fe80::e022:9cff:fecc:c7c4:443': Invalid IPv6 URL" assert not errors[12]["ok"] assert errors[12][ "error"] == "Malformed service 'https://[fe80::e022:9cff:fecc:c7c4': Invalid IPv6 URL" assert not errors[13]["ok"] assert ( errors[13]["error"] == "Malformed service 'https://fe80::e022:9cff:fecc:c7c4': Port could not be cast to integer value as ':e022:9cff:fecc:c7c4'" or errors[13]["error"] == "Malformed service 'https://fe80::e022:9cff:fecc:c7c4': invalid literal for int() with base 10: ':e022:9cff:fecc:c7c4'" ) assert not errors[14]["ok"] assert errors[14][ "error"] == "Malformed service 'https://bad-service:-1': Port out of range 0-65535" assert not errors[15]["ok"] assert errors[15][ "error"] == "Malformed service 'https://bad-service:70000': Port out of range 0-65535"