Skip to content
This repository has been archived by the owner on Jan 9, 2020. It is now read-only.

Add unit-testing for executorpodfactory #491

Merged
merged 18 commits into from
Oct 10, 2017
Merged

Conversation

foxish
Copy link
Member

@foxish foxish commented Sep 15, 2017

This is important for unit-testing executorpodfactory, which will be part of our first upstream PR.

cc @apache-spark-on-k8s/contributors @liyinan926

private val executorPrefix: String = "base"
private val executorImage: String = "executor-image"
private val driverPod = new PodBuilder()
.withNewMetadata()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

.set(KUBERNETES_DRIVER_POD_NAME, driverPodName)
.set(KUBERNETES_EXECUTOR_POD_NAME_PREFIX, executorPrefix)
.set(EXECUTOR_DOCKER_IMAGE, executorImage)
sc = new SparkContext("local", "test")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use mocks here? Can we use a mock SecurityManager?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would that be useful here?
The ExecutorPodFactoryImpl class doesn't use SecurityManager.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More just that we don't want to make a real SparkContext here. SparkContext.clearActiveContext could be problematic on parallel test runs. If we don't need to make a SparkContext, we should avoid it.

// Default memory limit is 1024M + 384M (minimum overhead constant).
assert(executor.getSpec.getContainers.size() == 1)
assert(executor.getSpec.getContainers.get(0).getImage == executorImage)
assert(executor.getSpec.getContainers.get(0).getVolumeMounts.size() == 0)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use isEmpty.

getLimits.get("memory").getAmount == "1408Mi")

// The pod has no node selector, volumes.
assert(executor.getSpec.getNodeSelector.size() == 0)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isEmpty - replace this everywhere.

