All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adaptc.mws.plugins.natives.NativePlugin.groovy Maven / Gradle / Ivy

There is a newer version: 1.9-1701809693
Show newest version
package com.adaptc.mws.plugins.natives

import com.adaptc.mws.plugins.*
import com.adaptc.mws.plugins.natives.utils.NativeUtils
import groovy.transform.Synchronized

/**
 * The Native Plugin is a replication of the Moab Native resource manager interface in MWS.
 * @author bsaville
 */
class NativePlugin extends AbstractPlugin {
	static description = "Basic implementation of a native (Wiki interface) plugin"

	static constraints = {
		environment(required: false)
		getCluster required: false, scriptableUrl: true
		getNodes required: false, scriptableUrl: true
		getVirtualMachines required: false, scriptableUrl: true
		getStorage required: false, scriptableUrl: true
		getJobs required: false, scriptableUrl: true
		jobCancel required: false, scriptableUrl: true
		jobModify required: false, scriptableUrl: true
		jobRequeue required: false, scriptableUrl: true
		jobResume required: false, scriptableUrl: true
		jobStart required: false, scriptableUrl: true
		jobSubmit required: false, scriptableUrl: true
		jobSuspend required: false, scriptableUrl: true
		nodeModify required: false, scriptableUrl: true
		nodePower required: false, scriptableUrl: true
		virtualMachinePower required: false, scriptableUrl: true
		startUrl required: false, scriptableUrl: true
		stopUrl required: false, scriptableUrl: true
		reportImages defaultValue: true
	}

	JobNativeTranslator jobNativeTranslator
	NodeNativeTranslator nodeNativeTranslator
	VirtualMachineNativeTranslator virtualMachineNativeTranslator
	StorageNativeTranslator storageNativeTranslator
	IJobRMService jobRMService
	INodeRMService nodeRMService
	IVirtualMachineRMService virtualMachineRMService
	IStorageRMService storageRMService
	ImageNativeTranslator imageNativeTranslator
	IPluginEventService pluginEventService
	DebugNativeTranslator debugNativeTranslator

	private def getConfigKey(String key) {
		if (config.containsKey(key))
			return config[key]
		return null
	}

	public void configure() throws InvalidPluginConfigurationException {
		imageNativeTranslator.pluginEventService = pluginEventService
	}

	public def verifyClusterQuery(Map params) {
		def wiki = params.body?.content ?: params.wiki
		if (!wiki)
			throw new WebServiceException(message(code:"nativePlugin.verify.wiki.empty.message"), 400)
		return debugNativeTranslator.verifyClusterWiki(wiki, id)
	}

	public def verifyWorkloadQuery(Map params) {
		def wiki = params.body?.content ?: params.wiki
		if (!wiki)
			throw new WebServiceException(message(code:"nativePlugin.verify.wiki.empty.message"), 400)
		return debugNativeTranslator.verifyWorkloadWiki(wiki, id)
	}

	/**
	 * Overrides the default implementation of poll so that a single
	 * cluster query can be used for both nodes and VMs.
	 * This is also synchronized so that only one poll can run at a time.
	 */
	@Synchronized
	public void poll() {
		def aggregateImagesInfo = new AggregateImagesInfo()
		def nodes = []
		def vms = []
		def storage = []
		if (getConfigKey("getCluster")) {
			log.debug("Polling getCluster URL")
			getCluster(aggregateImagesInfo)?.groupBy { it.class }?.each { Class clazz, List values ->
				switch (clazz) {
					case NodeReport.class:
						nodes = values
						break;
					case VirtualMachineReport.class:
						vms = values
						break;
					case StorageReport.class:
						storage = values
						break
					default:
						log.warn("Unknown object found from cluster query, ignoring values: ${clazz}")
						break;
				}
			}
		} else {
			log.debug("Polling getNodes, getVirtualMachines, and getStorage URLs")
			nodes = getNodes(aggregateImagesInfo)
			vms = getVirtualMachines(aggregateImagesInfo)
			storage = getStorage()
		}

		// Ensure that save is called EVERY time the poll is executed
		nodeRMService.save(nodes)
		virtualMachineRMService.save(vms)
		storageRMService.save(storage)

		log.debug("Polling getJobs URL")
		jobRMService.save(getJobs());

		// Save images
		if (config.reportImages) {
			log.debug("Creating images from cluster information")
			imageNativeTranslator.updateImages(id, aggregateImagesInfo)
		}
	}

