/*
 * Copyright 2015-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.deployer.spi.kubernetes;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import io.fabric8.kubernetes.api.model.AffinityBuilder;
import io.fabric8.kubernetes.api.model.ConfigMapKeySelector;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.HostPathVolumeSource;
import io.fabric8.kubernetes.api.model.HostPathVolumeSourceBuilder;
import io.fabric8.kubernetes.api.model.LabelSelector;
import io.fabric8.kubernetes.api.model.LabelSelectorRequirementBuilder;
import io.fabric8.kubernetes.api.model.NodeAffinity;
import io.fabric8.kubernetes.api.model.NodeSelectorRequirementBuilder;
import io.fabric8.kubernetes.api.model.NodeSelectorTerm;
import io.fabric8.kubernetes.api.model.PodAffinity;
import io.fabric8.kubernetes.api.model.PodAffinityTerm;
import io.fabric8.kubernetes.api.model.PodAntiAffinity;
import io.fabric8.kubernetes.api.model.PodSecurityContext;
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.PreferredSchedulingTerm;
import io.fabric8.kubernetes.api.model.SecretKeySelector;
import io.fabric8.kubernetes.api.model.Toleration;
import io.fabric8.kubernetes.api.model.VolumeBuilder;
import io.fabric8.kubernetes.api.model.WeightedPodAffinityTerm;
import org.junit.Test;

import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.cloud.deployer.resource.docker.DockerResource;
import org.springframework.cloud.deployer.spi.core.AppDefinition;
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

/**
 * Unit tests for {@link KubernetesAppDeployer}
 *
 * @author Donovan Muller
 * @author David Turanski
 * @author Ilayaperumal Gopinathan
 * @author Chris Schaefer
 * @author Enrique Medina Montenegro
 */
public class KubernetesAppDeployerTests {

	private KubernetesAppDeployer deployer;

	private DeploymentPropertiesResolver deploymentPropertiesResolver = new DeploymentPropertiesResolver(
			KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX, new KubernetesDeployerProperties());


