def test_rapid_additions_and_deletions(self): namespace = 'watt-rapid' # Install Ambassador install_ambassador(namespace=namespace) # Set up our listener. self.create_listeners(namespace) # Install QOTM apply_kube_artifacts(namespace=namespace, artifacts=qotm_manifests) # Install QOTM Ambassador manifests self.apply_qotm_endpoint_manifests(namespace=namespace) # Now let's wait for ambassador and QOTM pods to become ready run_and_assert([ 'kubectl', 'wait', '--timeout=90s', '--for=condition=Ready', 'pod', '-l', 'service=ambassador', '-n', namespace ]) run_and_assert([ 'kubectl', 'wait', '--timeout=90s', '--for=condition=Ready', 'pod', '-l', 'service=qotm', '-n', namespace ]) # Assume we can reach Ambassador through telepresence qotm_host = "ambassador." + namespace qotm_url = f"http://{qotm_host}/qotm/" # Assert 200 OK at /qotm/ endpoint qotm_ready = False loop_limit = 60 while not qotm_ready: assert loop_limit > 0, "QOTM is not ready yet, aborting..." try: qotm_http_code = get_code_with_retry(qotm_url) assert qotm_http_code == 200, f"Expected 200 OK, got {qotm_http_code}" print(f"{qotm_url} is ready") qotm_ready = True except Exception as e: print(f"Error: {e}") print(f"{qotm_url} not ready yet, trying again...") time.sleep(1) loop_limit -= 1 # Try to mess up Ambassador by applying and deleting QOTM mapping over and over for i in range(10): self.delete_qotm_mapping(namespace=namespace) self.create_qotm_mapping(namespace=namespace) # Let's give Ambassador a few seconds to register the changes... time.sleep(5) # Assert 200 OK at /qotm/ endpoint qotm_http_code = get_code_with_retry(qotm_url) assert qotm_http_code == 200, f"Expected 200 OK, got {qotm_http_code}"
def apply_qotm_endpoint_manifests(self, namespace): qotm_resolver = f""" apiVersion: getambassador.io/v2 kind: KubernetesEndpointResolver metadata: name: qotm-resolver namespace: {namespace} """ apply_kube_artifacts(namespace=namespace, artifacts=qotm_resolver) self.create_qotm_mapping(namespace=namespace)
def create_qotm_mapping(namespace): qotm_mapping = f""" --- apiVersion: getambassador.io/v2 kind: Mapping metadata: name: qotm-mapping namespace: {namespace} spec: prefix: /qotm/ service: qotm """ apply_kube_artifacts(namespace=namespace, artifacts=qotm_mapping)
def create_httpbin_mapping(namespace): httpbin_mapping = f""" --- apiVersion: getambassador.io/v2 kind: Mapping metadata: name: httpbin-mapping namespace: {namespace} spec: prefix: /httpbin/ rewrite: / service: httpbin """ apply_kube_artifacts(namespace=namespace, artifacts=httpbin_mapping)
def create_module(self, namespace): manifest = f""" --- apiVersion: getambassador.io/v2 kind: Module metadata: name: ambassador spec: config: header_case_overrides: - X-HELLO - X-FOO-Bar """ apply_kube_artifacts(namespace=namespace, artifacts=manifest)
def create_qotm_mapping(namespace): qotm_mapping = f""" --- apiVersion: x.getambassador.io/v3alpha1 kind: AmbassadorMapping metadata: name: qotm-mapping namespace: {namespace} spec: hostname: "*" prefix: /qotm/ service: qotm """ apply_kube_artifacts(namespace=namespace, artifacts=qotm_mapping)
def create_headerecho_mapping(namespace): headerecho_mapping = f""" --- apiVersion: x.getambassador.io/v3alpha1 kind: AmbassadorMapping metadata: name: headerecho-mapping namespace: {namespace} spec: hostname: "*" prefix: /headerecho/ rewrite: / service: headerecho """ apply_kube_artifacts(namespace=namespace, artifacts=headerecho_mapping)
def create_listeners(self, namespace): manifest = f""" --- apiVersion: x.getambassador.io/v3alpha1 kind: AmbassadorListener metadata: name: listener-8080 spec: port: 8080 protocol: HTTP securityModel: INSECURE hostBinding: namespace: from: SELF """ apply_kube_artifacts(namespace=namespace, artifacts=manifest)
def create_qotm_mapping(self, namespace): qotm_mapping = f""" --- apiVersion: getambassador.io/v2 kind: Mapping metadata: name: qotm-mapping namespace: {namespace} spec: prefix: /qotm/ service: qotm.{namespace} resolver: qotm-resolver load_balancer: policy: round_robin """ apply_kube_artifacts(namespace=namespace, artifacts=qotm_mapping)
def install_ambassador(namespace, single_namespace=True, envs=None): """ Install Ambassador into a given namespace. NOTE WELL that although there is a 'single_namespace' parameter, this function probably needs work to do the fully-correct thing with single_namespace False. :param namespace: namespace to install Ambassador in :param single_namespace: should we set AMBASSADOR_SINGLE_NAMESPACE? SEE NOTE ABOVE! :param envs: [ { 'name': 'ENV_NAME', 'value': 'ENV_VALUE' }, ... ... ] """ if envs is None: envs = [] found_single_namespace = False if single_namespace: for e in envs: if e['name'] == 'AMBASSADOR_SINGLE_NAMESPACE': e['value'] = 'true' found_single_namespace = True break if not found_single_namespace: envs.append({ 'name': 'AMBASSADOR_SINGLE_NAMESPACE', 'value': 'true' }) # Create namespace to install Ambassador create_namespace(namespace) # Create Ambassador CRDs apply_kube_artifacts(namespace=namespace, artifacts=load_manifest('crds')) # Proceed to install Ambassador now final_yaml = [] serviceAccountExtra = '' if os.environ.get("DEV_USE_IMAGEPULLSECRET", False): serviceAccountExtra = """ imagePullSecrets: - name: dev-image-pull-secret """ rbac_manifest_name = 'rbac_namespace_scope' if single_namespace else 'rbac_cluster_scope' # Hackish fakes of actual KAT structures -- it's _far_ too much work to synthesize # actual KAT Nodes and Paths. fakeNode = namedtuple('fakeNode', ['namespace', 'path', 'ambassador_id']) fakePath = namedtuple('fakePath', ['k8s']) ambassador_yaml = list( yaml.safe_load_all( (load_manifest(rbac_manifest_name) + load_manifest('ambassador') + (CLEARTEXT_HOST_YAML % namespace)).format( capabilities_block="", envs="", extra_ports="", serviceAccountExtra=serviceAccountExtra, image=os.environ["AMBASSADOR_DOCKER_IMAGE"], self=fakeNode(namespace=namespace, ambassador_id='default', path=fakePath(k8s='ambassador'))))) for manifest in ambassador_yaml: kind = manifest.get('kind', None) metadata = manifest.get('metadata', {}) name = metadata.get('name', None) if (kind == "Pod") and (name == "ambassador"): # Force AMBASSADOR_ID to match ours. # # XXX This is not likely to work without single_namespace=True. for envvar in manifest['spec']['containers'][0]['env']: if envvar.get('name', '') == 'AMBASSADOR_ID': envvar['value'] = 'default' # add new envs, if any manifest['spec']['containers'][0]['env'].extend(envs) apply_kube_artifacts(namespace=namespace, artifacts=yaml.safe_dump_all(ambassador_yaml))
def create_namespace(namespace): apply_kube_artifacts(namespace=namespace, artifacts=namespace_manifest(namespace))
def test_header_case_overrides(self): # Is there any reason not to use the default namespace? namespace = 'header-case-overrides' # Install Ambassador install_ambassador(namespace=namespace) # Install httpbin apply_kube_artifacts(namespace=namespace, artifacts=httpbin_manifests) # Install headerecho apply_kube_artifacts(namespace=namespace, artifacts=headerecho_manifests) # Install listeners. self.create_listeners(namespace) # Install module self.create_module(namespace) # Install httpbin mapping create_httpbin_mapping(namespace=namespace) # Install headerecho mapping create_headerecho_mapping(namespace=namespace) # Now let's wait for ambassador and httpbin pods to become ready run_and_assert([ 'kubectl', 'wait', '--timeout=90s', '--for=condition=Ready', 'pod', '-l', 'service=ambassador', '-n', namespace ]) run_and_assert([ 'kubectl', 'wait', '--timeout=90s', '--for=condition=Ready', 'pod', '-l', 'service=httpbin', '-n', namespace ]) # Assume we can reach Ambassador through telepresence ambassador_host = "ambassador." + namespace # Assert 200 OK at httpbin/status/200 endpoint ready = False httpbin_url = f'http://{ambassador_host}/httpbin/status/200' headerecho_url = f'http://{ambassador_host}/headerecho/' loop_limit = 10 while not ready: assert loop_limit > 0, "httpbin is not ready yet, aborting..." try: print(f"trying {httpbin_url}...") resp = requests.get(httpbin_url, timeout=5) code = resp.status_code assert code == 200, f"Expected 200 OK, got {code}" resp.close() print(f"{httpbin_url} is ready") print(f"trying {headerecho_url}...") resp = requests.get(headerecho_url, timeout=5) code = resp.status_code assert code == 200, f"Expected 200 OK, got {code}" resp.close() print(f"{headerecho_url} is ready") ready = True except Exception as e: print(f"Error: {e}") print(f"{httpbin_url} not ready yet, trying again...") time.sleep(1) loop_limit -= 1 assert ready httpbin_url = f'http://{ambassador_host}/httpbin/response-headers?x-Hello=1&X-foo-Bar=1&x-Lowercase1=1&x-lowercase2=1' resp = requests.get(httpbin_url, timeout=5) code = resp.status_code assert code == 200, f"Expected 200 OK, got {code}" # First, test that the response headers have the correct case. # Very important: this test relies on matching case sensitive header keys. # Fortunately it appears that we can convert resp.headers, a case insensitive # dictionary, into a list of case sensitive keys. keys = [h for h in resp.headers.keys()] for k in keys: print(f"header key: {k}") assert 'x-hello' not in keys assert 'X-HELLO' in keys assert 'x-foo-bar' not in keys assert 'X-FOO-Bar' in keys assert 'x-lowercase1' in keys assert 'x-Lowercase1' not in keys assert 'x-lowercase2' in keys resp.close() # Second, test that the request headers sent to the headerecho server # have the correct case. headers = { 'x-Hello': '1', 'X-foo-Bar': '1', 'x-Lowercase1': '1', 'x-lowercase2': '1' } resp = requests.get(headerecho_url, headers=headers, timeout=5) code = resp.status_code assert code == 200, f"Expected 200 OK, got {code}" response_obj = json.loads(resp.text) print(f"response_obj = {response_obj}") assert response_obj assert 'headers' in response_obj hdrs = response_obj['headers'] assert 'x-hello' not in hdrs assert 'X-HELLO' in hdrs assert 'x-foo-bar' not in hdrs assert 'X-FOO-Bar' in hdrs assert 'x-lowercase1' in hdrs assert 'x-Lowercase1' not in hdrs assert 'x-lowercase2' in hdrs
def test_knative(self): namespace = 'knative-testing' # Install Knative apply_kube_artifacts(namespace=None, artifacts=load_manifest("knative_serving_crds")) apply_kube_artifacts(namespace='knative-serving', artifacts=load_manifest("knative_serving_0.18.0")) run_and_assert([ 'kubectl', 'patch', 'configmap/config-network', '--type', 'merge', '--patch', r'{"data": {"ingress.class": "ambassador.ingress.networking.knative.dev"}}', '-n', 'knative-serving' ]) # Wait for Knative to become ready run_and_assert([ 'kubectl', 'wait', '--timeout=90s', '--for=condition=Ready', 'pod', '-l', 'app=activator', '-n', 'knative-serving' ]) run_and_assert([ 'kubectl', 'wait', '--timeout=90s', '--for=condition=Ready', 'pod', '-l', 'app=controller', '-n', 'knative-serving' ]) run_and_assert([ 'kubectl', 'wait', '--timeout=90s', '--for=condition=Ready', 'pod', '-l', 'app=webhook', '-n', 'knative-serving' ]) run_and_assert([ 'kubectl', 'wait', '--timeout=90s', '--for=condition=Ready', 'pod', '-l', 'app=autoscaler', '-n', 'knative-serving' ]) # Install Ambassador install_ambassador(namespace=namespace, envs=[{ 'name': 'AMBASSADOR_KNATIVE_SUPPORT', 'value': 'true' }]) # Install QOTM apply_kube_artifacts(namespace=namespace, artifacts=qotm_manifests) create_qotm_mapping(namespace=namespace) # Now let's wait for ambassador and QOTM pods to become ready run_and_assert([ 'kubectl', 'wait', '--timeout=90s', '--for=condition=Ready', 'pod', '-l', 'service=ambassador', '-n', namespace ]) run_and_assert([ 'kubectl', 'wait', '--timeout=90s', '--for=condition=Ready', 'pod', '-l', 'service=qotm', '-n', namespace ]) # Create kservice apply_kube_artifacts(namespace=namespace, artifacts=knative_service_example) # Assume we can reach Ambassador through telepresence qotm_host = "ambassador." + namespace # Assert 200 OK at /qotm/ endpoint qotm_url = f'http://{qotm_host}/qotm/' code = get_code_with_retry(qotm_url) assert code == 200, f"Expected 200 OK, got {code}" print(f"{qotm_url} is ready") # Assert 200 OK at / with Knative Host header and 404 with other/no header kservice_url = f'http://{qotm_host}/' code = get_code_with_retry(kservice_url) assert code == 404, f"Expected 404, got {code}" print(f"{kservice_url} returns 404 with no host") code = get_code_with_retry(kservice_url, headers={'Host': 'random.host.whatever'}) assert code == 404, f"Expected 404, got {code}" print(f"{kservice_url} returns 404 with a random host") # Wait for kservice run_and_assert([ 'kubectl', 'wait', '--timeout=90s', '--for=condition=Ready', 'ksvc', 'helloworld-go', '-n', namespace ]) # kservice pod takes some time to spin up, so let's try a few times code = 000 host = f'helloworld-go.{namespace}.example.com' for _ in range(5): code = get_code_with_retry(kservice_url, headers={'Host': host}) if code == 200: break assert code == 200, f"Expected 200, got {code}" print( f"{kservice_url} returns 200 OK with host helloworld-go.{namespace}.example.com" )