	public void beforeStart() {
		try {
			def url = getConfigKey("startUrl")?.toURL()
			if (!url)
				return
			log.debug("Starting plugin ${id} with ${url}")
			def result = readURL(url)
			if (result.exitCode == 0)
				log.info("Started plugin ${id} (${result.content})")
			else
				log.warn("Could not start plugin ${id} (${result.exitCode}): ${result.content}")
		} catch (Exception e) {
			log.warn("Could not start plugin ${id} due to exception: ${e.message}")
		}
	}

	public void afterStop() {
		try {
			def url = getConfigKey("stopUrl")?.toURL()
			if (!url)
				return
			log.debug("Stopping plugin ${id} with ${url}")
			def result = readURL(url)
			if (result.exitCode == 0)
				log.info("Stopped plugin ${id} (${result.content})")
			else
				log.warn("Could not stop plugin ${id} (${result.exitCode}): ${result.content}")
		} catch (Exception e) {
			log.warn("Could not stop plugin ${id} due to exception: ${e.message}")
		}
	}

	public List getJobs() {
		def url = getConfigKey("getJobs")?.toURL()
		if (!url)
			return []
		def result = readURL(url)
		if (!hasError(result)) {
			return NativeUtils.parseWiki(result.content).collect { Map attrs ->
				jobNativeTranslator.createReport(pluginEventService, attrs)
			}
		}
		return []
	}

	public List getCluster(AggregateImagesInfo aggregateImagesInfo) {
		def url = getConfigKey("getCluster")?.toURL()
		if (!url)
			return []
		def result = readURL(url)
		if (!hasError(result)) {
			return NativeUtils.parseWiki(result.content).collect { Map attrs ->
				if (virtualMachineNativeTranslator.isVirtualMachineWiki(attrs)) {
					def imageInfo = new VMImageInfo()
					aggregateImagesInfo.vmImages << imageInfo
					return virtualMachineNativeTranslator.createReport(pluginEventService, attrs, imageInfo)
				} else if (storageNativeTranslator.isStorageWiki(attrs)) {
					return storageNativeTranslator.createReport(pluginEventService, attrs)
				} else { // Default to a node
					def imageInfo = new HVImageInfo()
					aggregateImagesInfo.hypervisorImages << imageInfo
					return nodeNativeTranslator.createReport(pluginEventService, attrs, imageInfo)
				}
			}
		}
		return []
	}

	public List getNodes(AggregateImagesInfo aggregateImagesInfo) {
		def url = getConfigKey("getNodes")?.toURL()
		if (!url)
			return []
		def result = readURL(url)
		if (!hasError(result)) {
			return NativeUtils.parseWiki(result.content).collect { Map attrs ->
				def imageInfo = new HVImageInfo()
				aggregateImagesInfo.hypervisorImages << imageInfo
				nodeNativeTranslator.createReport(pluginEventService, attrs, imageInfo)
			}
		}
		return []
	}

	public List getStorage() {
		def url = getConfigKey("getStorage")?.toURL()
		if (!url)
			return []
		def result = readURL(url)
		if (!hasError(result)) {
			return NativeUtils.parseWiki(result.content).collect { Map attrs ->
				storageNativeTranslator.createReport(pluginEventService, attrs)
			}
		}
		return []
	}

	public List getVirtualMachines(AggregateImagesInfo aggregateImagesInfo) {
		def url = getConfigKey("getVirtualMachines")?.toURL()
		if (!url)
			return []
		def result = readURL(url)
		if (!hasError(result)) {
			return NativeUtils.parseWiki(result.content).collect { Map attrs ->
				def imageInfo = new VMImageInfo()
				aggregateImagesInfo.vmImages << imageInfo
				virtualMachineNativeTranslator.createReport(pluginEventService, attrs, imageInfo)
			}
		}
		return []
	}

	public boolean jobCancel(String jobName) {
		def url = getConfigKey("jobCancel")?.toURL()
		if (!url)
			return false
		url.query = jobName
		log.debug("Canceling job ${jobName}")
		def result = readURL(url)
		return !hasError(result)
	}

	public boolean jobModify(String jobName, Map attributes) {
		def url = getConfigKey("jobModify")?.toURL()
		if (!url)
			return false
		def queryParams = [jobName]
		queryParams.addAll(attributes.collect { "${it.key}=" + (it.value?.contains(" ") ? "\"${it.value}\"" : it.value) })
		url.query = queryParams.join("&")
		log.debug("Modifying job ${jobName} with properties ${attributes}")
		def result = readURL(url)
		return !hasError(result)
	}

	public boolean jobResume(String jobName) {
		def url = getConfigKey("jobResume")?.toURL()
		if (!url)
			return false
		url.query = jobName
		log.debug("Resuming job ${jobName}")
		def result = readURL(url)
		return !hasError(result)
	}