	@Test
	public void deployWithVolumesOnly() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(),
			new HashMap<>());

		deployer = new KubernetesAppDeployer(bindDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertThat(podSpec.getVolumes()).isEmpty();
	}

	@Test
	public void deployWithVolumesAndVolumeMounts() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.volumeMounts", "[" + "{name: 'testpvc', mountPath: '/test/pvc'}, "
			+ "{name: 'testnfs', mountPath: '/test/nfs', readOnly: 'true'}" + "]");
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(bindDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertThat(podSpec.getVolumes()).containsOnly(
			// volume 'testhostpath' defined in dataflow-server.yml should not be added
			// as there is no corresponding volume mount
			new VolumeBuilder().withName("testpvc").withNewPersistentVolumeClaim("testClaim", true).build(),
			new VolumeBuilder().withName("testnfs").withNewNfs("/test/nfs", null, "10.0.0.1:111").build());

		props.clear();
		props.put("spring.cloud.deployer.kubernetes.volumes",
			"[" + "{name: testhostpath, hostPath: { path: '/test/override/hostPath' }},"
				+ "{name: 'testnfs', nfs: { server: '192.168.1.1:111', path: '/test/override/nfs' }} " + "]");
		props.put("spring.cloud.deployer.kubernetes.volumeMounts",
			"[" + "{name: 'testhostpath', mountPath: '/test/hostPath'}, "
				+ "{name: 'testpvc', mountPath: '/test/pvc'}, "
				+ "{name: 'testnfs', mountPath: '/test/nfs', readOnly: 'true'}" + "]");
		appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(bindDeployerProperties(), null);
		podSpec = deployer.createPodSpec(appDeploymentRequest);

		HostPathVolumeSource hostPathVolumeSource = new HostPathVolumeSourceBuilder()
				.withPath("/test/override/hostPath").build();

		assertThat(podSpec.getVolumes()).containsOnly(
			new VolumeBuilder().withName("testhostpath").withHostPath(hostPathVolumeSource).build(),
			new VolumeBuilder().withName("testpvc").withNewPersistentVolumeClaim("testClaim", true).build(),
			new VolumeBuilder().withName("testnfs").withNewNfs("/test/override/nfs", null, "192.168.1.1:111").build());
	}

	@Test
	public void deployWithNodeSelectorGlobalProperty() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();
		kubernetesDeployerProperties.setNodeSelector("disktype:ssd, os:qnx");

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertThat(podSpec.getNodeSelector()).containsOnly(entry("disktype", "ssd"), entry("os", "qnx"));
	}

	@Test
	public void deployWithNodeSelectorDeploymentProperty() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		Map<String, String> props = new HashMap<>();
		props.put(KubernetesDeployerProperties.KUBERNETES_DEPLOYMENT_NODE_SELECTOR, "disktype:ssd, os: linux");
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(bindDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertThat(podSpec.getNodeSelector()).containsOnly(entry("disktype", "ssd"), entry("os", "linux"));
	}

	@Test
	public void deployWithNodeSelectorDeploymentPropertyGlobalOverride() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		Map<String, String> props = new HashMap<>();
		props.put(KubernetesDeployerProperties.KUBERNETES_DEPLOYMENT_NODE_SELECTOR, "disktype:ssd, os: openbsd");
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();
		kubernetesDeployerProperties.setNodeSelector("disktype:ssd, os:qnx");

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertThat(podSpec.getNodeSelector()).containsOnly(entry("disktype", "ssd"), entry("os", "openbsd"));
	}

	@Test
	public void deployWithEnvironmentWithCommaDelimitedValue() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.environmentVariables",
				"JAVA_TOOL_OPTIONS='thing1,thing2',foo='bar,baz',car=caz,boo='zoo,gnu',doo=dar,OPTS='thing1'");

		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(bindDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertThat(podSpec.getContainers().get(0).getEnv())
			.contains(
				new EnvVar("foo", "bar,baz", null),
				new EnvVar("car", "caz", null),
				new EnvVar("boo", "zoo,gnu", null),
				new EnvVar("doo", "dar", null),
				new EnvVar("JAVA_TOOL_OPTIONS", "thing1,thing2", null),
				new EnvVar("OPTS", "thing1", null));
	}

	@Test
	public void deployWithEnvironmentWithSingleCommaDelimitedValue() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.environmentVariables",
				"JAVA_TOOL_OPTIONS='thing1,thing2'");

		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(bindDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertThat(podSpec.getContainers().get(0).getEnv())
				.contains(new EnvVar("JAVA_TOOL_OPTIONS", "thing1,thing2", null));
	}

	@Test
	public void deployWithEnvironmentWithMultipleCommaDelimitedValue() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.environmentVariables",
				"JAVA_TOOL_OPTIONS='thing1,thing2',OPTS='thing3, thing4'");

		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(bindDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertThat(podSpec.getContainers().get(0).getEnv())
				.contains(
					new EnvVar("JAVA_TOOL_OPTIONS", "thing1,thing2", null),
					new EnvVar("OPTS", "thing3, thing4", null));
	}

	@Test
	public void deployWithImagePullSecretDeploymentProperty() {
		AppDefinition definition = new AppDefinition("app-test", null);

		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.imagePullSecret", "regcred");

		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(new KubernetesDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertThat(podSpec.getImagePullSecrets().size()).isEqualTo(1);
		assertThat(podSpec.getImagePullSecrets().get(0).getName()).isEqualTo("regcred");
	}

	@Test
	public void deployWithImagePullSecretDeployerProperty() {
		AppDefinition definition = new AppDefinition("app-test", null);

		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();
		kubernetesDeployerProperties.setImagePullSecret("regcred");

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertThat(podSpec.getImagePullSecrets().size()).isEqualTo(1);
		assertThat(podSpec.getImagePullSecrets().get(0).getName()).isEqualTo("regcred");
	}

	@Test
	public void deployWithDeploymentServiceAccountNameDeploymentProperties() {
		AppDefinition definition = new AppDefinition("app-test", null);

		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.deploymentServiceAccountName", "myserviceaccount");

		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(new KubernetesDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertNotNull(podSpec.getServiceAccountName());
		assertThat(podSpec.getServiceAccountName().equals("myserviceaccount"));
	}

	@Test
	public void deployWithDeploymentServiceAccountNameDeployerProperty() {
		AppDefinition definition = new AppDefinition("app-test", null);

		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();
		kubernetesDeployerProperties.setDeploymentServiceAccountName("myserviceaccount");

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertNotNull(podSpec.getServiceAccountName());
		assertThat(podSpec.getServiceAccountName().equals("myserviceaccount"));
	}

	@Test
	public void deployWithDeploymentServiceAccountNameDeploymentPropertyOverride() {
		AppDefinition definition = new AppDefinition("app-test", null);

		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.deploymentServiceAccountName", "overridesan");

		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();
		kubernetesDeployerProperties.setDeploymentServiceAccountName("defaultsan");

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertNotNull(podSpec.getServiceAccountName());
		assertThat(podSpec.getServiceAccountName().equals("overridesan"));
	}

	@Test
	public void deployWithTolerations() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(),
				new HashMap<>());

		deployer = new KubernetesAppDeployer(bindDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertThat(podSpec.getTolerations()).isNotEmpty();
	}

	@Test
	public void deployWithGlobalTolerations() {
		AppDefinition definition = new AppDefinition("app-test", null);

		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.tolerations",
				"[{key: 'test', value: 'true', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}, "
						+ "{key: 'test2', value: 'false', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}]");

		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(new KubernetesDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertNotNull(podSpec.getTolerations());
		assertThat(podSpec.getTolerations().size() == 2);
		assertThat(podSpec.getTolerations().contains(new Toleration("NoSchedule", "test", "Equal", 5L, "true")));
		assertThat(podSpec.getTolerations().contains(new Toleration("NoSchedule", "test2", "Equal", 5L, "false")));
	}

	@Test
	public void deployWithTolerationPropertyOverride() {
		AppDefinition definition = new AppDefinition("app-test", null);

		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.tolerations",
				"[{key: 'test', value: 'true', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}, "
						+ "{key: 'test2', value: 'false', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}]");

		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		KubernetesDeployerProperties.Toleration toleration = new KubernetesDeployerProperties.Toleration();
		toleration.setEffect("NoSchedule");
		toleration.setKey("test");
		toleration.setOperator("Equal");
		toleration.setTolerationSeconds(5L);
		toleration.setValue("false");

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();
		kubernetesDeployerProperties.getTolerations().add(toleration);

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertNotNull(podSpec.getTolerations());
		assertThat(podSpec.getTolerations().size() == 2);
		assertThat(podSpec.getTolerations().contains(new Toleration("NoSchedule", "test", "Equal", 5L, "true")));
		assertThat(podSpec.getTolerations().contains(new Toleration("NoSchedule", "test2", "Equal", 5L, "false")));
	}

	@Test
	public void deployWithDuplicateTolerationKeyPropertyOverride() {
		AppDefinition definition = new AppDefinition("app-test", null);

		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.tolerations",
				"[{key: 'test', value: 'true', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}]");

		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		KubernetesDeployerProperties.Toleration toleration = new KubernetesDeployerProperties.Toleration();
		toleration.setEffect("NoSchedule");
		toleration.setKey("test");
		toleration.setOperator("Equal");
		toleration.setTolerationSeconds(5L);
		toleration.setValue("false");

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();
		kubernetesDeployerProperties.getTolerations().add(toleration);

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertNotNull(podSpec.getTolerations());
		assertThat(podSpec.getTolerations().size() == 1);
		assertThat(podSpec.getTolerations().contains(new Toleration("NoSchedule", "test2", "Equal", 5L, "false")));
	}

	@Test
	public void deployWithDuplicateGlobalToleration() {
		AppDefinition definition = new AppDefinition("app-test", null);

		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();

		KubernetesDeployerProperties.Toleration toleration1 = new KubernetesDeployerProperties.Toleration();
		toleration1.setEffect("NoSchedule");
		toleration1.setKey("test");
		toleration1.setOperator("Equal");
		toleration1.setTolerationSeconds(5L);
		toleration1.setValue("false");

		kubernetesDeployerProperties.getTolerations().add(toleration1);

		KubernetesDeployerProperties.Toleration toleration2 = new KubernetesDeployerProperties.Toleration();
		toleration2.setEffect("NoSchedule");
		toleration2.setKey("test");
		toleration2.setOperator("Equal");
		toleration2.setTolerationSeconds(5L);
		toleration2.setValue("true");

		kubernetesDeployerProperties.getTolerations().add(toleration2);

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		assertNotNull(podSpec.getTolerations());
		assertThat(podSpec.getTolerations().size() == 1);
		assertThat(podSpec.getTolerations().contains(new Toleration("NoSchedule", "test2", "Equal", 5L, "true")));
	}

	@Test(expected = IllegalArgumentException.class)
	public void testInvalidDeploymentLabelDelimiter() {
		Map<String, String> props = Collections.singletonMap("spring.cloud.deployer.kubernetes.deploymentLabels",
				"label1|value1");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);
		this.deploymentPropertiesResolver.getDeploymentLabels(appDeploymentRequest.getDeploymentProperties());
	}

	@Test(expected = IllegalArgumentException.class)
	public void testInvalidMultipleDeploymentLabelDelimiter() {
		Map<String, String> props = Collections.singletonMap("spring.cloud.deployer.kubernetes.deploymentLabels",
				"label1:value1 label2:value2");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		this.deploymentPropertiesResolver.getDeploymentLabels(appDeploymentRequest.getDeploymentProperties());
	}

	@Test
	public void testDeploymentLabels() {
		Map<String, String> props = Collections.singletonMap("spring.cloud.deployer.kubernetes.deploymentLabels",
				"label1:value1,label2:value2");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		Map<String, String> deploymentLabels = this.deploymentPropertiesResolver.getDeploymentLabels(appDeploymentRequest.getDeploymentProperties());

		assertTrue("Deployment labels should not be empty", !deploymentLabels.isEmpty());
		assertEquals("Invalid number of labels", 2, deploymentLabels.size());
		assertTrue("Expected label 'label1' not found", deploymentLabels.containsKey("label1"));
		assertEquals("Invalid value for 'label1'", "value1", deploymentLabels.get("label1"));
		assertTrue("Expected label 'label2' not found", deploymentLabels.containsKey("label2"));
		assertEquals("Invalid value for 'label2'", "value2", deploymentLabels.get("label2"));
	}

	@Test
	public void testSecretKeyRef() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.secretKeyRefs",
				"[{envVarName: 'SECRET_PASSWORD', secretName: 'mySecret', dataKey: 'password'}]");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(new KubernetesDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();

		assertEquals("Invalid number of env vars", 2, envVars.size());

		EnvVar secretKeyRefEnvVar = envVars.get(0);
		assertEquals("Unexpected env var name", "SECRET_PASSWORD", secretKeyRefEnvVar.getName());
		SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();
		assertEquals("Unexpected secret name", "mySecret", secretKeySelector.getName());
		assertEquals("Unexpected secret data key", "password", secretKeySelector.getKey());
	}

	@Test
	public void testSecretKeyRefMultiple() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.secretKeyRefs",
				"[{envVarName: 'SECRET_PASSWORD', secretName: 'mySecret', dataKey: 'password'}," +
						"{envVarName: 'SECRET_USERNAME', secretName: 'mySecret2', dataKey: 'username'}]");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(new KubernetesDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();

		assertEquals("Invalid number of env vars", 3, envVars.size());

		EnvVar secretKeyRefEnvVar = envVars.get(0);
		assertEquals("Unexpected env var name", "SECRET_PASSWORD", secretKeyRefEnvVar.getName());
		SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();
		assertEquals("Unexpected secret name", "mySecret", secretKeySelector.getName());
		assertEquals("Unexpected secret data key", "password", secretKeySelector.getKey());

		secretKeyRefEnvVar = envVars.get(1);
		assertEquals("Unexpected env var name", "SECRET_USERNAME", secretKeyRefEnvVar.getName());
		secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();
		assertEquals("Unexpected secret name", "mySecret2", secretKeySelector.getName());
		assertEquals("Unexpected secret data key", "username", secretKeySelector.getKey());
	}

	@Test
	public void testSecretKeyRefGlobal() {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();
		KubernetesDeployerProperties.SecretKeyRef secretKeyRef = new KubernetesDeployerProperties.SecretKeyRef();
		secretKeyRef.setEnvVarName("SECRET_PASSWORD_GLOBAL");
		secretKeyRef.setSecretName("mySecretGlobal");
		secretKeyRef.setDataKey("passwordGlobal");
		kubernetesDeployerProperties.setSecretKeyRefs(Collections.singletonList(secretKeyRef));

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();

		assertEquals("Invalid number of env vars", 2, envVars.size());

		EnvVar secretKeyRefEnvVar = envVars.get(0);
		assertEquals("Unexpected env var name", "SECRET_PASSWORD_GLOBAL", secretKeyRefEnvVar.getName());
		SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();
		assertEquals("Unexpected secret name", "mySecretGlobal", secretKeySelector.getName());
		assertEquals("Unexpected secret data key", "passwordGlobal", secretKeySelector.getKey());
	}

	@Test
	public void testSecretKeyRefPropertyOverride() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.secretKeyRefs",
				"[{envVarName: 'SECRET_PASSWORD_GLOBAL', secretName: 'mySecret', dataKey: 'password'}," +
						"{envVarName: 'SECRET_USERNAME', secretName: 'mySecret2', dataKey: 'username'}]");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();

		List<KubernetesDeployerProperties.SecretKeyRef> globalSecretKeyRefs = new ArrayList<>();
		KubernetesDeployerProperties.SecretKeyRef globalSecretKeyRef1 = new KubernetesDeployerProperties.SecretKeyRef();
		globalSecretKeyRef1.setEnvVarName("SECRET_PASSWORD_GLOBAL");
		globalSecretKeyRef1.setSecretName("mySecretGlobal");
		globalSecretKeyRef1.setDataKey("passwordGlobal");

		KubernetesDeployerProperties.SecretKeyRef globalSecretKeyRef2 = new KubernetesDeployerProperties.SecretKeyRef();
		globalSecretKeyRef2.setEnvVarName("SECRET_USERNAME_GLOBAL");
		globalSecretKeyRef2.setSecretName("mySecretGlobal");
		globalSecretKeyRef2.setDataKey("usernameGlobal");

		globalSecretKeyRefs.add(globalSecretKeyRef1);
		globalSecretKeyRefs.add(globalSecretKeyRef2);

		kubernetesDeployerProperties.setSecretKeyRefs(globalSecretKeyRefs);

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();

		assertEquals("Invalid number of env vars", 4, envVars.size());

		// deploy prop overrides global
		EnvVar secretKeyRefEnvVar = envVars.get(0);
		assertEquals("Unexpected env var name", "SECRET_PASSWORD_GLOBAL", secretKeyRefEnvVar.getName());
		SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();
		assertEquals("Unexpected secret name", "mySecret", secretKeySelector.getName());
		assertEquals("Unexpected secret data key", "password", secretKeySelector.getKey());

		// unique deploy prop
		secretKeyRefEnvVar = envVars.get(1);
		assertEquals("Unexpected env var name", "SECRET_USERNAME", secretKeyRefEnvVar.getName());
		secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();
		assertEquals("Unexpected secret name", "mySecret2", secretKeySelector.getName());
		assertEquals("Unexpected secret data key", "username", secretKeySelector.getKey());

		// unique, non-overridden global prop
		secretKeyRefEnvVar = envVars.get(2);
		assertEquals("Unexpected env var name", "SECRET_USERNAME_GLOBAL", secretKeyRefEnvVar.getName());
		secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();
		assertEquals("Unexpected secret name", "mySecretGlobal", secretKeySelector.getName());
		assertEquals("Unexpected secret data key", "usernameGlobal", secretKeySelector.getKey());
	}

	@Test
	public void testSecretKeyRefGlobalFromYaml() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		deployer = new KubernetesAppDeployer(bindDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();

		assertEquals("Invalid number of env vars", 3, envVars.size());

		EnvVar secretKeyRefEnvVar = envVars.get(0);
		assertEquals("Unexpected env var name", "SECRET_PASSWORD", secretKeyRefEnvVar.getName());
		SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();
		assertEquals("Unexpected secret name", "mySecret", secretKeySelector.getName());
		assertEquals("Unexpected secret data key", "myPassword", secretKeySelector.getKey());
	}

	@Test
	public void testConfigMapKeyRef() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.configMapKeyRefs",
				"[{envVarName: 'MY_ENV', configMapName: 'myConfigMap', dataKey: 'envName'}]");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(new KubernetesDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();

		assertEquals("Invalid number of env vars", 2, envVars.size());

		EnvVar configMapKeyRefEnvVar = envVars.get(0);
		assertEquals("Unexpected env var name", "MY_ENV", configMapKeyRefEnvVar.getName());
		ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();
		assertEquals("Unexpected config map name", "myConfigMap", configMapKeySelector.getName());
		assertEquals("Unexpected config map data key", "envName", configMapKeySelector.getKey());
	}

	@Test
	public void testConfigMapKeyRefMultiple() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.configMapKeyRefs",
				"[{envVarName: 'MY_ENV', configMapName: 'myConfigMap', dataKey: 'envName'}," +
						"{envVarName: 'ENV_VALUES', configMapName: 'myOtherConfigMap', dataKey: 'diskType'}]");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(new KubernetesDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();

		assertEquals("Invalid number of env vars", 3, envVars.size());

		EnvVar configMapKeyRefEnvVar = envVars.get(0);
		assertEquals("Unexpected env var name", "MY_ENV", configMapKeyRefEnvVar.getName());
		ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();
		assertEquals("Unexpected config map name", "myConfigMap", configMapKeySelector.getName());
		assertEquals("Unexpected config map data key", "envName", configMapKeySelector.getKey());

		configMapKeyRefEnvVar = envVars.get(1);
		assertEquals("Unexpected env var name", "ENV_VALUES", configMapKeyRefEnvVar.getName());
		configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();
		assertEquals("Unexpected config map name", "myOtherConfigMap", configMapKeySelector.getName());
		assertEquals("Unexpected config map data key", "diskType", configMapKeySelector.getKey());
	}

	@Test
	public void testConfigMapKeyRefGlobal() {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();
		KubernetesDeployerProperties.ConfigMapKeyRef configMapKeyRef = new KubernetesDeployerProperties.ConfigMapKeyRef();
		configMapKeyRef.setEnvVarName("MY_ENV_GLOBAL");
		configMapKeyRef.setConfigMapName("myConfigMapGlobal");
		configMapKeyRef.setDataKey("envGlobal");
		kubernetesDeployerProperties.setConfigMapKeyRefs(Collections.singletonList(configMapKeyRef));

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();

		assertEquals("Invalid number of env vars", 2, envVars.size());

		EnvVar configMapKeyRefEnvVar = envVars.get(0);
		assertEquals("Unexpected env var name", "MY_ENV_GLOBAL", configMapKeyRefEnvVar.getName());
		ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();
		assertEquals("Unexpected config map name", "myConfigMapGlobal", configMapKeySelector.getName());
		assertEquals("Unexpected config data key", "envGlobal", configMapKeySelector.getKey());
	}

	@Test
	public void testConfigMapKeyRefPropertyOverride() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.configMapKeyRefs",
				"[{envVarName: 'MY_ENV', configMapName: 'myConfigMap', dataKey: 'envName'}," +
						"{envVarName: 'ENV_VALUES', configMapName: 'myOtherConfigMap', dataKey: 'diskType'}]");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();

		List<KubernetesDeployerProperties.ConfigMapKeyRef> globalConfigMapKeyRefs = new ArrayList<>();
		KubernetesDeployerProperties.ConfigMapKeyRef globalConfigMapKeyRef1 = new KubernetesDeployerProperties.ConfigMapKeyRef();
		globalConfigMapKeyRef1.setEnvVarName("MY_ENV");
		globalConfigMapKeyRef1.setConfigMapName("myEnvGlobal");
		globalConfigMapKeyRef1.setDataKey("envGlobal");

		KubernetesDeployerProperties.ConfigMapKeyRef globalConfigMapKeyRef2 = new KubernetesDeployerProperties.ConfigMapKeyRef();
		globalConfigMapKeyRef2.setEnvVarName("MY_VALS_GLOBAL");
		globalConfigMapKeyRef2.setConfigMapName("myValsGlobal");
		globalConfigMapKeyRef2.setDataKey("valsGlobal");

		globalConfigMapKeyRefs.add(globalConfigMapKeyRef1);
		globalConfigMapKeyRefs.add(globalConfigMapKeyRef2);

		kubernetesDeployerProperties.setConfigMapKeyRefs(globalConfigMapKeyRefs);

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();

		assertEquals("Invalid number of env vars", 4, envVars.size());

		// deploy prop overrides global
		EnvVar configMapKeyRefEnvVar = envVars.get(0);
		assertEquals("Unexpected env var name", "MY_ENV", configMapKeyRefEnvVar.getName());
		ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();
		assertEquals("Unexpected config map name", "myConfigMap", configMapKeySelector.getName());
		assertEquals("Unexpected config map data key", "envName", configMapKeySelector.getKey());

		// unique deploy prop
		configMapKeyRefEnvVar = envVars.get(1);
		assertEquals("Unexpected env var name", "ENV_VALUES", configMapKeyRefEnvVar.getName());
		configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();
		assertEquals("Unexpected config map name", "myOtherConfigMap", configMapKeySelector.getName());
		assertEquals("Unexpected config map data key", "diskType", configMapKeySelector.getKey());

		// unique, non-overridden global prop
		configMapKeyRefEnvVar = envVars.get(2);
		assertEquals("Unexpected env var name", "MY_VALS_GLOBAL", configMapKeyRefEnvVar.getName());
		configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();
		assertEquals("Unexpected config map name", "myValsGlobal", configMapKeySelector.getName());
		assertEquals("Unexpected config map data key", "valsGlobal", configMapKeySelector.getKey());
	}

	@Test
	public void testConfigMapKeyRefGlobalFromYaml() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		deployer = new KubernetesAppDeployer(bindDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();

		assertEquals("Invalid number of env vars", 3, envVars.size());

		EnvVar configMapKeyRefEnvVar = envVars.get(1);
		assertEquals("Unexpected env var name", "MY_ENV", configMapKeyRefEnvVar.getName());
		ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();
		assertEquals("Unexpected config map name", "myConfigMap", configMapKeySelector.getName());
		assertEquals("Unexpected config map data key", "envName", configMapKeySelector.getKey());
	}

	@Test
	public void testPodSecurityContextProperty() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.podSecurityContext", "{runAsUser: 65534, fsGroup: 65534}");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(new KubernetesDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		PodSecurityContext podSecurityContext = podSpec.getSecurityContext();

		assertNotNull("Pod security context should not be null", podSecurityContext);

		assertEquals("Unexpected run as user", Long.valueOf("65534"), podSecurityContext.getRunAsUser());
		assertEquals("Unexpected fs group", Long.valueOf("65534"), podSecurityContext.getFsGroup());
	}

	@Test
	public void testNodeAffinityProperty() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.affinity.nodeAffinity",
				"{ requiredDuringSchedulingIgnoredDuringExecution:" +
						"  { nodeSelectorTerms:" +
						"    [ { matchExpressions:" +
						"        [ { key: 'kubernetes.io/e2e-az-name', " +
						"            operator: 'In'," +
						"            values:" +
						"            [ 'e2e-az1', 'e2e-az2']}]}]}, " +
						"  preferredDuringSchedulingIgnoredDuringExecution:" +
						"  [ { weight: 1," +
						"      preference:" +
						"      { matchExpressions:" +
						"        [ { key: 'another-node-label-key'," +
						"            operator: 'In'," +
						"            values:" +
						"            [ 'another-node-label-value' ]}]}}]}");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(new KubernetesDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		NodeAffinity nodeAffinity = podSpec.getAffinity().getNodeAffinity();
		assertNotNull("Node affinity should not be null", nodeAffinity);
		assertNotNull("RequiredDuringSchedulingIgnoredDuringExecution should not be null", nodeAffinity.getRequiredDuringSchedulingIgnoredDuringExecution());
		assertEquals("PreferredDuringSchedulingIgnoredDuringExecution should have one element", 1, nodeAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size());
	}

	@Test
	public void testPodAffinityProperty() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.affinity.podAffinity",
				"{ requiredDuringSchedulingIgnoredDuringExecution:" +
						"  { labelSelector:" +
						"    [ { matchExpressions:" +
						"        [ { key: 'app', " +
						"            operator: 'In'," +
						"            values:" +
						"            [ 'store']}]}], " +
						"     topologyKey: 'kubernetes.io/hostname'}, " +
						"  preferredDuringSchedulingIgnoredDuringExecution:" +
						"  [ { weight: 1," +
						"      podAffinityTerm:" +
						"      { labelSelector:" +
						"        { matchExpressions:" +
						"          [ { key: 'security'," +
						"              operator: 'In'," +
						"              values:" +
						"              [ 'S2' ]}]}, " +
						"        topologyKey: 'failure-domain.beta.kubernetes.io/zone'}}]}");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(new KubernetesDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		PodAffinity podAffinity = podSpec.getAffinity().getPodAffinity();
		assertNotNull("Pod affinity should not be null", podAffinity);
		assertNotNull("RequiredDuringSchedulingIgnoredDuringExecution should not be null", podAffinity.getRequiredDuringSchedulingIgnoredDuringExecution());
		assertEquals("PreferredDuringSchedulingIgnoredDuringExecution should have one element", 1, podAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size());
	}

	@Test
	public void testPodAntiAffinityProperty() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.affinity.podAntiAffinity",
				"{ requiredDuringSchedulingIgnoredDuringExecution:" +
						"  { labelSelector:" +
						"    [ { matchExpressions:" +
						"        [ { key: 'app', " +
						"            operator: 'In'," +
						"            values:" +
						"            [ 'store']}]}], " +
						"     topologyKey: 'kubernetes.io/hostname'}, " +
						"  preferredDuringSchedulingIgnoredDuringExecution:" +
						"  [ { weight: 1," +
						"      podAffinityTerm:" +
						"      { labelSelector:" +
						"        { matchExpressions:" +
						"          [ { key: 'security'," +
						"              operator: 'In'," +
						"              values:" +
						"              [ 'S2' ]}]}, " +
						"        topologyKey: 'failure-domain.beta.kubernetes.io/zone'}}]}");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(new KubernetesDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		PodAntiAffinity podAntiAffinity = podSpec.getAffinity().getPodAntiAffinity();
		assertNotNull("Pod anti-affinity should not be null", podAntiAffinity);
		assertNotNull("RequiredDuringSchedulingIgnoredDuringExecution should not be null", podAntiAffinity.getRequiredDuringSchedulingIgnoredDuringExecution());
		assertEquals("PreferredDuringSchedulingIgnoredDuringExecution should have one element", 1, podAntiAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size());
	}

	@Test
	public void testPodSecurityContextGlobalProperty() {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();

		KubernetesDeployerProperties.PodSecurityContext securityContext = new KubernetesDeployerProperties.PodSecurityContext();
		securityContext.setFsGroup(65534L);
		securityContext.setRunAsUser(65534L);

		kubernetesDeployerProperties.setPodSecurityContext(securityContext);

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		PodSecurityContext podSecurityContext = podSpec.getSecurityContext();

		assertNotNull("Pod security context should not be null", podSecurityContext);

		assertEquals("Unexpected run as user", Long.valueOf("65534"), podSecurityContext.getRunAsUser());
		assertEquals("Unexpected fs group", Long.valueOf("65534"), podSecurityContext.getFsGroup());
	}

	@Test
	public void testNodeAffinityGlobalProperty() {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();

		NodeSelectorTerm nodeSelectorTerm = new NodeSelectorTerm();
		nodeSelectorTerm.setMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder()
				.withKey("kubernetes.io/e2e-az-name")
				.withOperator("In")
				.withValues("e2e-az1", "e2e-az2")
				.build()));
		NodeSelectorTerm nodeSelectorTerm2 = new NodeSelectorTerm();
		nodeSelectorTerm2.setMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder()
				.withKey("another-node-label-key")
				.withOperator("In")
				.withValues("another-node-label-value2")
				.build()));
		PreferredSchedulingTerm preferredSchedulingTerm = new PreferredSchedulingTerm(nodeSelectorTerm2, 1);
		NodeAffinity nodeAffinity = new AffinityBuilder()
				.withNewNodeAffinity()
				.withNewRequiredDuringSchedulingIgnoredDuringExecution()
				.withNodeSelectorTerms(nodeSelectorTerm)
				.endRequiredDuringSchedulingIgnoredDuringExecution()
				.withPreferredDuringSchedulingIgnoredDuringExecution(preferredSchedulingTerm)
				.endNodeAffinity()
				.buildNodeAffinity();

		kubernetesDeployerProperties.setNodeAffinity(nodeAffinity);

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		NodeAffinity nodeAffinityTest = podSpec.getAffinity().getNodeAffinity();
		assertNotNull("Node affinity should not be null", nodeAffinityTest);
		assertNotNull("RequiredDuringSchedulingIgnoredDuringExecution should not be null", nodeAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution());
		assertEquals("PreferredDuringSchedulingIgnoredDuringExecution should have one element", 1, nodeAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size());
	}

	@Test
	public void testPodAffinityGlobalProperty() {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();

		LabelSelector labelSelector = new LabelSelector();
		labelSelector.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()
				.withKey("security")
				.withOperator("In")
				.withValues("S1")
				.build()));
		PodAffinityTerm podAffinityTerm = new PodAffinityTerm(labelSelector, null, "failure-domain.beta.kubernetes.io/zone");
		LabelSelector labelSelector2 = new LabelSelector();
		labelSelector2.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()
				.withKey("security")
				.withOperator("In")
				.withValues("s2")
				.build()));
		PodAffinityTerm podAffinityTerm2 = new PodAffinityTerm(labelSelector2, null, "failure-domain.beta.kubernetes.io/zone");
		WeightedPodAffinityTerm weightedPodAffinityTerm = new WeightedPodAffinityTerm(podAffinityTerm2, 100);
		PodAffinity podAffinity = new AffinityBuilder()
				.withNewPodAffinity()
				.withRequiredDuringSchedulingIgnoredDuringExecution(podAffinityTerm)
				.withPreferredDuringSchedulingIgnoredDuringExecution(weightedPodAffinityTerm)
				.endPodAffinity()
				.buildPodAffinity();

		kubernetesDeployerProperties.setPodAffinity(podAffinity);

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		PodAffinity podAffinityTest = podSpec.getAffinity().getPodAffinity();
		assertNotNull("Pod affinity should not be null", podAffinityTest);
		assertNotNull("RequiredDuringSchedulingIgnoredDuringExecution should not be null", podAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution());
		assertEquals("PreferredDuringSchedulingIgnoredDuringExecution should have one element", 1, podAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size());
	}

	@Test
	public void testPodAntiAffinityGlobalProperty() {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();

		LabelSelector labelSelector = new LabelSelector();
		labelSelector.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()
				.withKey("app")
				.withOperator("In")
				.withValues("store")
				.build()));
		PodAffinityTerm podAffinityTerm = new PodAffinityTerm(labelSelector, null, "kubernetes.io/hostname");
		LabelSelector labelSelector2 = new LabelSelector();
		labelSelector2.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()
				.withKey("security")
				.withOperator("In")
				.withValues("s2")
				.build()));
		PodAffinityTerm podAffinityTerm2 = new PodAffinityTerm(labelSelector2, null, "failure-domain.beta.kubernetes.io/zone");
		WeightedPodAffinityTerm weightedPodAffinityTerm = new WeightedPodAffinityTerm(podAffinityTerm2, 100);
		PodAntiAffinity podAntiAffinity = new AffinityBuilder()
				.withNewPodAntiAffinity()
				.withRequiredDuringSchedulingIgnoredDuringExecution(podAffinityTerm)
				.withPreferredDuringSchedulingIgnoredDuringExecution(weightedPodAffinityTerm)
				.endPodAntiAffinity()
				.buildPodAntiAffinity();

		kubernetesDeployerProperties.setPodAntiAffinity(podAntiAffinity);

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		PodAntiAffinity podAntiAffinityTest = podSpec.getAffinity().getPodAntiAffinity();
		assertNotNull("Pod anti-affinity should not be null", podAntiAffinityTest);
		assertNotNull("RequiredDuringSchedulingIgnoredDuringExecution should not be null", podAntiAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution());
		assertEquals("PreferredDuringSchedulingIgnoredDuringExecution should have one element", 1, podAntiAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size());
	}

	@Test
	public void testPodSecurityContextFromYaml() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		deployer = new KubernetesAppDeployer(bindDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		PodSecurityContext podSecurityContext = podSpec.getSecurityContext();

		assertNotNull("Pod security context should not be null", podSecurityContext);

		assertEquals("Unexpected run as user", Long.valueOf("65534"), podSecurityContext.getRunAsUser());
		assertEquals("Unexpected fs group", Long.valueOf("65534"), podSecurityContext.getFsGroup());
	}

	@Test
	public void testNodeAffinityFromYaml() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		deployer = new KubernetesAppDeployer(bindDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		NodeAffinity nodeAffinity = podSpec.getAffinity().getNodeAffinity();
		assertNotNull("Node affinity should not be null", nodeAffinity);
		assertNotNull("RequiredDuringSchedulingIgnoredDuringExecution should not be null", nodeAffinity.getRequiredDuringSchedulingIgnoredDuringExecution());
		assertEquals("PreferredDuringSchedulingIgnoredDuringExecution should have one element", 1, nodeAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size());
	}

	@Test
	public void testPodAffinityFromYaml() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		deployer = new KubernetesAppDeployer(bindDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		PodAffinity podAffinity = podSpec.getAffinity().getPodAffinity();
		assertNotNull("Pod affinity should not be null", podAffinity);
		assertNotNull("RequiredDuringSchedulingIgnoredDuringExecution should not be null", podAffinity.getRequiredDuringSchedulingIgnoredDuringExecution());
		assertEquals("PreferredDuringSchedulingIgnoredDuringExecution should have one element", 1, podAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size());
	}

	@Test
	public void testPodAntiAffinityFromYaml() throws Exception {
		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);

		deployer = new KubernetesAppDeployer(bindDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		PodAntiAffinity podAntiAffinity = podSpec.getAffinity().getPodAntiAffinity();
		assertNotNull("Pod anti-affinity should not be null", podAntiAffinity);
		assertNotNull("RequiredDuringSchedulingIgnoredDuringExecution should not be null", podAntiAffinity.getRequiredDuringSchedulingIgnoredDuringExecution());
		assertEquals("PreferredDuringSchedulingIgnoredDuringExecution should have one element", 1, podAntiAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size());
	}

	@Test
	public void testPodSecurityContextUIDOnly() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.podSecurityContext", "{runAsUser: 65534}");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(new KubernetesDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		PodSecurityContext podSecurityContext = podSpec.getSecurityContext();

		assertNotNull("Pod security context should not be null", podSecurityContext);

		assertEquals("Unexpected run as user", Long.valueOf("65534"), podSecurityContext.getRunAsUser());
		assertNull("Unexpected fs group", podSecurityContext.getFsGroup());
	}

	@Test
	public void testPodSecurityContextFsGroupOnly() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.podSecurityContext", "{fsGroup: 65534}");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		deployer = new KubernetesAppDeployer(new KubernetesDeployerProperties(), null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		PodSecurityContext podSecurityContext = podSpec.getSecurityContext();

		assertNotNull("Pod security context should not be null", podSecurityContext);

		assertNull("Unexpected run as user", podSecurityContext.getRunAsUser());
		assertEquals("Unexpected fs group", Long.valueOf("65534"), podSecurityContext.getFsGroup());
	}

	@Test
	public void testPodSecurityContextPropertyOverrideGlobal() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.podSecurityContext", "{runAsUser: 65534, fsGroup: 65534}");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();

		KubernetesDeployerProperties.PodSecurityContext securityContext = new KubernetesDeployerProperties.PodSecurityContext();
		securityContext.setFsGroup(1000L);
		securityContext.setRunAsUser(1000L);

		kubernetesDeployerProperties.setPodSecurityContext(securityContext);

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		PodSecurityContext podSecurityContext = podSpec.getSecurityContext();

		assertNotNull("Pod security context should not be null", podSecurityContext);

		assertEquals("Unexpected run as user", Long.valueOf("65534"), podSecurityContext.getRunAsUser());
		assertEquals("Unexpected fs group", Long.valueOf("65534"), podSecurityContext.getFsGroup());
	}

	@Test
	public void testNodeAffinityPropertyOverrideGlobal() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.affinity.nodeAffinity",
				"{ requiredDuringSchedulingIgnoredDuringExecution:" +
						"  { nodeSelectorTerms:" +
						"    [ { matchExpressions:" +
						"        [ { key: 'kubernetes.io/e2e-az-name', " +
						"            operator: 'In'," +
						"            values:" +
						"            [ 'e2e-az1', 'e2e-az2']}]}]}, " +
						"  preferredDuringSchedulingIgnoredDuringExecution:" +
						"  [ { weight: 1," +
						"      preference:" +
						"      { matchExpressions:" +
						"        [ { key: 'another-node-label-key'," +
						"            operator: 'In'," +
						"            values:" +
						"            [ 'another-node-label-value' ]}]}}]}");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();

		NodeSelectorTerm nodeSelectorTerm = new NodeSelectorTerm();
		nodeSelectorTerm.setMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder()
				.withKey("kubernetes.io/e2e-az-name")
				.withOperator("In")
				.withValues("e2e-az1", "e2e-az2")
				.build()));
		NodeAffinity nodeAffinity = new AffinityBuilder()
				.withNewNodeAffinity()
				.withNewRequiredDuringSchedulingIgnoredDuringExecution()
				.withNodeSelectorTerms(nodeSelectorTerm)
				.endRequiredDuringSchedulingIgnoredDuringExecution()
				.endNodeAffinity()
				.buildNodeAffinity();

		kubernetesDeployerProperties.setNodeAffinity(nodeAffinity);

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		NodeAffinity nodeAffinityTest = podSpec.getAffinity().getNodeAffinity();
		assertNotNull("Node affinity should not be null", nodeAffinityTest);
		assertNotNull("RequiredDuringSchedulingIgnoredDuringExecution should not be null", nodeAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution());
		assertEquals("PreferredDuringSchedulingIgnoredDuringExecution should have one element", 1, nodeAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size());
	}

	@Test
	public void testPodAffinityPropertyOverrideGlobal() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.affinity.podAffinity",
				"{ requiredDuringSchedulingIgnoredDuringExecution:" +
						"  { labelSelector:" +
						"    [ { matchExpressions:" +
						"        [ { key: 'security', " +
						"            operator: 'In'," +
						"            values:" +
						"            [ 'S1']}]}], " +
						"     topologyKey: 'failure-domain.beta.kubernetes.io/zone'}, " +
						"  preferredDuringSchedulingIgnoredDuringExecution:" +
						"  [ { weight: 1," +
						"      podAffinityTerm:" +
						"      { labelSelector:" +
						"        { matchExpressions:" +
						"          [ { key: 'security'," +
						"              operator: 'In'," +
						"              values:" +
						"              [ 'S2' ]}]}, " +
						"        topologyKey: 'failure-domain.beta.kubernetes.io/zone'}}]}");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();

		LabelSelector labelSelector = new LabelSelector();
		labelSelector.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()
				.withKey("tolerance")
				.withOperator("In")
				.withValues("Reliable")
				.build()));
		PodAffinityTerm podAffinityTerm = new PodAffinityTerm(labelSelector, null, "failure-domain.beta.kubernetes.io/zone");
		PodAffinity podAffinity = new AffinityBuilder()
				.withNewPodAffinity()
				.withRequiredDuringSchedulingIgnoredDuringExecution(podAffinityTerm)
				.endPodAffinity()
				.buildPodAffinity();

		kubernetesDeployerProperties.setPodAffinity(podAffinity);

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		PodAffinity podAffinityTest = podSpec.getAffinity().getPodAffinity();
		assertNotNull("Pod affinity should not be null", podAffinityTest);
		assertNotNull("RequiredDuringSchedulingIgnoredDuringExecution should not be null", podAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution());
		assertEquals("PreferredDuringSchedulingIgnoredDuringExecution should have one element", 1, podAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size());
	}

	@Test
	public void testPodAntiAffinityPropertyOverrideGlobal() {
		Map<String, String> props = new HashMap<>();
		props.put("spring.cloud.deployer.kubernetes.affinity.podAntiAffinity",
				"{ requiredDuringSchedulingIgnoredDuringExecution:" +
						"  { labelSelector:" +
						"    [ { matchExpressions:" +
						"        [ { key: 'app', " +
						"            operator: 'In'," +
						"            values:" +
						"            [ 'store']}]}], " +
						"     topologyKey: 'kubernetes.io/hostnam'}, " +
						"  preferredDuringSchedulingIgnoredDuringExecution:" +
						"  [ { weight: 1," +
						"      podAffinityTerm:" +
						"      { labelSelector:" +
						"        { matchExpressions:" +
						"          [ { key: 'security'," +
						"              operator: 'In'," +
						"              values:" +
						"              [ 'S2' ]}]}, " +
						"        topologyKey: 'failure-domain.beta.kubernetes.io/zone'}}]}");

		AppDefinition definition = new AppDefinition("app-test", null);
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);

		KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();

		LabelSelector labelSelector = new LabelSelector();
		labelSelector.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()
				.withKey("version")
				.withOperator("Equals")
				.withValues("v1")
				.build()));
		PodAffinityTerm podAffinityTerm = new PodAffinityTerm(labelSelector, null, "kubernetes.io/hostnam");
		PodAntiAffinity podAntiAffinity = new AffinityBuilder()
				.withNewPodAntiAffinity()
				.withRequiredDuringSchedulingIgnoredDuringExecution(podAffinityTerm)
				.endPodAntiAffinity()
				.buildPodAntiAffinity();

		kubernetesDeployerProperties.setPodAntiAffinity(podAntiAffinity);

		deployer = new KubernetesAppDeployer(kubernetesDeployerProperties, null);
		PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);

		PodAntiAffinity podAntiAffinityTest = podSpec.getAffinity().getPodAntiAffinity();
		assertNotNull("Pod anti-affinity should not be null", podAntiAffinityTest);
		assertNotNull("RequiredDuringSchedulingIgnoredDuringExecution should not be null", podAntiAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution());
		assertEquals("PreferredDuringSchedulingIgnoredDuringExecution should have one element", 1, podAntiAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size());
	}

	private Resource getResource() {
		return new DockerResource("springcloud/spring-cloud-deployer-spi-test-app:latest");
	}

	private KubernetesDeployerProperties bindDeployerProperties() throws Exception {
		YamlPropertiesFactoryBean properties = new YamlPropertiesFactoryBean();
		properties.setResources(new ClassPathResource("dataflow-server.yml"),
				new ClassPathResource("dataflow-server-tolerations.yml"),
				new ClassPathResource("dataflow-server-secretKeyRef.yml"),
				new ClassPathResource("dataflow-server-configMapKeyRef.yml"),
				new ClassPathResource("dataflow-server-podsecuritycontext.yml"),
				new ClassPathResource("dataflow-server-nodeAffinity.yml"),
				new ClassPathResource("dataflow-server-podAffinity.yml"),
				new ClassPathResource("dataflow-server-podAntiAffinity.yml"));
		Properties yaml = properties.getObject();
		MapConfigurationPropertySource source = new MapConfigurationPropertySource(yaml);
		return new Binder(source).bind("", Bindable.of(KubernetesDeployerProperties.class)).get();
	}
}