Configuration-as-code-plugin: Configuration creates Cloud instances twice.

Created on 18 May 2020  路  25Comments  路  Source: jenkinsci/configuration-as-code-plugin

Your checklist for this issue

馃毃 Please review the guidelines for contributing to this repository.

  • [x] Jenkins version 2.176.4

  • [x] Plugin version 1.35

  • [x] OS jenkins/jenkins:2.176.4 Docker image

Description

This issue relates to the MesosCloud plugin.

Given the following configuration.yaml

jenkins:
  agentProtocols:
    - "JNLP4-connect"
    - "Ping"
  numExecutors: 0
  clouds:
    - mesos:
        agentUser: "${JENKINS_AGENT_USER:-root}"
        frameworkName: "${JENKINS_FRAMEWORK_NAME:-Jenkins Scheduler}"
        jenkinsURL: "http://${JENKINS_FRAMEWORK_NAME:-jenkins}.${MARATHON_NAME:-marathon}.mesos:${PORT0:-8080}"
        mesosAgentSpecTemplates:
          - label: "linux"
            agentAttributes: ""
            agentCommandStyle: Linux
            containerInfo:
              dockerForcePullImage: false
              dockerImage: "mesosphere/jenkins-dind:0.8.0"
              dockerPrivilegedMode: true
              isDind: true
              networking: HOST
              type: "DOCKER"
            cpus: "0.1"
            disk: "0.0"
            domainFilterModel: "home"
            idleTerminationMinutes: 3
            jnlpArgs: "-noReconnect"
            maxExecutors: 1
            mem: "512"
            minExecutors: 1
            mode: EXCLUSIVE 
          - label: "windows"
            agentAttributes: "os:windows"
            agentCommandStyle: Windows
            containerInfo:
              dockerForcePullImage: false
              dockerImage: "mesosphere/jenkins-windows-node:latest"
              dockerPrivilegedMode: false
              isDind: false
              networking: BRIDGE
              type: "DOCKER"
            cpus: "1.0"
            disk: "0.0"
            domainFilterModel: "any"
            idleTerminationMinutes: 3
            jnlpArgs: "-noReconnect"
            maxExecutors: 1
            mem: "4096"
            minExecutors: 1
            mode: EXCLUSIVE 
        mesosMasterUrl: "${JENKINS_MESOS_MASTER:-http://leader.mesos:5050}"
        role: "${JENKINS_AGENT_ROLE:-*}"
unclassified:
  location:
    adminAddress: "address not configured yet <nobody@nowhere>"
    url: "http://jenkins.marathon.mesos:${PORT0:-8080}/"

When I build and start the docker container with

docker build -t mesosphere/jenkins:unstable .
docker run -e JENKINS_AGENT_USER=kjeschkies -e JENKINS_AGENT_ROLE=foo \
    -e JENKINS_FRAMEWORK_NAME=jenkins \
    -e JENKINS_CONTEXT=f -e PORT0=8080 \
    -e JENKINS_MESOS_MASTER="http://127.0.0.1:5050" \
    -e PORT1=7070 \
    mesosphere/jenkins:unstable

I see two instances of the MesosCloud class:

INFO: Augmented all extensions
May 15, 2020 3:58:47 PM org.jenkinsci.plugins.mesos.MesosCloud <init>
INFO: Created Mesos cloud instance for framework 28a354b3-d435-4c27-973d-abd474f21951 and endpoint http://127.0.0.1:5050                <------ HERE
May 15, 2020 3:58:49 PM akka.event.slf4j.Slf4jLogger$$anonfun$receive$1 applyOrElse
INFO: Slf4jLogger started
May 15, 2020 3:58:49 PM org.jenkinsci.plugins.mesos.MesosApi <init>
INFO: Starting USI scheduler flow.
May 15, 2020 3:58:50 PM org.jenkinsci.plugins.mesos.MesosApi getInstance
INFO: Initialized Mesos API object for framework 28a354b3-d435-4c27-973d-abd474f21951
May 15, 2020 3:58:51 PM com.mesosphere.mesos.client.MesosClient$ com$mesosphere$mesos$client$MesosClient$$mesosHttpConnection
INFO: Connecting to Mesos master http://127.0.0.1:5050
May 15, 2020 3:58:51 PM org.jenkinsci.plugins.mesos.MesosCloud <init>
INFO: Created Mesos cloud instance for framework 467fe63d-1c4e-43a2-8bde-7b26a63e4331 and endpoint http://127.0.0.1:5050                <------ HERE
May 15, 2020 3:58:51 PM akka.event.slf4j.Slf4jLogger$$anonfun$receive$1 applyOrElse
INFO: Slf4jLogger started
May 15, 2020 3:58:51 PM org.jenkinsci.plugins.mesos.MesosApi <init>
INFO: Starting USI scheduler flow.
May 15, 2020 3:58:51 PM org.jenkinsci.plugins.mesos.MesosApi getInstance
INFO: Initialized Mesos API object for framework 467fe63d-1c4e-43a2-8bde-7b26a63e4331
May 15, 2020 3:58:51 PM com.mesosphere.mesos.client.MesosClient$ com$mesosphere$mesos$client$MesosClient$$mesosHttpConnection
INFO: Connecting to Mesos master http://127.0.0.1:5050
bug