	public boolean jobRequeue(String jobName) {
		def url = getConfigKey("jobRequeue")?.toURL()
		if (!url)
			return false
		url.query = jobName
		log.debug("Requeuing job ${jobName}")
		def result = readURL(url)
		return !hasError(result)
	}

	public boolean jobStart(String jobId, List nodes, String username) {
		def url = getConfigKey("jobStart")?.toURL()
		if (!url)
			return false
		url.query = [jobId, nodes.join(","), username].join("&")
		log.debug("Starting job ${jobId} with allocated nodes ${nodes} and user ${username}")
		def result = readURL(url)
		return !hasError(result)
	}

	public String jobSubmit(Map job, String submissionString, String submissionFlags) {
		def url = getConfigKey("jobSubmit")?.toURL()
		if (!url)
			return null
		// Spool file for submission string
		File spoolFile = File.createTempFile("mws-plugins-native-spool-", "")
		spoolFile.deleteOnExit()
		spoolFile << submissionString

		// Convert to wiki and call configured URL
		url.query = jobNativeTranslator.convertJobToWiki(job, spoolFile, submissionFlags)
				.collect { "${it.key}=" + (it.value?.toString()?.contains(" ") ? "\"${it.value}\"" : it.value) }.join("&")
		log.debug("Submitting job ${job.name}")
		def result = readURL(url)

		// Delete spool file immediately
		spoolFile.delete()

		// Return the first line of the output as the job ID to use, else the job name from the input
		if (!hasError(result))
			return result.content?.size()>0 ? result.content[0] : job.name
		return null
	}

	public boolean jobSuspend(String jobName) {
		def url = getConfigKey("jobSuspend")?.toURL()
		if (!url)
			return false
		url.query = jobName
		log.debug("Suspending job ${jobName}")
		def result = readURL(url)
		return !hasError(result)
	}

	public boolean nodeModify(List nodes, Map properties) {
		def url = getConfigKey("nodeModify")?.toURL()
		if (!url)
			return false
		url.query = nodes.join(",") + "&--set&"
		url.query += properties.collect { "${it.key}=" + (it.value?.contains(" ") ? "\"${it.value}\"" : it.value) }.join("&")
		log.debug("Modifying nodes ${nodes} with properties ${properties}")
		def result = readURL(url)
		return !hasError(result)
	}

	public boolean nodePower(List nodes, NodeReportPower state) {
		def url = getConfigKey("nodePower")?.toURL()
		if (!url)
			return false
		url.query = [nodes.join(","), state.name()].join("&")
		log.debug("Changing power state to ${state} for nodes ${nodes}")
		def result = readURL(url)
		return !hasError(result)
	}

	public boolean virtualMachinePower(List virtualMachines, NodeReportPower state) {
		def url = getConfigKey("virtualMachinePower")?.toURL()
		if (!url)
			return false
		url.query = [virtualMachines.join(","), state.name()].join("&")
		log.debug("Changing power state to ${state} for virtual machines ${virtualMachines}")
		def result = readURL(url)
		return !hasError(result)
	}

	private def readURL(URL url) {
		log.debug("Retrieving URL ${url}")
		try {
			if (url.protocol != "exec") {
				def content = url.readLines()
				log.debug("Received non-script content of ${content}")
				return [content: content, exitCode: 0]    // mimic success
			}

			// Exec protocol handling for exit code return
			def conn = url.openConnection()
			conn.connect()
			setEnvironment(conn)
			def content = ((InputStream) conn.content).readLines()
			def exitCode = conn.exitCode
			log.debug("Received script content of ${content} with exit code ${exitCode}")
			return [content: content, exitCode: exitCode]
		} catch (FileNotFoundException e) {
			log.error("Could not read from file ${url}, it does not exist!")
		} catch (IOException e) {
			log.error("Could not read from file ${url}: ${e.message}")
		}
		return null
	}

	private void setEnvironment(urlConnection) {
		def env = getConfigKey("environment")
		if (!env)
			return
		// parse into a map
		def envMap = env.tokenize("&").inject([:]) { Map map, entry ->
			def entrySplit = entry.tokenize("=")
			if (entrySplit.size() > 1)
				map[entrySplit[0]] = entrySplit[1]
			else
				map[entry] = null
			map
		}
		log.debug("Setting environment to ${env} for URL")
		urlConnection.setEnvironment(envMap)
	}

	/**
	 * Checks a result from readURL for errors.
	 * 

* Changed in 1.7 to always allow empty content * @param result * @return */ private boolean hasError(result) { if (result == null) return true // Account for workload queries which have an attribute called "ERROR" return result.exitCode != 0 || result.content == null || (result.content.size() != 0 && result.content[0].contains("ERROR") && !result.content[0].contains("ERROR=")) } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy