-
Notifications
You must be signed in to change notification settings - Fork 118
Add unit-testing for executorpodfactory #491
Conversation
private val executorPrefix: String = "base" | ||
private val executorImage: String = "executor-image" | ||
private val driverPod = new PodBuilder() | ||
.withNewMetadata() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indentation
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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). |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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]()) | ||
|
There was a problem hiding this comment.
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 = { |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
Does |
CI build failing?
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. |
The packages have been renamed to |
Basically we only want to have the impl for |
I rebased this branch to the |
bf62649
to
9bf7256
Compare
Yes we should merge this into the 2.2 branch first; only a subset of the
tests are valid for the first apache/spark PR.
…On Thu, Sep 28, 2017 at 3:27 PM, Erik Erlandson ***@***.***> wrote:
@foxish <https://github.com/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
<#498> ?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#491 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AA3U5_586I5BPRTbzgvppAvvDQIFY4Vgks5snB1hgaJpZM4PYWCO>
.
--
Anirudh Ramanathan
|
OK I'll make sure its rebased to 2.2 branch before it merges |
9bf7256
to
4f0beb5
Compare
private var baseConf: SparkConf = _ | ||
|
||
private val nodeAffinityExecutorPodModifier = mock(classOf[NodeAffinityExecutorPodModifier]) | ||
when(nodeAffinityExecutorPodModifier.addNodeAffinityAnnotationIfUseful( |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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:
- Use
Mockito.verify
- Return meaningful modifications to the pod/container from the mock and test that such modifications were done in the appropriate locations.
val container = invocation.getArgumentAt(1, classOf[Container]) | ||
val secretName = "secret1" | ||
val secretMountPath = "/var/secret1" | ||
val resolvedPod = new PodBuilder(pod) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
@erikerlandson @foxish ping on latest comment? |
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. |
There was a problem hiding this 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.
* 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)
* 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
This is important for unit-testing executorpodfactory, which will be part of our first upstream PR.
cc @apache-spark-on-k8s/contributors @liyinan926