From 5b0ce90dade0830c42c83c980209a99e783ff146 Mon Sep 17 00:00:00 2001 From: hyokeun Date: Thu, 5 Apr 2018 19:39:20 +0900 Subject: [PATCH] Assigning snapshot job to separate node Change-Id: I79d496d21727719948cfe8bf284de7ee87bc7bab --- debian/jenkins-scripts.install | 1 + groovy_init_scripts/scriptApproval.xml | 18 +++ job_add_new_node.groovy | 213 +++++++++++++++++++++++++++++++++ job_jobs_dispatcher.py | 41 ++++++- packaging/jenkins-scripts.spec | 1 + 5 files changed, 268 insertions(+), 6 deletions(-) create mode 100644 job_add_new_node.groovy diff --git a/debian/jenkins-scripts.install b/debian/jenkins-scripts.install index 38abb05..43972ad 100644 --- a/debian/jenkins-scripts.install +++ b/debian/jenkins-scripts.install @@ -52,3 +52,4 @@ debian/tmp/job_gbs_dashboard_build.py /var/lib/jenkins/jenkins-scripts/ debian/tmp/job_gbsdbbuild_create_snapshot.py /var/lib/jenkins/jenkins-scripts/ debian/tmp/job_gbs_build_dispatcher.py /var/lib/jenkins/jenkins-scripts/ debian/tmp/job_gbsdbbuild_update_meta.py /var/lib/jenkins/jenkins-scripts/ +debian/tmp/job_add_new_node.groovy /var/lib/jenkins/jenkins-scripts/ diff --git a/groovy_init_scripts/scriptApproval.xml b/groovy_init_scripts/scriptApproval.xml index a0f2c69..49f7c34 100644 --- a/groovy_init_scripts/scriptApproval.xml +++ b/groovy_init_scripts/scriptApproval.xml @@ -61,6 +61,7 @@ 8f55d4f32226edf90e29cdbece0c0aaa7e1636ec 91dd560b8c31636f4cf0025bfad0abd92800d102 92318df17b760e3dd8e8446dde7a9ef7ea92a41b + 92fdda9c74dd5278ec75f1fb7a879775da9cf418 931836c0b5555ab3399a5b64c68e04bc86186f9f 93b2188439cc18091839439e9650d09e3c6dca4c 93f92899721b83cd85a647c5c281500118f881d3 @@ -108,6 +109,8 @@ field hudson.model.Queue$Item task + method com.cloudbees.plugins.credentials.common.IdCredentials getId + method com.cloudbees.plugins.credentials.common.UsernameCredentials getUsername method groovy.lang.GroovyObject getProperty java.lang.String method groovy.lang.GroovyObject invokeMethod java.lang.String java.lang.Object method groovy.lang.GroovyObject setProperty java.lang.String java.lang.Object @@ -122,6 +125,8 @@ method hudson.model.Computer doDoDelete method hudson.model.Computer getIdleStartMilliseconds method hudson.model.Computer getName + method hudson.model.Computer getNode + method hudson.model.Computer getNumExecutors method hudson.model.Computer isAlive method hudson.model.Computer isOffline method hudson.model.Computer setTemporarilyOffline boolean hudson.slaves.OfflineCause @@ -130,14 +135,22 @@ method hudson.model.ItemGroup getItem java.lang.String method hudson.model.Job isBuilding method hudson.model.Job isInQueue + method hudson.model.Node getLabelString method hudson.model.Queue getItems method hudson.model.Queue$Item getId method hudson.model.Run getEnvironment hudson.model.TaskListener method hudson.model.Saveable save method hudson.model.Slave getComputer method hudson.model.Slave getLauncher + method hudson.plugins.sshslaves.SSHLauncher getCredentials method hudson.plugins.sshslaves.SSHLauncher getHost + method hudson.plugins.sshslaves.SSHLauncher getPort + method hudson.slaves.SlaveComputer getAbsoluteRemoteFs + method hudson.slaves.SlaveComputer getLauncher method java.io.BufferedReader readLine + method java.io.File getName + method java.io.File isFile + method java.io.File listFiles method java.io.Flushable flush method java.io.Writer write java.lang.String method java.lang.AutoCloseable close @@ -153,6 +166,8 @@ method java.net.URLConnection getOutputStream method java.net.URLConnection setDoOutput boolean method java.net.URLConnection setRequestProperty java.lang.String java.lang.String + method java.security.MessageDigest digest + method java.security.MessageDigest update byte[] method java.util.Collection remove java.lang.Object method java.util.Dictionary get java.lang.Object method java.util.Properties load java.io.InputStream @@ -164,6 +179,7 @@ new java.io.OutputStreamWriter java.io.OutputStream new java.lang.StringBuffer new java.lang.Thread java.lang.Runnable + new java.math.BigInteger int byte[] new java.util.Base64 new java.util.Properties staticMethod hudson.model.Hudson getInstance @@ -173,9 +189,11 @@ staticMethod java.lang.String format java.lang.String java.lang.Object[] staticMethod java.lang.System getProperty java.lang.String staticMethod java.lang.System getenv java.lang.String + staticMethod java.security.MessageDigest getInstance java.lang.String staticMethod javax.xml.bind.DatatypeConverter printBase64Binary byte[] staticMethod jenkins.model.Jenkins getInstance staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods append java.io.File java.lang.Object + staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods contains java.lang.Object[] java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods execute java.lang.String staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods find java.lang.Object groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods find java.util.Collection groovy.lang.Closure diff --git a/job_add_new_node.groovy b/job_add_new_node.groovy new file mode 100644 index 0000000..6ef9ed2 --- /dev/null +++ b/job_add_new_node.groovy @@ -0,0 +1,213 @@ +import hudson.model.* +import jenkins.model.* +import groovy.json.JsonSlurper +import java.io.BufferedReader +import java.io.InputStreamReader +import java.io.OutputStreamWriter +import java.security.MessageDigest; + +class SlaveStatus { + + private Map slave_info = [:] + + SlaveStatus(String full_name) { + for (slave in Hudson.instance.slaves) { + def slaveComputer = slave.getComputer() + if ("${slaveComputer.getName()}" == full_name) { + def this_slave = ["name":slaveComputer.getName(), + "host":slave.getLauncher().getHost(), + "object":slaveComputer] + slave_info[slaveComputer.getName()] = this_slave + } + } + } + Map get_slave_info() { + return slave_info + } + void disconnect_node(slaveName) { + if (slaveName in slave_info) { + slave_info[slaveName]["object"].setTemporarilyOffline(true, null) + slave_info[slaveName]["object"].disconnect(null) + } + } + void delete_node(slaveName) { + if (slaveName in slave_info) { + slave_info[slaveName]["object"].doDoDelete() + } + } +} + +def get_node_to_create_env(workspace) { + def Properties conf + conf = new Properties() + conf.load(new FileInputStream("${workspace}/NODE_TO_CREATE.env")) + return conf +} + +def get_slave_info(node_name) { + def Map data = [:] + + def existing_slaves = new SlaveStatus("${node_name}"); + def existing_slaves_info = existing_slaves.get_slave_info() + existing_slaves_info.each{k, v -> + if( k != "${node_name}" ) { + return + } + def computer = v["object"] + //def user_id = computer.getNode().getUserId() + def remoteFS = computer.getAbsoluteRemoteFs() + def slave_name = computer.getName() + def numExecutors = computer.getNumExecutors() + def isAlive = computer.isAlive() + def label_string = computer.getNode().getLabelString() + def _launcher = computer.getLauncher() + assert _launcher instanceof hudson.plugins.sshslaves.SSHLauncher + def host = _launcher.getHost() + def port = _launcher.getPort() + def _credentials = _launcher.getCredentials() + def user_id = _credentials.getUsername() + def credential_id = _credentials.getId() + + data["slave_name"] = slave_name + data["user_id"] = user_id + data["remoteFS"] = remoteFS + data["numExecutors"] = numExecutors + data["isalive"] = isAlive + data["label_string"] = label_string + data["host"] = host + data["port"] = port + data["credential_id"] = credential_id + } + return data +} + +def append_backend_selection(workspace, request_file, label_string) { + // Append created node to trigger env + File[] files = new File("${workspace}").listFiles() + for (File file : files) { + if (file.isFile()) { + if (file.getName() == "${request_file}.env") { + def target_file = "${workspace}/${request_file}.env" + println "Writing ${label_string} to ${target_file}" + f = new File("${target_file}") + f.append("\nBACKEND_SELECTION=${label_string}") + } + } + } +} + +def create_new_node(project_name, project_name_hash, jenkins_home ) { + def node_name = "download_${project_name_hash}" + println "Creating node for ${project_name} to ${node_name}" + + // Load function modules + e = { filepath -> + evaluate(new File("${jenkins_home}" + "/init.groovy.d/" + filepath)) + } + create_slave_node = e("Module_Node") + + def parent_node_info = get_slave_info("download") + def target_node_info = get_slave_info("${node_name}") + println "Parent Node:" + println parent_node_info + println "Target Node:" + println target_node_info + def check_key_list = ["slave_name", "user_id", "remoteFS", "host", "port", "credential_id"] + check_key_list.each{k -> + assert parent_node_info.containsKey(k) + } + + // Jenkins instance + def instance = Jenkins.getInstance() + + // Required variables + def remoteFS = "${parent_node_info["remoteFS"]}/${node_name}" + def numExecutors = 1 + def thisLabelString = "snapshot_${project_name}" + def labelString = "" + if( target_node_info.containsKey("label_string") ) { + def previous_labels = "${target_node_info["label_string"]}" + if( "${previous_labels}".split(' ').contains("${thisLabelString}") ) { + labelString = "${previous_labels}" + } else { + labelString = "${thisLabelString} ${previous_labels}" + } + } else { + labelString = "${thisLabelString}" + } + + def sshHost = parent_node_info["host"] + def sshPort = parent_node_info["port"] + def sshCredentials = parent_node_info["credential_id"] + def userId = "jenkins" + def description = "${node_name}" + + create_slave_node( + instance, + node_name, + remoteFS, + numExecutors.toString(), + labelString, + sshHost, + sshPort, + sshCredentials, + userId, + description + ) + + // Save configurations + instance.save() + + return "${thisLabelString}" +} + +def __main__() { + + + try { + + def buildEnv = build.getEnvironment(listener) + + if (buildEnv["OBS_DISPATCHER_LOAD_BALANCING_ENABLED"] != "1") { + println "FEATURE NOT ENABLED" + return + } + + def project_name = "" + def project_name_hash = "" + def request_file = "" + try { + def node_env = get_node_to_create_env(buildEnv["WORKSPACE"]) + println node_env + project_name = node_env["PROJECT_NAME"] + request_file = node_env["REQUEST_FILE"] + println project_name + println request_file + MessageDigest digest = MessageDigest.getInstance("MD5"); + digest.update(project_name.bytes); + project_name_hash = new BigInteger(1, digest.digest()); + println project_name_hash + project_name_hash = project_name_hash % 100000000; + println project_name_hash + } catch(Exception ex) { + println "No env file. Skip creating node" + return + } + + // Create Jenkins Node + def thisLabelString = create_new_node("${project_name}", "${project_name_hash}", "${buildEnv["JENKINS_HOME"]}") + + // Append created node to trigger env + append_backend_selection("${buildEnv["WORKSPACE"]}", "${request_file}", "${thisLabelString}") + + } catch(Exception e) { + println 'Fail to create new node' + return + } + +} + +__main__() + +return 0 + diff --git a/job_jobs_dispatcher.py b/job_jobs_dispatcher.py index 0c272a0..6534e8c 100755 --- a/job_jobs_dispatcher.py +++ b/job_jobs_dispatcher.py @@ -76,6 +76,24 @@ def is_build_cycle_check_enabled(project_name): buildmonitor_db.disconnect_db() return int(ret_data) +def find_node_name(name, request_file): + node_postfix = '%08d' % (abs(hash(name)) % (10 ** 8)) + node_name = 'download-%s' % node_postfix + with open('NODE_TO_CREATE.env', 'w') as wf: + wf.write('PROJECT_NAME=%s\nREQUEST_FILE=%s' % (name, request_file)) + print 'find_node_name: %s, %s' % (name, request_file) + return 'snapshot_%s' % name + +def is_devel_project(project): + + if os.getenv('OBS_DISPATCHER_DEVEL_PREFIX', None): + for d in os.getenv('OBS_DISPATCHER_DEVEL_PREFIX').split(','): + if project.startswith(d): + print 'is_devel_project: True' + return True + print 'is_devel_project: False' + return False + def main(obs_event_fields): """The main body""" @@ -97,9 +115,15 @@ def main(obs_event_fields): 'Triggering the prerelease.' % (project) target_project_name = get_info_from_prerelease_name(project)[0] obs_event_fields['build_cycle_check'] = is_build_cycle_check_enabled(target_project_name) - trigger_next('%s#PRERELEASE#%s#%s' \ - % (make_prefix_trigger_file(target_project_name), project, event_type), \ - obs_event_fields) + if os.getenv("OBS_DISPATCHER_LOAD_BALANCING_ENABLED", "0") == "1": + request_file = '#LOAD_BALANCE#PRERELEASE#%s#%s' % (project, event_type) + print request_file + selected_node_name = find_node_name(target_project_name, request_file) + trigger_next(request_file, obs_event_fields) + else: + trigger_next('%s#PRERELEASE#%s#%s' \ + % (make_prefix_trigger_file(target_project_name), project, event_type), \ + obs_event_fields) elif(is_trbs_project(project)): if(isReadyForTrbs(build, project)): print 'All the repositories are published for project %s.' \ @@ -110,9 +134,14 @@ def main(obs_event_fields): print 'All the repositories are published for project %s.' \ 'Triggering the creating snapshot job..'%(project) obs_event_fields['build_cycle_check'] = is_build_cycle_check_enabled(project) - trigger_next('%s#SNAP#%s#%s' \ - % (make_prefix_trigger_file(project), project, event_type), \ - obs_event_fields) + if os.getenv("OBS_DISPATCHER_LOAD_BALANCING_ENABLED", "0") == "1" and not is_devel_project(project): + request_file = '#LOAD_BALANCE#SNAP#%s#%s' % (project, event_type) + selected_node_name = find_node_name(project, request_file) + trigger_next(request_file, obs_event_fields) + else: + trigger_next('%s#SNAP#%s#%s' \ + % (make_prefix_trigger_file(project), project, event_type), \ + obs_event_fields) else: trigger_next('#%s#%s' %(project,obs_event_fields['event_type']), obs_event_fields) diff --git a/packaging/jenkins-scripts.spec b/packaging/jenkins-scripts.spec index 1b34719..6db910e 100644 --- a/packaging/jenkins-scripts.spec +++ b/packaging/jenkins-scripts.spec @@ -244,6 +244,7 @@ fi %{destdir}/common/get_name_from_specfile.py %{destdir}/common/gbsutils.py %{destdir}/job_gbs_build_dispatcher.py +%{destdir}/job_add_new_node.groovy %files dependsgraph %defattr(-,jenkins,jenkins) -- 2.7.4