sort of cool now
This commit is contained in:
		
						commit
						dfdd42f006
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| devserverroot/ | ||||
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| default: devserver | ||||
| 
 | ||||
| devserver: | ||||
| 	/bin/bash bin/devserver | ||||
							
								
								
									
										12
									
								
								Pipfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								Pipfile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| [[source]] | ||||
| name = "pypi" | ||||
| url = "https://pypi.org/simple" | ||||
| verify_ssl = true | ||||
| 
 | ||||
| [dev-packages] | ||||
| 
 | ||||
| [packages] | ||||
| sanelogging = "*" | ||||
| 
 | ||||
| [requires] | ||||
| python_version = "3.6" | ||||
							
								
								
									
										35
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| { | ||||
|     "_meta": { | ||||
|         "hash": { | ||||
|             "sha256": "d189f59cdb9acb7b516c87ead0bf88b4ebc5d7057caf2f46d4cb25e25b317035" | ||||
|         }, | ||||
|         "pipfile-spec": 6, | ||||
|         "requires": { | ||||
|             "python_version": "3.6" | ||||
|         }, | ||||
|         "sources": [ | ||||
|             { | ||||
|                 "name": "pypi", | ||||
|                 "url": "https://pypi.org/simple", | ||||
|                 "verify_ssl": true | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     "default": { | ||||
|         "colorlog": { | ||||
|             "hashes": [ | ||||
|                 "sha256:3cf31b25cbc8f86ec01fef582ef3b840950dea414084ed19ab922c8b493f9b42", | ||||
|                 "sha256:450f52ea2a2b6ebb308f034ea9a9b15cea51e65650593dca1da3eb792e4e4981" | ||||
|             ], | ||||
|             "version": "==4.0.2" | ||||
|         }, | ||||
|         "sanelogging": { | ||||
|             "hashes": [ | ||||
|                 "sha256:ae018de50f00ace45b34dbade66b511651cb1758a7c26e262841651b2dd6f2cf" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==1.0.1" | ||||
|         } | ||||
|     }, | ||||
|     "develop": {} | ||||
| } | ||||
							
								
								
									
										15
									
								
								bin/devserver
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										15
									
								
								bin/devserver
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,15 @@ | ||||
| #!/bin/bash | ||||
| 
 | ||||
| set -x | ||||
| 
 | ||||
| THISDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd -P)" | ||||
| UPDIR="$( cd "$( dirname "$THISDIR" )" >/dev/null 2>&1 && pwd -P)" | ||||
| export STRPC_BASE="$UPDIR/devserverroot" | ||||
| export PYTHONPATH="$UPDIR" | ||||
| 
 | ||||
| cd "$UPDIR" | ||||
| if [[ ! -d "$STRPC_BASE" ]]; then | ||||
| 	mkdir -p "$STRPC_BASE" | ||||
| fi | ||||
| pipenv run bin/strpc | ||||
| 
 | ||||
							
								
								
									
										12
									
								
								bin/strpc
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										12
									
								
								bin/strpc
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,12 @@ | ||||
| #!/usr/bin/env python3 | ||||
| #234567891123456789212345678931234567894123456789512345678961234567897123456789 | ||||
| # encoding: utf-8 | ||||
| 
 | ||||
| from strpc import STRpcRunnerService | ||||
| 
 | ||||
| def main(): | ||||
|     s = STRpcRunnerService() | ||||
|     s.start() | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
							
								
								
									
										5
									
								
								strpc/__init__.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										5
									
								
								strpc/__init__.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,5 @@ | ||||
| #!/usr/bin/env python3 | ||||
| #234567891123456789212345678931234567894123456789512345678961234567897123456789 | ||||
| # encoding: utf-8 | ||||
| 
 | ||||
| from .strpc import STRpcRunnerService | ||||
							
								
								
									
										10
									
								
								strpc/job-template.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								strpc/job-template.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| { | ||||
| 	"berlin.sneak.type": "strpcjob", | ||||
| 	"ready": false, | ||||
| 	"run": { | ||||
| 		"type": "docker", | ||||
| 		"image": "ubuntu:18.04", | ||||
| 		"command": "/bin/bash -c 'cat /proc/cpuinfo'" | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										214
									
								
								strpc/strpc.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										214
									
								
								strpc/strpc.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,214 @@ | ||||
| #!/usr/bin/env python3 | ||||
| #234567891123456789212345678931234567894123456789512345678961234567897123456789 | ||||
| # encoding: utf-8 | ||||
| 
 | ||||
| from datetime import datetime, tzinfo, timedelta | ||||
| from sanelogging import log | ||||
| from time import sleep | ||||
| import json | ||||
| import os | ||||
| import pathlib | ||||
| import socket | ||||
| import subprocess | ||||
| import uuid | ||||
| 
 | ||||
| def dirs_in_dir(path): | ||||
|     assert os.path.isdir(path) | ||||
|     items = os.listdir(path) | ||||
|     output = [] | ||||
|     for fn in items: | ||||
|         if os.path.isdir(path + '/' + fn): | ||||
|             output.append(path + '/' + fn) | ||||
|     return output | ||||
| 
 | ||||
| class simple_utc(tzinfo): | ||||
|     def tzname(self,**kwargs): | ||||
|         return "UTC" | ||||
|     def utcoffset(self, dt): | ||||
|         return timedelta(0) | ||||
| 
 | ||||
| def isodatetime(): | ||||
|     return str( | ||||
|         datetime.utcnow().replace(tzinfo=simple_utc()).isoformat() | ||||
|     ).replace('+00:00', 'Z') | ||||
| 
 | ||||
| def read_file(path): | ||||
|     with open(path) as f: | ||||
|         data = f.read() | ||||
|     return data | ||||
| 
 | ||||
| # FIXME is there some auto reading-and-writing JSONFile class out there | ||||
| def read_json_file(path): | ||||
|     return json.loads(read_file(path)) | ||||
| 
 | ||||
| def json_pretty(obj): | ||||
|     return json.dumps(obj, sort_keys=True, indent=4, separators=(',', ': ')) | ||||
| 
 | ||||
| def write_json_file(path, obj): | ||||
|     write_file(path, json_pretty(obj)) | ||||
| 
 | ||||
| def write_file(path,content): | ||||
|     with open(path, 'w') as file: | ||||
|         file.write(content) | ||||
| 
 | ||||
| class STRpcJob(object): | ||||
|     def __init__(self,rpcserver,newjobdir): | ||||
|         self.uid = str(uuid.uuid4()) | ||||
|         self.parent = rpcserver | ||||
|         self.jobdir = newjobdir | ||||
|         self.shortname = os.path.basename(newjobdir) | ||||
|         if not os.path.isdir(self.jobdir): | ||||
|             raise Exception("job directory not found") | ||||
|         self.statusfile = self.jobdir + '/' + 'status.json' | ||||
|         self.jobfile = self.jobdir + '/' + 'job.json' | ||||
|         self.jobdesc = None | ||||
|         self.state = None | ||||
|         self.error = None | ||||
| 
 | ||||
|         if os.path.exists(self.statusfile): | ||||
|             # job was known to previous version | ||||
|             x = read_json_file(self.statusfile) | ||||
|             self.uid = x['uid'] | ||||
|             self.shortname = x['shortname'] | ||||
|             if x['state'] not in ['init', 'ready']: | ||||
|                 self.set_state('old') | ||||
|         else: | ||||
|             self.set_state('init') | ||||
| 
 | ||||
|         self.crank() | ||||
| 
 | ||||
|     def write_template_jobfile(self, path): | ||||
|         thisdir = os.path.dirname(os.path.realpath(__file__)) | ||||
|         template = read_json_file(thisdir + '/' + 'job-template.json') | ||||
|         write_json_file(path, template) | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         return "<STRrpcJob(%s,'%s'>" % (self.uid, self.shortname) | ||||
| 
 | ||||
|     def write_status(self): | ||||
|         write_file(self.statusfile, json_pretty(self.to_json())) | ||||
| 
 | ||||
|     def read_jobfile(self): | ||||
|         jobdesc = None | ||||
|         try: | ||||
|             jobdesc = read_json_file(self.jobfile) | ||||
|         except(FileNotFoundError): | ||||
|             jobfile_template_filename = self.jobdir + '/' + 'job-template.json' | ||||
|             if not os.path.exists(jobfile_template_filename): | ||||
|                 self.write_template_jobfile(jobfile_template_filename) | ||||
|             return | ||||
|         # FIXME validate jobdesc before changing state or updating job-ject | ||||
|         self.jobdesc = jobdesc | ||||
|         self.set_state('ready') | ||||
| 
 | ||||
|     def set_state(self, state): | ||||
|         STATES = [ | ||||
|             'done', | ||||
|             'error', | ||||
|             'init', | ||||
|             'old', | ||||
|             'ready', | ||||
|             'running', | ||||
|         ] | ||||
|         assert state in STATES | ||||
|         self.state = state | ||||
|         log.info("%s is in state %s" % (self, self.state)) | ||||
|         self.write_status() | ||||
| 
 | ||||
|     def crank(self): | ||||
|         if self.state == 'init': | ||||
|             self.read_jobfile() | ||||
|         elif self.state == 'ready': | ||||
|             self.run() | ||||
| 
 | ||||
|     def to_json(self): | ||||
|         me = { | ||||
|             'state': self.state, | ||||
|             'hostname': self.parent.hostname, | ||||
|             'jobdir': self.jobdir, | ||||
|             'shortname': self.shortname, | ||||
|             'uid': self.uid, | ||||
|         } | ||||
|         if self.error: | ||||
|             me['error'] = self.error | ||||
|         return me | ||||
| 
 | ||||
|     def ready_to_run(self): | ||||
|         if self.state == 'ready': | ||||
|             return True | ||||
|         return False | ||||
| 
 | ||||
|     def run(self): | ||||
|         self.set_state('running') | ||||
|         log.die("run job here") | ||||
| 
 | ||||
| 
 | ||||
| class STRpcRunnerService(object): | ||||
| 
 | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.dir = os.environ.get('STRPC_BASE','/var/run/strpc') | ||||
|         self.hostname = socket.gethostname() | ||||
|         self.hostdir = self.dir + '/' + self.hostname | ||||
|         self.logdir = self.hostdir + '/logs' | ||||
|         self.statusfile = self.hostdir + '/status.json' | ||||
|         self.jobdir = self.hostdir + '/jobs' | ||||
|         self.jobs = [] | ||||
|         self.running = True | ||||
|         if not os.path.isdir(self.dir): | ||||
|             raise Exception("STRPC base directory not found") | ||||
|         self.setup() | ||||
| 
 | ||||
|     def setup(self): | ||||
|         pathlib.Path(self.jobdir).mkdir(parents=True, exist_ok=True) | ||||
|         pathlib.Path(self.logdir).mkdir(parents=True, exist_ok=True) | ||||
|         self.write_status_file() | ||||
| 
 | ||||
|     def write_status_file(self): | ||||
|         log.info("writing out server status") | ||||
|         write_json_file(self.statusfile, { | ||||
|             'started': isodatetime(), | ||||
|             'hostname': self.hostname, | ||||
|             'jobs': [ job.to_json() for job in self.jobs ], | ||||
|         }) | ||||
| 
 | ||||
|     def start(self): | ||||
|         log.info("strpcrunner starting up on %s" % self.hostname) | ||||
|         os.chdir(self.dir) | ||||
|         self.run_forever() | ||||
| 
 | ||||
|     def scan_jobsdir(self, jobsdir): | ||||
|         existing_jobdirs = [ job.jobdir for job in self.jobs ] | ||||
|         for newjobdir in dirs_in_dir(jobsdir): | ||||
|             if newjobdir not in existing_jobdirs: | ||||
|                 log.info("found new job: " + os.path.basename(newjobdir)) | ||||
|                 self.jobs.append(STRpcJob(self,newjobdir)) | ||||
|                 self.write_status_file() | ||||
| 
 | ||||
|     def run_jobs(self): | ||||
|         for job in self.jobs: | ||||
|             if job.ready_to_run(): | ||||
|                 job.run() | ||||
| 
 | ||||
|     def run_forever(self): | ||||
|         STATUS_INTERVAL = 60 | ||||
|         LOOP_INTERVAL = 0.5 | ||||
|         STATUS_ITERATIONS = STATUS_INTERVAL / LOOP_INTERVAL | ||||
|         i = 0 | ||||
| 
 | ||||
|         while self.running: | ||||
|             i = i + 1 | ||||
|             if i >= STATUS_ITERATIONS: | ||||
|                 i = 0 | ||||
|                 self.write_status_file() | ||||
|             self.scan_jobsdir(self.jobdir) | ||||
|             for job in self.jobs: | ||||
|                 job.crank() | ||||
|             sleep(LOOP_INTERVAL) | ||||
| 
 | ||||
| def main(): | ||||
|     s = STRpcRunnerService() | ||||
|     s.start() | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user