diff --git a/monkey_business/cc/admin/ui/index.html b/monkey_business/cc/admin/ui/index.html index b938c8199..5705f9776 100644 --- a/monkey_business/cc/admin/ui/index.html +++ b/monkey_business/cc/admin/ui/index.html @@ -56,6 +56,22 @@ + +
+
+ Log +
+
+ + + + + + +
TimeData
+
+
+ diff --git a/monkey_business/cc/admin/ui/js/monkeysb-admin.js b/monkey_business/cc/admin/ui/js/monkeysb-admin.js index 0033d6beb..bbc3a6634 100644 --- a/monkey_business/cc/admin/ui/js/monkeysb-admin.js +++ b/monkey_business/cc/admin/ui/js/monkeysb-admin.js @@ -11,8 +11,10 @@ const ICONS_EXT = ".png"; // If variable from local storage != null, assign it, otherwise set it's default value. var jobsTable = undefined; +var logsTable = undefined; var vcenterCfg = undefined; var jobCfg = undefined; +var selectedJob = undefined; JSONEditor.defaults.theme = 'bootstrap3'; @@ -22,6 +24,9 @@ function initAdmin() { "ordering": true, "order": [[1, "desc"]], }); + logsTable = $("#logs-table").DataTable({ + "ordering": false, + }); jobsTable.on( 'click', 'tr', function () { if ( $(this).hasClass('selected') ) { $(this).removeClass('selected'); @@ -31,7 +36,9 @@ function initAdmin() { $(this).addClass('selected'); } jobdata = jobsTable.row(this).data(); - createNewJob(jobdata[0], jobdata[3]); + selectedJob = jobdata[0]; + createNewJob(selectedJob, jobdata[3]); + showLog(selectedJob); } ); vcenterCfg = new JSONEditor(document.getElementById('vcenter-config'),{ @@ -98,12 +105,31 @@ function initAdmin() { disable_properties: true, }); - window.setTimeout(updateJobs, 5000); + setInterval(updateJobs, 2000); + setInterval(showLog, 2000); loadVcenterConfig(); updateJobs(); } +function showLog() { + logsTable.clear(); + + if (!selectedJob) { + return; + } + + $.getJSON('/job?action=log&id=' + selectedJob, function(json) { + var logsList = json.log; + for (var i = 0; i < logsList.length; i++) { + logsTable.row.add([logsList[i][0], logsList[i][1]]); + } + + logsTable.draw(); + + }); +} + function updateJobs() { $.getJSON('/job', function(json) { jobsTable.clear(); @@ -150,9 +176,15 @@ function updateVcenterConfig() { } +function emptySelection() { + showLog(); + selectedJob = undefined; + jobsTable.$('tr.selected').removeClass('selected'); +} + function createNewJob(id, state) { if (!id) { - jobsTable.$('tr.selected').removeClass('selected'); + emptySelection(); } elem = document.getElementById('job-config'); diff --git a/monkey_business/cc/common.py b/monkey_business/cc/common.py index 79822a492..04307233b 100644 --- a/monkey_business/cc/common.py +++ b/monkey_business/cc/common.py @@ -6,8 +6,8 @@ available_jobs = [VCenterJob, DemoJob] def get_connector_by_name(name): for jobclass in available_jobs: - if name == jobclass.connector.__name__: - return jobclass.connector() + if name == jobclass.connector_type.__name__: + return jobclass.connector_type() return None diff --git a/monkey_business/cc/connectors/__init__.py b/monkey_business/cc/connectors/__init__.py index c1688d2f6..a6ae03932 100644 --- a/monkey_business/cc/connectors/__init__.py +++ b/monkey_business/cc/connectors/__init__.py @@ -36,9 +36,17 @@ class NetControllerConnector(object): def disconnect(self): return + def log(self, text): + pass + + def set_logger(self, logger): + self.log = logger class NetControllerJob(object): - connector = NetControllerConnector + connector_type = NetControllerConnector + _connector = None + _logger = None + _properties = { # property: value } @@ -47,9 +55,15 @@ class NetControllerJob(object): } - def __init__(self, existing_connector = None): - if existing_connector: - self.connector = existing_connector + def __init__(self, existing_connector=None, logger=None): + self._connector = existing_connector + self._logger = logger + if logger: + self._connector.set_logger(self.log) + + def log(self, text): + if self._logger: + self._logger.log(text) def get_job_properties(self): return self._properties diff --git a/monkey_business/cc/connectors/demo.py b/monkey_business/cc/connectors/demo.py index e89ebce17..f32e0d2cd 100644 --- a/monkey_business/cc/connectors/demo.py +++ b/monkey_business/cc/connectors/demo.py @@ -35,7 +35,7 @@ class DemoConnector(NetControllerConnector): return [] class DemoJob(NetControllerJob): - connector = DemoConnector + connector_type = DemoConnector _properties = { "vlan": 0, } @@ -45,4 +45,5 @@ class DemoJob(NetControllerJob): def run(self): import time + self.log("Running demo job...") time.sleep(30) \ No newline at end of file diff --git a/monkey_business/cc/connectors/vcenter.py b/monkey_business/cc/connectors/vcenter.py index 0486ac396..44977d412 100644 --- a/monkey_business/cc/connectors/vcenter.py +++ b/monkey_business/cc/connectors/vcenter.py @@ -63,20 +63,25 @@ class VCenterConnector(NetControllerConnector): def get_entities_on_vlan(self, vlanid): return [] - def deploy_monkey(self, vlanid, vm_name): + def deploy_monkey(self, vm_name): if not self._properties["monkey_template_name"]: raise Exception("Monkey template not configured") + if not self.is_connected(): + self.connect() + vcontent = self._service_instance.RetrieveContent() # get updated vsphare state monkey_template = self._get_obj(vcontent, [vim.VirtualMachine], self._properties["monkey_template_name"]) if not monkey_template: raise Exception("Monkey template not found") - task = self._clone_vm(vcontent, monkey_template, vm_name) - if not task: + self.log("Cloning vm: (%s -> %s)" % (monkey_template, vm_name)) + monkey_vm = self._clone_vm(vcontent, monkey_template, vm_name) + if not monkey_vm: raise Exception("Error deploying monkey VM") + self.log("Finished cloning") - monkey_vm = task.entity + return monkey_vm def disconnect(self): Disconnect(self._service_instance) @@ -101,7 +106,7 @@ class VCenterConnector(NetControllerConnector): else: datastore = self._get_obj(vcontent, [vim.Datastore], vm.datastore[0].info.name) - # get vm target resoucepool + # get vm target resource pool if self._properties["monkey_vm_info"]["resource_pool"]: resource_pool = self._get_obj(vcontent, [vim.ResourcePool], self._properties["monkey_vm_info"]["resource_pool"]) else: @@ -116,12 +121,12 @@ class VCenterConnector(NetControllerConnector): clonespec = vim.vm.CloneSpec() clonespec.location = relospec + self.log("Starting clone task with the following info: %s" % repr({"folder": destfolder, "name": name, "clonespec": clonespec})) + task = vm.Clone(folder=destfolder, name=name, spec=clonespec) return self._wait_for_task(task) - - @staticmethod - def _wait_for_task(task): + def _wait_for_task(self, task): """ wait for a vCenter task to finish """ task_done = False while not task_done: @@ -129,6 +134,7 @@ class VCenterConnector(NetControllerConnector): return task.info.result if task.info.state == 'error': + self.log("Error waiting for task: %s" % repr(task.info)) return None @staticmethod @@ -153,9 +159,9 @@ class VCenterConnector(NetControllerConnector): class VCenterJob(NetControllerJob): - connector = VCenterConnector + connector_type = VCenterConnector _properties = { - "vlan": 0, + "vlan": "", "vm_name": "", } _enumerations = { @@ -163,5 +169,9 @@ class VCenterJob(NetControllerJob): } def run(self): - pass + if not self._connector: + return False + + monkey_vm = self._connector.deploy_monkey(self._properties["vm_name"]) + return True diff --git a/monkey_business/cc/main.py b/monkey_business/cc/main.py index 1d18fda6b..afe03a646 100644 --- a/monkey_business/cc/main.py +++ b/monkey_business/cc/main.py @@ -24,8 +24,13 @@ class Root(restful.Resource): class Job(restful.Resource): def get(self, **kw): - id = kw.get('id') + id = request.args.get('id') timestamp = request.args.get('timestamp') + action = request.args.get('action') + + if action == "log": + return {"log": get_job_log(id)} + result = {} if (id): @@ -65,8 +70,8 @@ class Connector(restful.Resource): if not contype: conlist = [] for jobclass in available_jobs: - if jobclass.connector.__name__ not in conlist: - conlist.append(jobclass.connector.__name__) + if jobclass.connector_type.__name__ not in conlist: + conlist.append(jobclass.connector_type.__name__) return {"oneOf": conlist} con = get_connector_by_name(contype) @@ -101,7 +106,7 @@ class JobCreation(restful.Resource): res = [] update_connectors() for con in available_jobs: - if con.connector.__name__ in active_connectors: + if con.connector_type.__name__ in active_connectors: res.append({"title": con.__name__, "$ref": "/jobcreate?type=" + con.__name__}) return {"oneOf": res} @@ -121,7 +126,7 @@ class JobCreation(restful.Resource): else: return {'status': 'bad state'} - if job and job.connector.__name__ in active_connectors.keys(): + if job and job.connector_type.__name__ in active_connectors.keys(): properties = { "type": { "type": "enum", @@ -148,7 +153,7 @@ class JobCreation(restful.Resource): properties[prop]["type"] = "string" enum = job.get_property_function(prop) if enum: - properties[prop]["enum"] = list(active_connectors[job.connector.__name__].__getattribute__(enum)()) + properties[prop]["enum"] = list(active_connectors[job.connector_type.__name__].__getattribute__(enum)()) res = dict({ "title": "%s Job" % jobtype, @@ -229,11 +234,17 @@ def output_json(obj, code, headers=None): return resp +def get_job_log(jobid): + res = mongo.db.results.find_one({"jobid": bson.ObjectId(jobid)}) + if res: + return res["log"] + return [] + def update_connectors(): for con in available_jobs: - connector_name = con.connector.__name__ + connector_name = con.connector_type.__name__ if connector_name not in active_connectors: - active_connectors[connector_name] = con.connector() + active_connectors[connector_name] = con.connector_type() if not active_connectors[connector_name].is_connected(): refresh_connector_config(mongo, active_connectors[connector_name]) diff --git a/monkey_business/cc/tasks_manager.py b/monkey_business/cc/tasks_manager.py index e3995a744..0b57b8bbc 100644 --- a/monkey_business/cc/tasks_manager.py +++ b/monkey_business/cc/tasks_manager.py @@ -23,6 +23,7 @@ fapp.config.from_object('dbconfig') celery = make_celery(fapp) mongo = PyMongo(fapp) + class JobExecution(object): _jobinfo = None _job = None @@ -35,9 +36,10 @@ class JobExecution(object): self.update_job_state("processing") job_class = get_jobclass_by_name(self._jobinfo["type"]) - con = job_class.connector() + con = job_class.connector_type() refresh_connector_config(self._mongo, con) - self._job = job_class(con) + self._job = job_class(con, self) + self._job.load_job_properties(self._jobinfo["properties"]) def get_job(self): return self._job @@ -56,22 +58,27 @@ class JobExecution(object): upsert=True) def log(self, text): - self._log.append("[%s] %s" % (datetime.now(), text)) + self._log.append([datetime.now().isoformat(), text]) self._mongo.db.results.update({"jobid": self._jobinfo["_id"]}, {"$set": {"log": self._log}}, upsert=True) def run(self): self.log("Starting job") + res = False try: - self._job.run() + res = self._job.run() except Exception, e: self.log("Exception raised while running: %s" % e) self.update_job_state("error") return False - self.log("done job startup") - self.update_job_state("running") - return True + if res: + self.log("Done job startup") + self.update_job_state("running") + else: + self.log("Job startup error") + self.update_job_state("error") + return res def get_results(self): self.log("Trying to get results") @@ -92,9 +99,15 @@ def run_task(jobid): if not job_info: return False - job_exec = JobExecution(mongo, job_info) + job_exec = None + try: + job_exec = JobExecution(mongo, job_info) + except Exception, e: + print "init JobExecution exception - ", e + return False + if not job_exec.get_job(): - job_exec.update_job_state(job_info, "error") + job_exec.update_job_state("error") return False if not job_exec.run():