All 25 comments

This is an issue with the mesos plugin rather than JCasC.

I suspect it is either using configure override or not saving/loading properly as according to Jenkins and JCasC standards.

Very similar to https://github.com/jenkinsci/gitlab-plugin/pull/1063

Ya, looking at the logs and seeing the two init calls makes me very suspicious.

@jetersen, thanks for the help. I have a couple of questions

according to Jenkins and JCasC standards.

Where can I find these standards documented?

I've dug into the load() method of the descriptor. This is it what I saw in other plugins.

I've commented our public DescriptorImpl() { load(); } and I still see this issue :/. Trying to replicate it in a unit test now.

I'm inching close. I have a test the reproduces the issue locally: https://github.com/jenkinsci/mesos-plugin/pull/399/files#diff-310729d418274a17cf07674821575b7fR90. I don't know how to check the number of instances in Code but I see them in the logs.

EDIT: Jenkis.clouds has a size of 1 yet I see two class constructions.

The JCasC test-harness has some utils to help you check it.
See this for reference: https://github.com/jetersen/gitlab-plugin/blob/fix/jcasc-compat/src/test/java/com/dabsquared/gitlabjenkins/connection/GitLabConnectionConfigAsCodeTest.java

like toStringFromYamlFile and toYamlString

Unfortunately I opted to use JUnit 5 for MesosCloud. It was not too hard to load the config and apply it though. However, I see one MesosCloud instance in Jenkins.clouds but two invocations of the constructor. How can this be?

Honestly beats me 馃槄

Alright, I ran the test with a break point. Turns out JCasC calls the constructor twice.

First Time

<init>:118, MesosCloud (org.jenkinsci.plugins.mesos)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
tryConstructor:174, DataBoundConfigurator (io.jenkins.plugins.casc.impl.configurators)
instance:77, DataBoundConfigurator (io.jenkins.plugins.casc.impl.configurators)
configure:267, BaseConfigurator (io.jenkins.plugins.casc)
configure:83, DataBoundConfigurator (io.jenkins.plugins.casc.impl.configurators)
lambda$doConfigure$16668e2$1:278, HeteroDescribableConfigurator (io.jenkins.plugins.casc.impl.configurators)
apply:-1, 1637704878 (io.jenkins.plugins.casc.impl.configurators.HeteroDescribableConfigurator$$Lambda$1119)
lambda$unchecked$52349c75$1:247, CheckedFunction0 (io.vavr)
apply:-1, 1810224313 (io.vavr.CheckedFunction0$$Lambda$1102)
doConfigure:278, HeteroDescribableConfigurator (io.jenkins.plugins.casc.impl.configurators)
lambda$null$2:86, HeteroDescribableConfigurator (io.jenkins.plugins.casc.impl.configurators)
apply:-1, 1638818225 (io.jenkins.plugins.casc.impl.configurators.HeteroDescribableConfigurator$$Lambda$1118)
map:392, Option (io.vavr.control)
lambda$configure$3:86, HeteroDescribableConfigurator (io.jenkins.plugins.casc.impl.configurators)
apply:-1, 1623662625 (io.jenkins.plugins.casc.impl.configurators.HeteroDescribableConfigurator$$Lambda$1103)
apply:238, Tuple2 (io.vavr)
configure:83, HeteroDescribableConfigurator (io.jenkins.plugins.casc.impl.configurators)
check:92, HeteroDescribableConfigurator (io.jenkins.plugins.casc.impl.configurators)
check:55, HeteroDescribableConfigurator (io.jenkins.plugins.casc.impl.configurators)
configure:344, BaseConfigurator (io.jenkins.plugins.casc)
check:287, BaseConfigurator (io.jenkins.plugins.casc)
lambda$checkWith$7:746, ConfigurationAsCode (io.jenkins.plugins.casc)
apply:-1, 525618048 (io.jenkins.plugins.casc.ConfigurationAsCode$$Lambda$1065)
invokeWith:696, ConfigurationAsCode (io.jenkins.plugins.casc)
checkWith:746, ConfigurationAsCode (io.jenkins.plugins.casc)
configureWith:731, ConfigurationAsCode (io.jenkins.plugins.casc)
configureWith:611, ConfigurationAsCode (io.jenkins.plugins.casc)
configure:581, ConfigurationAsCode (io.jenkins.plugins.casc)
configure:570, ConfigurationAsCode (io.jenkins.plugins.casc)
configureAsCode:92, MesosCloudTest (org.jenkinsci.plugins.mesos)

Second Time