test("basic executor pod has reasonable defaults") {
val factory = new ExecutorPodFactoryImpl(baseConf,
NodeAffinityExecutorPodModifierImpl, None, None, None, None, None)
val executor = factory.createExecutorPod("1", "dummy", "dummy",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move all args to the next line and all on one line if they fit in 100 chars. If not - put each arg on its own line.


val factory = new ExecutorPodFactoryImpl(conf,
NodeAffinityExecutorPodModifierImpl, None, None, None, None, None)
val executor = factory.createExecutorPod("1",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move args to one per line.

assert(executor.getSpec.getContainers.size() == 1)
assert(executor.getSpec.getContainers.get(0).getVolumeMounts.size() == 1)
assert(executor.getSpec.getContainers.get(0).getVolumeMounts.get(0).getName == "secret1-volume")
assert(executor.getSpec.getContainers.get(0).getVolumeMounts.get(0).
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a couple of places where the dot is on the end of the line followed by the method or field being on the next line. I think we want to move the dot to the next line and continue the chain of calls from there.

SparkTransportConf.fromSparkConf(conf, "shuffle"),
sc.env.securityManager,
sc.env.securityManager.isAuthenticationEnabled())
val shuffleManager = new KubernetesExternalShuffleManagerImpl(conf,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use the impl - use a mock.

val shuffleManager = new KubernetesExternalShuffleManagerImpl(conf,
kubernetesClient, kubernetesExternalShuffleClient)
val factory = new ExecutorPodFactoryImpl(conf,
NodeAffinityExecutorPodModifierImpl, None, None, None, None, Some(shuffleManager))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use the impl, use a mock.

kubernetesClient, kubernetesExternalShuffleClient)
val factory = new ExecutorPodFactoryImpl(conf,
NodeAffinityExecutorPodModifierImpl, None, None, None, None, Some(shuffleManager))
val executor = factory.createExecutorPod("1",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argument formatting as stated elsewhere in the review - fix this in all applicable locations.


assert(executor.getSpec.getContainers.size() == 1)
assert(executor.getSpec.getContainers.get(0).getVolumeMounts.size() == 1)
assert(executor.getSpec.getContainers.get(0).getVolumeMounts.get(0).getName == "0-tmp")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - all assertions should use triple-equals - === - instead of double equals.

NodeAffinityExecutorPodModifierImpl, None, Some(smallFiles), None, None, None)
val executor = factory.createExecutorPod("1",
"dummy", "dummy", Seq[(String, String)](), driverPod, Map[String, Int]())

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra whitespace here

}

// Check that the expected environment variables are present.
private def checkEnv(executor: Pod, additionalEnvVars: Set[String]): Unit = {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not also want to be checking the values of the environment variables we are setting, not just the keys? Hence the additionalEnvVars should be a Map, not a Set.

test("init-container bootstrap step adds an init container") {
val conf = baseConf.clone()

val initContainerBootstrap = new SparkPodInitContainerBootstrapImpl(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a mock and not the impl. Add a separate unit test for this class if it does not already exist.

conf.set(KUBERNETES_SHUFFLE_NAMESPACE, "default")
conf.set(KUBERNETES_SHUFFLE_DIR, "/tmp")

val kubernetesExternalShuffleClient = new KubernetesExternalShuffleClientImpl(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a mock, not the impl.

SparkTransportConf.fromSparkConf(conf, "shuffle"),
sc.env.securityManager,
sc.env.securityManager.isAuthenticationEnabled())
val shuffleManager = new KubernetesExternalShuffleManagerImpl(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a mock, not an impl.


val factory = new ExecutorPodFactoryImpl(
conf,
NodeAffinityExecutorPodModifierImpl,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a mock, not the impl. Add a separate unit test for this impl class.

val secretsBootstrap = new MountSecretsBootstrapImpl(Map("secret1" -> "/var/secret1"))
val factory = new ExecutorPodFactoryImpl(
conf,
NodeAffinityExecutorPodModifierImpl,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a mock

test("basic executor pod has reasonable defaults") {
val factory = new ExecutorPodFactoryImpl(
baseConf,
NodeAffinityExecutorPodModifierImpl,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a mock - replace this everywhere

@erikerlandson
Copy link
Member

Does NodeAffinityExecutorPodModifierImpl object need to be mocked? The problem seemed to be around use of sc as dep for construction of shuffleManager

@erikerlandson
Copy link
Member

erikerlandson commented Sep 28, 2017

CI build failing?

[error] /home/jenkins/workspace/PR-spark-k8s-full-build/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/scheduler/cluster/kubernetes/ExecutorPodFactorySuite.scala:34: object kubernetes is not a member of package org.apache.spark.deploy
[error] import org.apache.spark.deploy.kubernetes.{constants, SparkPodInitContainerBootstrapImpl}
[error]                                ^

Trying to figure out if I broke something here, but I didn't change that import, and my edits were all local to the unit test file.

@mccheah
Copy link

mccheah commented Sep 28, 2017

The packages have been renamed to k8s right?

@mccheah
Copy link

mccheah commented Sep 28, 2017

Does NodeAffinityExecutorPodModifierImpl object need to be mocked? The problem seemed to be around use of sc as dep for construction of shuffleManager

Basically we only want to have the impl for ExecutorPodFactory - SparkContext and any other object should be mocks.

@erikerlandson
Copy link
Member

I rebased this branch to the v2.2.0-kubernetes-0.4.0 release tag to pick up the k8s rename and the other remaining 0.4.0 commits

@erikerlandson
Copy link
Member

@foxish I just rebased this to the 0.4.0 tag, to pick up the .k8s. rename - however this PR is currently targeted to branch-2.2.-kubernetes. Was the intent to merge it to branch-2.2.-kubernetes and then port it to #498 ?

@foxish
Copy link
Member Author

foxish commented Sep 28, 2017 via email

@erikerlandson
Copy link
Member

OK I'll make sure its rebased to 2.2 branch before it merges

private var baseConf: SparkConf = _

private val nodeAffinityExecutorPodModifier = mock(classOf[NodeAffinityExecutorPodModifier])
when(nodeAffinityExecutorPodModifier.addNodeAffinityAnnotationIfUseful(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to the before block.

any(classOf[Map[String, Int]]))).thenAnswer(AdditionalAnswers.returnsFirstArg())

before {
SparkContext.clearActiveContext()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think we need to do this anymore.

test("init-container bootstrap step adds an init container") {
val conf = baseConf.clone()

val initContainerBootstrap = new SparkPodInitContainerBootstrapImpl(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use the impl, use a mock.


test("Small-files add a secret & secret volume mount to the container") {
val conf = baseConf.clone()
val smallFiles = new MountSmallFilesBootstrapImpl("secret1", "/var/secret1")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use the impl, use a mock

val setEnvs = executor.getSpec.getContainers.get(0).getEnv.asScala.map {
x => x.getName
}.toSet
assert(defaultEnvs === setEnvs)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to check the values for these as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not obvious to me what the expected values for some of the env-vars ought to be. Possibly @foxish knows. I've been assuming the main logic being tested there is purely env propagation - to confirm what vars are being passed under what circumstances.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

.set(EXECUTOR_DOCKER_IMAGE, executorImage)
when(nodeAffinityExecutorPodModifier.addNodeAffinityAnnotationIfUseful(
any(classOf[Pod]),
any(classOf[Map[String, Int]]))).thenAnswer(AdditionalAnswers.returnsFirstArg())
Copy link

@mccheah mccheah Sep 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This setup makes it such that we don't check if nodeAffinityExecutorPodModifier is used in the pod factory. Either:

  1. Use Mockito.verify
  2. Return meaningful modifications to the pod/container from the mock and test that such modifications were done in the appropriate locations.

@ifilonenko ifilonenko self-requested a review October 4, 2017 17:39
val container = invocation.getArgumentAt(1, classOf[Container])
val secretName = "secret1"
val secretMountPath = "/var/secret1"
val resolvedPod = new PodBuilder(pod)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can be simplified. Just return the pod with an extra label/annotation, and the container with an extra environment variable. Verify that the pod has this extra label/annotation and the container has the appropriate environment variable.

Or, use Mockito.verify() - I'm still not sure which is the stronger/more idiomatic approach here.

Copy link
Member Author

@foxish foxish Oct 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that we're mocking the very behavior we're testing here. I'd actually be in favor of omitting mocks for this particular case entirely and just relying on a real stamped out MountSmallFilesBootstrapImpol since it's not nearly complex enough to need a mock IMO.

@mccheah
Copy link

mccheah commented Oct 9, 2017

@erikerlandson @foxish ping on latest comment?

@foxish
Copy link
Member Author

foxish commented Oct 10, 2017

Addressed comment - like I mentioned there, I see the value in mocks elsewhere, but not in that particular case, since using the actual class itself seems very straightforward. Also addressed comment about env-variable value checking in addition to just the key names. This should be ready to merge at this point. PTAL @mccheah.

Copy link

@mccheah mccheah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might take another pass over later to remove the use of MountSecretsBootstrapImpl, but I want to move this along.

@foxish foxish merged commit 887fdce into branch-2.2-kubernetes Oct 10, 2017
@foxish foxish deleted the add-testing branch October 10, 2017 21:48
ifilonenko pushed a commit to ifilonenko/spark that referenced this pull request Feb 26, 2019
* Unit test for executorpodfactory

* Fix test

* Indentation fix

* Fix isEmpty and split between lines

* Address issues with multi-line code fragments

* Replace == with ===

* mock shuffleManager

* .kubernetes. => .k8s.

* move to k8s subdir

* fix package clause to k8s

* mock nodeAffinityExecutorPodModifier

* remove commented code

* move when clause to before{} block

* mock initContainerBootstrap, smallFiles

* insert actual logic into smallFiles mock

* verify application of nodeAffinityExecutorPodModifier

* avoid cumulative invocation

* Fixed env-var check to include values, removed mock for small files

(cherry picked from commit 887fdce)
puneetloya pushed a commit to puneetloya/spark that referenced this pull request Mar 11, 2019
* Unit test for executorpodfactory

* Fix test

* Indentation fix

* Fix isEmpty and split between lines

* Address issues with multi-line code fragments

* Replace == with ===

* mock shuffleManager

* .kubernetes. => .k8s.

* move to k8s subdir

* fix package clause to k8s

* mock nodeAffinityExecutorPodModifier

* remove commented code

* move when clause to before{} block

* mock initContainerBootstrap, smallFiles

* insert actual logic into smallFiles mock

* verify application of nodeAffinityExecutorPodModifier

* avoid cumulative invocation

* Fixed env-var check to include values, removed mock for small files
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants