From 6d444920e754b298cf2c87a3bc7fc8950f5b716b Mon Sep 17 00:00:00 2001 From: itsikkes Date: Sun, 5 Jun 2016 23:41:12 +0300 Subject: [PATCH 1/3] UI change to generic connector config still needs to handle client cache problem --- monkey_business/cc/admin/ui/index.html | 27 ++--- .../cc/admin/ui/js/monkeysb-admin.js | 107 ++++++------------ monkey_business/cc/connectors/__init__.py | 10 +- monkey_business/cc/connectors/vcenter.py | 1 - monkey_business/cc/main.py | 55 ++++++++- 5 files changed, 101 insertions(+), 99 deletions(-) diff --git a/monkey_business/cc/admin/ui/index.html b/monkey_business/cc/admin/ui/index.html index 5705f9776..f85c8885f 100644 --- a/monkey_business/cc/admin/ui/index.html +++ b/monkey_business/cc/admin/ui/index.html @@ -129,26 +129,19 @@
- - -
-
- +
diff --git a/monkey_business/cc/admin/ui/js/monkeysb-admin.js b/monkey_business/cc/admin/ui/js/monkeysb-admin.js index 8c154298a..abe33ad34 100644 --- a/monkey_business/cc/admin/ui/js/monkeysb-admin.js +++ b/monkey_business/cc/admin/ui/js/monkeysb-admin.js @@ -12,8 +12,8 @@ const ICONS_EXT = ".png"; var jobsTable = undefined; var logsTable = undefined; -var vcenterCfg = undefined; var jobCfg = undefined; +var conCfg = undefined; var selectedJob = undefined; JSONEditor.defaults.theme = 'bootstrap3'; @@ -41,73 +41,8 @@ function initAdmin() { showLog(selectedJob); } ); - vcenterCfg = new JSONEditor(document.getElementById('vcenter-config'),{ - schema: { - type: "object", - title: "vcenter", - properties: { - address: { - title: "Address", - type: "string", - }, - port: { - title: "Port", - type: "integer", - }, - username: { - title: "Username", - type: "string", - }, - password: { - title: "Password", - type: "string", - }, - monkey_template_name: { - title: "Monkey Template Name", - type: "string", - }, - monkey_vm_info: { - title: "Monkey Creation VM", - type: "object", - properties: { - name: { - title: "Deployment Name", - type: "string", - }, - vm_folder: { - title: "VM Folder (opt.)", - type: "string", - }, - resource_pool: { - title: "Resource Pool (opt.)", - type: "string", - }, - datacenter_name: { - title: "Datacenter (opt.)", - type: "string", - }, - cluster_name: { - title: "Cluster (opt.)", - type: "string", - }, - datastore_name: { - title: "Datastore (opt.)", - type: "string", - }, - } - } - }, - options: { - "collapsed": true - }, - }, - disable_edit_json: false, - disable_properties: true, - }); - setInterval(updateJobs, 5000); setInterval(showLog, 5000); - loadVcenterConfig(); updateJobs(); } @@ -140,20 +75,39 @@ function updateJobs() { } jobsTable.draw(); - //enableJobsSelect(); }); } -function loadVcenterConfig() { - $.getJSON('/connector?type=VCenterConnector', function(json) { - vcenterCfg.setValue(json); +function loadConnectorsConfig() { + elem = document.getElementById('connectors-config'); + elem.innerHTML = "" + conCfg = new JSONEditor(elem,{ + schema: { + type: "object", + title: "Connector", + properties: { + connector: { + title: "Type", + $ref: "/connector", + } + }, + options: { + "collapsed": false + }, + }, + ajax: true, + disable_edit_json: false, + disable_collapse: true, + disable_properties: true, + no_additional_properties: true + }); + conCfg.on('ready',function() { + document.getElementById("btnSaveConnectorConfig").style.visibility = "visible"; }); } -function updateVcenterConfig() { - var vc_config = vcenterCfg.getValue() - vc_config["type"] = "VCenterConnector"; - +function updateConnectorConfig() { + var con_config = conCfg.getValue() $.ajax({ headers : { 'Accept' : 'application/json', @@ -161,9 +115,12 @@ function updateVcenterConfig() { }, url : '/connector', type : 'POST', - data : JSON.stringify(vc_config), + data : JSON.stringify(con_config.connector), success : function(response, textStatus, jqXhr) { console.log("New vcenter config successfully updated!"); + document.getElementById("btnSaveConnectorConfig").style.visibility = "hidden"; + elem = document.getElementById('connectors-config'); + elem.innerHTML = "" }, error : function(jqXHR, textStatus, errorThrown) { // log the error to the console diff --git a/monkey_business/cc/connectors/__init__.py b/monkey_business/cc/connectors/__init__.py index a6ae03932..b282d014a 100644 --- a/monkey_business/cc/connectors/__init__.py +++ b/monkey_business/cc/connectors/__init__.py @@ -65,6 +65,8 @@ class NetControllerJob(object): if self._logger: self._logger.log(text) + # external API + def get_job_properties(self): return self._properties @@ -80,4 +82,10 @@ class NetControllerJob(object): raise NotImplementedError() def get_results(self): - return [] \ No newline at end of file + return [] + + def get_state(self): + return None + + def stop(self): + raise NotImplementedError() \ No newline at end of file diff --git a/monkey_business/cc/connectors/vcenter.py b/monkey_business/cc/connectors/vcenter.py index bdd9c062b..4ddbf3c2d 100644 --- a/monkey_business/cc/connectors/vcenter.py +++ b/monkey_business/cc/connectors/vcenter.py @@ -12,7 +12,6 @@ class VCenterConnector(NetControllerConnector): "password": "", "monkey_template_name": "", "monkey_vm_info": { - "name": "Monkey Test", "datacenter_name": "", "vm_folder": "", "datastore_name": "", diff --git a/monkey_business/cc/main.py b/monkey_business/cc/main.py index afe03a646..a38549155 100644 --- a/monkey_business/cc/main.py +++ b/monkey_business/cc/main.py @@ -63,15 +63,39 @@ class Job(restful.Resource): class Connector(restful.Resource): + def _build_prop_dict(self, properties, job_obj=None): + res = dict() + for prop in properties: + res[prop] = dict({}) + res[prop]["default"] = properties[prop] + if type(properties[prop]) is int: + res[prop]["type"] = "number" + elif type(properties[prop]) is bool: + res[prop]["type"] = "boolean" + elif type(properties[prop]) is dict: + res[prop]["type"] = "object" + res[prop]["properties"] = self._build_prop_dict(properties[prop], job_obj) + else: + res[prop]["type"] = "string" + + if job_obj: + enum = job_obj.get_property_function(prop) + if enum: + properties[prop]["enum"] = list( + active_connectors[job_obj.connector_type.__name__].__getattribute__(enum)()) + return res + def get(self, **kw): contype = request.args.get('type') # if no type given - return list of types if not contype: conlist = [] + checked_con = [] # used for easy checking for reoccurring connectors for jobclass in available_jobs: - if jobclass.connector_type.__name__ not in conlist: - conlist.append(jobclass.connector_type.__name__) + if jobclass.connector_type.__name__ not in checked_con: + checked_con.append(jobclass.connector_type.__name__) + conlist.append({"title": jobclass.connector_type.__name__, "$ref": "/connector?type=" + jobclass.connector_type.__name__}) return {"oneOf": conlist} con = get_connector_by_name(contype) @@ -80,14 +104,34 @@ class Connector(restful.Resource): properties = mongo.db.connector.find_one({"type": con.__class__.__name__}) if properties: con.load_properties(properties) - ret = con.get_properties() - ret["password"] = "" # for better security, don't expose password - return ret + con_prop = con.get_properties() + con_prop["password"] = "" # for better security, don't expose password + + properties = self._build_prop_dict(con_prop) + properties["type"] = { + "type": "enum", + "enum": [contype], + "options": {"hidden": True} + } + + res = dict({ + "title": "%s Connector" % contype, + "type": "object", + "options": { + "disable_collapse": True, + "disable_properties": True, + }, + "properties": properties + }) + return res def post(self, **kw): settings_json = json.loads(request.data) contype = settings_json.get("type") + if not contype: + return {} + # preserve password if empty given properties = mongo.db.connector.find_one({"type": contype}) if properties and (not settings_json.has_key("password") or not settings_json["password"]): @@ -97,6 +141,7 @@ class Connector(restful.Resource): {"$set": settings_json}, upsert=True) + class JobCreation(restful.Resource): def get(self, **kw): jobtype = request.args.get('type') From 7a0092c0f40df63f8922f1daef8d96360ab6390a Mon Sep 17 00:00:00 2001 From: itsikkes Date: Mon, 6 Jun 2016 00:18:34 +0300 Subject: [PATCH 2/3] remove duplicate code --- monkey_business/cc/main.py | 77 ++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 44 deletions(-) diff --git a/monkey_business/cc/main.py b/monkey_business/cc/main.py index a38549155..7cf89d670 100644 --- a/monkey_business/cc/main.py +++ b/monkey_business/cc/main.py @@ -14,6 +14,7 @@ mongo = PyMongo(app) active_connectors = {} + class Root(restful.Resource): def get(self): return { @@ -25,7 +26,6 @@ class Root(restful.Resource): class Job(restful.Resource): def get(self, **kw): id = request.args.get('id') - timestamp = request.args.get('timestamp') action = request.args.get('action') if action == "log": @@ -33,7 +33,7 @@ class Job(restful.Resource): result = {} - if (id): + if id: return mongo.db.job.find_one_or_404({"_id": id}) else: result['timestamp'] = datetime.now().isoformat() @@ -63,35 +63,13 @@ class Job(restful.Resource): class Connector(restful.Resource): - def _build_prop_dict(self, properties, job_obj=None): - res = dict() - for prop in properties: - res[prop] = dict({}) - res[prop]["default"] = properties[prop] - if type(properties[prop]) is int: - res[prop]["type"] = "number" - elif type(properties[prop]) is bool: - res[prop]["type"] = "boolean" - elif type(properties[prop]) is dict: - res[prop]["type"] = "object" - res[prop]["properties"] = self._build_prop_dict(properties[prop], job_obj) - else: - res[prop]["type"] = "string" - - if job_obj: - enum = job_obj.get_property_function(prop) - if enum: - properties[prop]["enum"] = list( - active_connectors[job_obj.connector_type.__name__].__getattribute__(enum)()) - return res - def get(self, **kw): contype = request.args.get('type') # if no type given - return list of types if not contype: conlist = [] - checked_con = [] # used for easy checking for reoccurring connectors + checked_con = [] # used for easy checking for reoccurring connectors for jobclass in available_jobs: if jobclass.connector_type.__name__ not in checked_con: checked_con.append(jobclass.connector_type.__name__) @@ -107,7 +85,7 @@ class Connector(restful.Resource): con_prop = con.get_properties() con_prop["password"] = "" # for better security, don't expose password - properties = self._build_prop_dict(con_prop) + properties = _build_prop_dict(con_prop) properties["type"] = { "type": "enum", "enum": [contype], @@ -172,34 +150,22 @@ class JobCreation(restful.Resource): return {'status': 'bad state'} if job and job.connector_type.__name__ in active_connectors.keys(): - properties = { - "type": { + job_prop = job.get_job_properties() + properties = _build_prop_dict(job_prop, job) + + properties["type"] = { "type": "enum", "enum": [job.__class__.__name__], "options": {"hidden": True} } - } - if (jobid): + + if jobid: properties["_id"] = { "type": "enum", "enum": [jobid], "name": "ID", } - job_prop = job.get_job_properties() - for prop in job_prop: - properties[prop] = dict({}) - properties[prop]["default"] = job_prop[prop] - if type(job_prop[prop]) is int: - properties[prop]["type"] = "number" - elif type(job_prop[prop]) is bool: - properties[prop]["type"] = "boolean" - else: - properties[prop]["type"] = "string" - enum = job.get_property_function(prop) - if enum: - properties[prop]["enum"] = list(active_connectors[job.connector_type.__name__].__getattribute__(enum)()) - res = dict({ "title": "%s Job" % jobtype, "type": "object", @@ -272,6 +238,29 @@ def normalize_obj(obj): return obj +def _build_prop_dict(properties, job_obj=None): + res = dict() + for prop in properties: + res[prop] = dict({}) + res[prop]["default"] = properties[prop] + if type(properties[prop]) is int: + res[prop]["type"] = "number" + elif type(properties[prop]) is bool: + res[prop]["type"] = "boolean" + elif type(properties[prop]) is dict: + res[prop]["type"] = "object" + res[prop]["properties"] = _build_prop_dict(properties[prop], job_obj) + else: + res[prop]["type"] = "string" + + if job_obj: + enum = job_obj.get_property_function(prop) + if enum: + res[prop]["enum"] = list( + active_connectors[job_obj.connector_type.__name__].__getattribute__(enum)()) + return res + + def output_json(obj, code, headers=None): obj = normalize_obj(obj) resp = make_response(bson.json_util.dumps(obj), code) From fdc562bbed8cf6483b45184f7b139b1799900cba Mon Sep 17 00:00:00 2001 From: itsikkes Date: Mon, 6 Jun 2016 11:50:33 +0300 Subject: [PATCH 3/3] improve db sync and task log bugfix --- .../cc/admin/ui/js/monkeysb-admin.js | 2 +- monkey_business/cc/connectors/__init__.py | 2 ++ monkey_business/cc/main.py | 17 +++++++++-------- monkey_business/cc/tasks_manager.py | 17 +++++++++++++---- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/monkey_business/cc/admin/ui/js/monkeysb-admin.js b/monkey_business/cc/admin/ui/js/monkeysb-admin.js index abe33ad34..247381a0a 100644 --- a/monkey_business/cc/admin/ui/js/monkeysb-admin.js +++ b/monkey_business/cc/admin/ui/js/monkeysb-admin.js @@ -71,7 +71,7 @@ function updateJobs() { var jobsList = json.objects; for (var i = 0; i < jobsList.length; i++) { - jobsTable.row.add([jobsList[i].id, jobsList[i].creation_time, jobsList[i].type,jobsList[i].execution.state, JSON.stringify(jobsList[i].properties)]); + jobsTable.row.add([jobsList[i].id, jobsList[i].creation_time, jobsList[i].type,jobsList[i].state, JSON.stringify(jobsList[i].properties)]); } jobsTable.draw(); diff --git a/monkey_business/cc/connectors/__init__.py b/monkey_business/cc/connectors/__init__.py index b282d014a..113de9db4 100644 --- a/monkey_business/cc/connectors/__init__.py +++ b/monkey_business/cc/connectors/__init__.py @@ -42,8 +42,10 @@ class NetControllerConnector(object): def set_logger(self, logger): self.log = logger + class NetControllerJob(object): connector_type = NetControllerConnector + _connector = None _logger = None diff --git a/monkey_business/cc/main.py b/monkey_business/cc/main.py index 7cf89d670..8a1d37fcb 100644 --- a/monkey_business/cc/main.py +++ b/monkey_business/cc/main.py @@ -143,9 +143,12 @@ class JobCreation(restful.Resource): job.load_job_properties(loaded_job.get("properties")) if action == "delete": - if loaded_job.get("execution")["state"] == "pending": - mongo.db.job.remove({"_id": bson.ObjectId(jobid)}) - return {'status': 'ok'} + if loaded_job.get("state") == "pending": + res = mongo.db.job.remove({"_id": bson.ObjectId(jobid), "state": "pending"}) + if res["nModified"] == 1: + return {'status': 'ok'} + else: + return {'status': 'error deleting'} else: return {'status': 'bad state'} @@ -202,19 +205,17 @@ class JobCreation(restful.Resource): return {'status': 'failed'} else: - execution_state = {"taskid": "", - "state" : "pending"} new_job = { "creation_time": datetime.now(), "type": jobtype, "properties": parsed_prop, - "execution": execution_state, + "taskid": "", + "state" : "pending", } jobid = mongo.db.job.insert(new_job) async = tasks_manager.run_task.delay(jobid) - execution_state["taskid"] = async.id mongo.db.job.update({"_id": jobid}, - {"$set": {"execution": execution_state}}) + {"$set": {"taskid": async.id}}) return {'status': 'created'} diff --git a/monkey_business/cc/tasks_manager.py b/monkey_business/cc/tasks_manager.py index 0b57b8bbc..03a82e537 100644 --- a/monkey_business/cc/tasks_manager.py +++ b/monkey_business/cc/tasks_manager.py @@ -33,13 +33,17 @@ class JobExecution(object): def __init__(self, mongo, jobinfo): self._mongo = mongo self._jobinfo = jobinfo - self.update_job_state("processing") job_class = get_jobclass_by_name(self._jobinfo["type"]) con = job_class.connector_type() refresh_connector_config(self._mongo, con) self._job = job_class(con, self) self._job.load_job_properties(self._jobinfo["properties"]) + prev_log = self._mongo.db.results.find_one({"jobid": self._jobinfo["_id"]}) + if prev_log: + self._log = prev_log["log"] + else: + self._log = [] def get_job(self): return self._job @@ -48,9 +52,8 @@ class JobExecution(object): self._jobinfo = self._mongo.db.job.find_one({"_id": self._jobinfo["_id"]}) def update_job_state(self, state): - self._jobinfo["execution"]["state"] = state self._mongo.db.job.update({"_id": self._jobinfo["_id"]}, - {"$set": {"execution": self._jobinfo["execution"]}}) + {"$set": {"state": state}}) def _log_resutls(self, res): self._mongo.db.results.update({"jobid": self._jobinfo["_id"]}, @@ -65,13 +68,15 @@ class JobExecution(object): def run(self): self.log("Starting job") - res = False + + res = None try: res = self._job.run() except Exception, e: self.log("Exception raised while running: %s" % e) self.update_job_state("error") return False + if res: self.log("Done job startup") self.update_job_state("running") @@ -95,6 +100,10 @@ class JobExecution(object): @celery.task def run_task(jobid): print "searching for ", jobid + aquire_task = mongo.db.job.update({"_id": jobid, "state": "pending"}, {"$set": {"state": "processing"}}) + if aquire_task["nModified"] != 1: + return False + job_info = mongo.db.job.find_one({"_id": jobid}) if not job_info: return False