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