<init>:118, MesosCloud (org.jenkinsci.plugins.mesos)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
tryConstructor:174, DataBoundConfigurator (io.jenkins.plugins.casc.impl.configurators)
instance:77, DataBoundConfigurator (io.jenkins.plugins.casc.impl.configurators)
configure:267, BaseConfigurator (io.jenkins.plugins.casc)
configure:83, DataBoundConfigurator (io.jenkins.plugins.casc.impl.configurators)
lambda$doConfigure$16668e2$1:278, HeteroDescribableConfigurator (io.jenkins.plugins.casc.impl.configurators)
apply:-1, 1637704878 (io.jenkins.plugins.casc.impl.configurators.HeteroDescribableConfigurator$$Lambda$1119)
lambda$unchecked$52349c75$1:247, CheckedFunction0 (io.vavr)
apply:-1, 1810224313 (io.vavr.CheckedFunction0$$Lambda$1102)
doConfigure:278, HeteroDescribableConfigurator (io.jenkins.plugins.casc.impl.configurators)
lambda$null$2:86, HeteroDescribableConfigurator (io.jenkins.plugins.casc.impl.configurators)
apply:-1, 1638818225 (io.jenkins.plugins.casc.impl.configurators.HeteroDescribableConfigurator$$Lambda$1118)
map:392, Option (io.vavr.control)
lambda$configure$3:86, HeteroDescribableConfigurator (io.jenkins.plugins.casc.impl.configurators)
apply:-1, 1623662625 (io.jenkins.plugins.casc.impl.configurators.HeteroDescribableConfigurator$$Lambda$1103)
apply:238, Tuple2 (io.vavr)
configure:83, HeteroDescribableConfigurator (io.jenkins.plugins.casc.impl.configurators)
configure:55, HeteroDescribableConfigurator (io.jenkins.plugins.casc.impl.configurators)
configure:345, BaseConfigurator (io.jenkins.plugins.casc)
configure:270, BaseConfigurator (io.jenkins.plugins.casc)
lambda$configureWith$5:738, ConfigurationAsCode (io.jenkins.plugins.casc)
apply:-1, 1972056923 (io.jenkins.plugins.casc.ConfigurationAsCode$$Lambda$1076)
invokeWith:696, ConfigurationAsCode (io.jenkins.plugins.casc)
configureWith:738, ConfigurationAsCode (io.jenkins.plugins.casc)
configureWith:611, ConfigurationAsCode (io.jenkins.plugins.casc)
configure:581, ConfigurationAsCode (io.jenkins.plugins.casc)
configure:570, ConfigurationAsCode (io.jenkins.plugins.casc)
configureAsCode:92, MesosCloudTest (org.jenkinsci.plugins.mesos)

The first calls through checkWith in configureWith and the second invokeWith in configureWith.

You should try and avoid having big complex databound constructors.
This is actually documented: https://www.jenkins.io/doc/developer/plugin-development/pipeline-integration/#constructor-vs-setters

Look at how the kubernetes one does it.
https://github.com/jenkinsci/kubernetes-plugin/blob/master/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java

There is properly more to it.

@jetersen, thanks a lot. I think the issue is that we are connecting to Mesos when the MesosCloud instance is created. The Kubernetes plugin keeps a cache of connection and only connects on the first demand. Luckily we already have such a client provider.

I think we can close this issue?

I think we can close this issue?

@jetersen, I would like to document this somewhere as I am sure more will stumble upon this. As I understand the constructor should have no side effects other than the creation of the class.

We could properly merge these two docs and add some more about constructors and setters, have minimal side effects.

Those that extends GlobalConfiguration need to call save on each setters.

https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/docs/PLUGINS.md
https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/docs/REQUIREMENTS.md

@jetersen, thanks. I completely missed the docs folder. Is this hosted somewhere? What do you think about a simple Github pages for this? As I tried to find the docs link in the README and on the Jenkins plugin page but could not find anything.

@timja, the section is linked above. It does help but it is not easy to find.

Agreed, I have to go searching for it to give to people... (apologies missed Joseph's comment)

What do you think about a simple Github pages for this?

I have an unfinished PoC for it:
https://jenkins-configuration-as-code.netlify.app/docs/configuration-reload/

I have an unfinished PoC for it

What's left to make it happen? I am happy to help. The Mesos Cloud plugin could benefit from such an help page as well.

I think our docs could live on Jenkins.io? 馃

I have an unfinished PoC for it

What's left to make it happen? I am happy to help. The Mesos Cloud plugin could benefit from such an help page as well.

time and interest that others would be interested in it.

I think our docs could live on Jenkins.io? 馃

not sure if plugin specific docs should live there,
I'm very much in favour of docs living with the code so that they stay in sync when changed

I guess we can close this issue then 馃檪

Was this page helpful?
0 / 5 - 0 ratings

Related issues

szandala picture szandala  路  4Comments

alexgeek picture alexgeek  路  7Comments

timblaktu picture timblaktu  路  4Comments

darkn3rd picture darkn3rd  路  3Comments

reschex picture reschex  路  4Comments