def delete(self, software_rq): """ A ``DELETE`` to this endpoint will delete the requested software tag .. http:delete:: /api/v1/software/<str:softwarename> HTTP/1.1 **Request** .. sourcecode:: http DELETE /api/v1/software/Autodesk%20Maya HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 204 NO_CONTENT :statuscode 204: the software tag was deleted or didn't exist """ if isinstance(software_rq, STRING_TYPES): software = Software.query.filter_by(software=software_rq).first() else: software = Software.query.filter_by(id=software_rq).first() if not software: return jsonify(None), NO_CONTENT db.session.delete(software) db.session.commit() logger.info("Deleted software %s", software.software) return jsonify(None), NO_CONTENT
def delete(self, agent_id): """ Delete a single agent .. http:delete:: /api/v1/agents/(uuid:agent_id) HTTP/1.1 **Request (agent exists)** .. sourcecode:: http DELETE /api/v1/agents/b25ee7eb-9586-439a-b131-f5d022e0d403 HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 204 NO CONTENT Content-Type: application/json :statuscode 204: the agent was deleted or did not exist """ agent = Agent.query.filter_by(id=agent_id).first() if agent is None: return jsonify(None), NO_CONTENT else: db.session.delete(agent) db.session.commit() assign_tasks.delay() return jsonify(None), NO_CONTENT
def delete(self, pathmap_id): """ A ``DELETE`` to this endpoint will remove the specified pathmap .. http:delete:: /api/v1/pathmaps/<int:pathmap_id> HTTP/1.1 **Request** .. sourcecode:: http DELETE /api/v1/pathmaps/1 HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 204 NO_CONTENT :statuscode 204: the path map was deleted or did not exist in the first place """ pathmap = PathMap.query.filter_by(id=pathmap_id).first() if not pathmap: return jsonify(None), NO_CONTENT db.session.delete(pathmap) db.session.commit() logger.info("deleted pathmap id %s", pathmap_id) return jsonify(None), NO_CONTENT
def delete(self, jobtype_name, software): """ A ``DELETE`` to this endpoint will delete the requested software requirement from the specified jobtype, creating a new version of the jobtype in the process .. http:delete:: /api/v1/jobtypes/[<str:name>|<int:id>]/software_requirements/<int:id> HTTP/1.1 **Request** .. sourcecode:: http DELETE /api/v1/jobtypes/TestJobType/software_requirements/1 HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 204 NO CONTENT :statuscode 204: the software requirement was deleted or didn't exist """ if isinstance(jobtype_name, STRING_TYPES): jobtype = JobType.query.filter_by(name=jobtype_name).first() else: jobtype = JobType.query.filter_by(id=jobtype_name).first() if not jobtype: return (jsonify(error="JobType %s not found" % jobtype_name), NOT_FOUND) jobtype_version = JobTypeVersion.query.filter_by(jobtype=jobtype).order_by("version desc").first() if not jobtype_version: return jsonify(error="JobType has no versions"), NOT_FOUND new_version = JobTypeVersion() for name in JobTypeVersion.types().columns: if name not in JobTypeVersion.types().primary_keys: setattr(new_version, name, getattr(jobtype_version, name)) new_version.version += 1 for old_req in jobtype_version.software_requirements: if old_req.software.software != software: new_req = JobTypeSoftwareRequirement() for name in JobTypeSoftwareRequirement.types().columns: setattr(new_req, name, getattr(old_req, name)) new_req.jobtype_version = new_version db.session.add(new_req) db.session.add(new_version) db.session.commit() logger.info( "Deleted software requirement %s for jobtype %s, creating " "new version %s", software, jobtype.id, new_version.version, ) return jsonify(None), NO_CONTENT
def post(self): """ A ``POST`` to this endpoint will create a new job queue. .. http:post:: /api/v1/jobqueues/ HTTP/1.1 **Request** .. sourcecode:: http POST /api/v1/jobqueues/ HTTP/1.1 Accept: application/json { "name": "Test Queue" } **Response** .. sourcecode:: http HTTP/1.1 201 CREATED Content-Type: application/json { "weight": 10, "jobs": [], "minimum_agents": null, "priority": 5, "name": "Test Queue", "maximum_agents": null, "id": 1, "parent": null, "parent_jobqueue_id": null } :statuscode 201: a new job queue was created :statuscode 400: there was something wrong with the request (such as invalid columns being included) :statuscode 409: a job queue with that name already exists """ jobqueue = JobQueue.query.filter_by(name=g.json["name"]).first() if jobqueue: return (jsonify(error="Job queue %s already exists" % g.json["name"]), CONFLICT) jobqueue = JobQueue(**g.json) db.session.add(jobqueue) db.session.flush() jobqueue.fullpath = jobqueue.path() db.session.add(jobqueue) db.session.commit() jobqueue_data = jobqueue.to_dict() logger.info("Created job queue %s: %r", jobqueue.name, jobqueue_data) return jsonify(jobqueue_data), CREATED
def delete(self, jobtype_name, software): """ A ``DELETE`` to this endpoint will delete the requested software requirement from the specified jobtype, creating a new version of the jobtype in the process .. http:delete:: /api/v1/jobtypes/[<str:name>|<int:id>]/software_requirements/<int:id> HTTP/1.1 **Request** .. sourcecode:: http DELETE /api/v1/jobtypes/TestJobType/software_requirements/1 HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 204 NO CONTENT :statuscode 204: the software requirement was deleted or didn't exist """ if isinstance(jobtype_name, STRING_TYPES): jobtype = JobType.query.filter_by(name=jobtype_name).first() else: jobtype = JobType.query.filter_by(id=jobtype_name).first() if not jobtype: return (jsonify(error="JobType %s not found" % jobtype_name), NOT_FOUND) jobtype_version = JobTypeVersion.query.filter_by( jobtype=jobtype).order_by("version desc").first() if not jobtype_version: return jsonify(error="JobType has no versions"), NOT_FOUND new_version = JobTypeVersion() for name in JobTypeVersion.types().columns: if name not in JobTypeVersion.types().primary_keys: setattr(new_version, name, getattr(jobtype_version, name)) new_version.version += 1 for old_req in jobtype_version.software_requirements: if old_req.software.software != software: new_req = JobTypeSoftwareRequirement() for name in JobTypeSoftwareRequirement.types().columns: setattr(new_req, name, getattr(old_req, name)) new_req.jobtype_version = new_version db.session.add(new_req) db.session.add(new_version) db.session.commit() logger.info( "Deleted software requirement %s for jobtype %s, creating " "new version %s", software, jobtype.id, new_version.version) return jsonify(None), NO_CONTENT
def get(self, software_rq, version_name): """ A ``GET`` to this endpoint will return the specified version .. http:get:: /api/v1/software/<str:softwarename>/versions/<str:version> HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/software/Autodesk%20Maya/versions/2014 HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "version": "2013", "id": 1, "rank": 100, "discovery_function_name": null } :statuscode 200: no error :statuscode 404: the requested software tag or version was not found """ if isinstance(software_rq, STRING_TYPES): software = Software.query.filter_by(software=software_rq).first() else: software = Software.query.filter_by(id=software_rq).first() if not software: return jsonify(error="Requested software not found"), NOT_FOUND if isinstance(version_name, STRING_TYPES): version = SoftwareVersion.query.filter( SoftwareVersion.software==software, SoftwareVersion.version==version_name).first() else: version = SoftwareVersion.query.filter_by(id=version_name).first() if not version: return jsonify(error="Requested version not found"), NOT_FOUND out = { "id": version.id, "version": version.version, "rank": version.rank, "discovery_function_name": version.discovery_function_name} return jsonify(out), OK
def get(self, job_id, task_id, attempt): """ A ``GET`` to this endpoint will return a list of all known logs that are associated with this attempt at running this task .. http:get:: /api/v1/jobs/<job_id>/tasks/<task_id>/attempts/<attempt>/logs/ HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/jobs/4/tasks/1300/attempts/5/logs/ HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "agent_id": "3087ada4-290a-45b0-8c1a-21db4cd284fc", "created_on": "2014-09-03T10:58:59.754880", "identifier": "2014-09-03_10-58-59_4_4ee02475335911e4a935c86000cbf5fb.csv" } ] :statuscode 200: no error :statuscode 404: the specified task was not found """ task = Task.query.filter_by(id=task_id, job_id=job_id).first() if not task: return jsonify(task_id=task_id, job_id=job_id, error="Specified task not found"), NOT_FOUND association_objects = TaskTaskLogAssociation.query.filter( TaskTaskLogAssociation.task == task, TaskTaskLogAssociation.attempt == attempt) out = [] for item in association_objects: log = item.log out.append({ "identifier": log.identifier, "created_on": log.created_on, "agent_id": str(log.agent_id) }) return jsonify(out), OK
def get(self, job_id, task_id, attempt, log_identifier): """ A ``GET`` to this endpoint will return metadata about the specified logfile .. http:get:: /api/v1/jobs/<job_id>/tasks/<task_id>/attempts/<attempt>/logs/<log_identifier> HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/jobs/4/tasks/1300/attempts/5/logs/2014-09-03_10-58-59_4_4ee02475335911e4a935c86000cbf5fb.csv HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "id": 147, "identifier": "2014-09-03_10-58-59_4_4ee02475335911e4a935c86000cbf5fb.csv", "created_on": "2014-09-03T10:58:59.754880", "agent_id": "836ce137-6ad4-443f-abb9-94c4465ff87c" } :statuscode 200: no error :statuscode 404: task or logfile not found """ task = Task.query.filter_by(id=task_id, job_id=job_id).first() if not task: return jsonify(task_id=task_id, job_id=job_id, error="Specified task not found"), NOT_FOUND log = TaskLog.query.filter_by(identifier=log_identifier).first() if not log: return jsonify(task_id=task_id, job_id=job_id, error="Specified log not found"), NOT_FOUND association = TaskTaskLogAssociation.query.filter_by( task=task, log=log, attempt=attempt).first() if not association: return jsonify(task_id=task.id, log=log.identifier, error="Specified log not found in task"), NOT_FOUND return jsonify(log.to_dict(unpack_relationships=False))
def get(self, software_rq, version_name): """ A ``GET`` to this endpoint will return just the python code for detecting whether this software version is installed on an agent. .. http:get:: /api/v1/software/[<str:software_name>|<int:software_id>]/versions/<str:version>/code HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/software/Blender/versions/2.72/code HTTP/1.1 Accept: text/x-python **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: text/x-python def blender_2_72_installed() return True :statuscode 200: no error :statuscode 404: software or version not found or this software version has no discovery code defined """ if isinstance(software_rq, STRING_TYPES): software = Software.query.filter_by(software=software_rq).first() else: software = Software.query.filter_by(id=software_rq).first() if not software: return jsonify(error="Requested software not found"), NOT_FOUND version = SoftwareVersion.query.filter( SoftwareVersion.software==software, SoftwareVersion.version==version_name).first() if not version: return jsonify(error="Requested version not found"), NOT_FOUND if not version.discovery_code: return jsonify(error="Specified software version has no discovery " "code"), NOT_FOUND return version.discovery_code, OK, {"Content-Type": "text/x-python"}
def get(self, software_rq): """ A ``GET`` to this endpoint will return the requested software tag .. http:get:: /api/v1/software/<str:softwarename> HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/software/Autodesk%20Maya HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "software": "Autodesk Maya", "id": 1, "versions": [ { "version": "2013", "id": 1, "rank": 100 }, { "version": "2014", "id": 2, "rank": 200 } ] } :statuscode 200: no error :statuscode 404: the requested software tag was not found """ if isinstance(software_rq, STRING_TYPES): software = Software.query.filter_by(software=software_rq).first() else: software = Software.query.filter_by(id=software_rq).first() if not software: return jsonify(error="Requested software not found"), NOT_FOUND return jsonify(software.to_dict()), OK
def get(self, job_id, task_id, attempt): """ A ``GET`` to this endpoint will return a list of all known logs that are associated with this attempt at running this task .. http:get:: /api/v1/jobs/<job_id>/tasks/<task_id>/attempts/<attempt>/logs/ HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/jobs/4/tasks/1300/attempts/5/logs/ HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "agent_id": "3087ada4-290a-45b0-8c1a-21db4cd284fc", "created_on": "2014-09-03T10:58:59.754880", "identifier": "2014-09-03_10-58-59_4_4ee02475335911e4a935c86000cbf5fb.csv" } ] :statuscode 200: no error :statuscode 404: the specified task was not found """ task = Task.query.filter_by(id=task_id, job_id=job_id).first() if not task: return jsonify(task_id=task_id, job_id=job_id, error="Specified task not found"), NOT_FOUND association_objects = TaskTaskLogAssociation.query.filter( TaskTaskLogAssociation.task == task, TaskTaskLogAssociation.attempt == attempt) out = [] for item in association_objects: log = item.log out.append({"identifier": log.identifier, "created_on": log.created_on, "agent_id": str(log.agent_id)}) return jsonify(out), OK
def delete(self, agent_id, software_name, version_name): """ A ``DELETE`` to this endpoint will remove the specified software version from the list of supported software in this agent .. http:delete:: /api/v1/agents/<str:agent_id>/software/<str:software>/versions/<str:version> HTTP/1.1 **Request** .. sourcecode:: http DELETE /api/v1/agents/bbf55143-f2b1-4c15-9d41-139bd8057931/software/Blender/versions/2.72 HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 NO CONTENT :statuscode 204: the software version has been removed from the supported versions on this agent or has not been on the list in the first place :statuscode 404: agent not found """ agent = Agent.query.filter_by(id=agent_id).first() if agent is None: return jsonify(error="Agent %r not found" % agent_id), NOT_FOUND software = Software.query.filter_by(software=software_name).first() if not software: return (jsonify(error="Software %s not found" % software_name), NOT_FOUND) version = SoftwareVersion.query.filter_by( software=software, version=version_name).first() if not version: return (jsonify(error="Version %s not found" % version_name), NOT_FOUND) if version not in agent.software_versions: return jsonify(), NO_CONTENT agent.software_versions.remove(version) db.session.add(agent) db.session.commit() return jsonify(), NO_CONTENT
def get(self, tagname=None): """ A ``GET`` to this endpoint will list all agents associated with this tag. .. http:get:: /api/v1/tags/<str:tagname>/agents/ HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/tags/interesting/agents/ HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 201 CREATED Content-Type: application/json [ { "hostname": "agent3", "id": 1, "href": "/api/v1/agents/1 } ] :statuscode 200: the list of agents associated with this tag is returned :statuscode 404: the tag specified does not exist """ if isinstance(tagname, STRING_TYPES): tag = Tag.query.filter_by(tag=tagname).first() else: tag = Tag.query.filter_by(id=tagname).first() if tag is None: return jsonify(error="tag %s not found" % tagname), NOT_FOUND out = [] for agent in tag.agents: out.append({ "id": agent.id, "hostname": agent.hostname, "href": url_for(".single_agent_api", agent_id=agent.id)}) return jsonify(out), OK
def schema(): """ Returns the basic schema of :class:`.JobQueue` .. http:get:: /api/v1/jobqueues/schema HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/jobqueues/schema HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "id": "INTEGER", "name": VARCHAR(255)", "minimum_agents": "INTEGER", "maximum_agents": "INTEGER", "priority": "INTEGER", "weight": "INTEGER", "parent_jobqueue_id": "INTEGER" } :statuscode 200: no error """ return jsonify(JobQueue.to_schema()), OK
def schema(): """ Returns the basic schema of :class:`.Tag` .. http:get:: /api/v1/tags/schema/ HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/tags/schema/ HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "id": "INTEGER", "tag": "VARCHAR(64)" } :statuscode 200: no error """ return jsonify(Tag.to_schema())
def delete(self, jobtype_name): """ A ``DELETE`` to this endpoint will delete the requested jobtype .. http:delete:: /api/v1/jobtypes/[<str:name>|<int:id>] HTTP/1.1 **Request** .. sourcecode:: http DELETE /api/v1/jobtypes/TestJobType HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 204 NO CONTENT :statuscode 204: the jobtype was deleted or didn't exist """ if isinstance(jobtype_name, STRING_TYPES): jobtype = JobType.query.filter(JobType.name == jobtype_name).first() else: jobtype = JobType.query.filter(JobType.id == jobtype_name).first() if jobtype: logger.debug("jobtype %s will be deleted", jobtype.name) db.session.delete(jobtype) db.session.commit() logger.info("jobtype %s has been deleted", jobtype.name) return jsonify(None), NO_CONTENT
def delete(self, jobtype_name): """ A ``DELETE`` to this endpoint will delete the requested jobtype .. http:delete:: /api/v1/jobtypes/[<str:name>|<int:id>] HTTP/1.1 **Request** .. sourcecode:: http DELETE /api/v1/jobtypes/TestJobType HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 204 NO CONTENT :statuscode 204: the jobtype was deleted or didn't exist """ if isinstance(jobtype_name, STRING_TYPES): jobtype = JobType.query.filter( JobType.name == jobtype_name).first() else: jobtype = JobType.query.filter(JobType.id == jobtype_name).first() if jobtype: logger.debug("jobtype %s will be deleted", jobtype.name) db.session.delete(jobtype) db.session.commit() logger.info("jobtype %s has been deleted", jobtype.name) return jsonify(None), NO_CONTENT
def schema(): """ Returns the basic schema of :class:`.Agent` .. http:get:: /api/v1/pathmaps/schema HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/pathmaps/schema HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "id": "INTEGER", "path_linux": "VARCHAR(512)", "path_windows": "VARCHAR(512)", "path_osx": "VARCHAR(512)", "tag": "VARCHAR(64)" } :statuscode 200: no error """ out = PathMap.to_schema() del out["tag_id"] out["tag"] = "VARCHAR(%s)" % config.get("max_tag_length") return jsonify(out)
def delete(self, software_rq, version_name): """ A ``DELETE`` to this endpoint will delete the requested software version .. http:delete:: /api/v1/software/<str:softwarename>/versions/<str:version> HTTP/1.1 **Request** .. sourcecode:: http DELETE /api/v1/software/Autodesk%20Maya/versions/2013 HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 204 NO_CONTENT :statuscode 204: the software version was deleted or didn't exist :statuscode 404: the software specified does not exist """ if isinstance(software_rq, STRING_TYPES): software = Software.query.filter_by(software=software_rq).first() else: software = Software.query.filter_by(id=software_rq).first() if not software: return jsonify(error="Requested software not found"), NOT_FOUND if isinstance(version_name, STRING_TYPES): version = SoftwareVersion.query.filter( SoftwareVersion.software==software, SoftwareVersion.version==version_name).first() else: version = SoftwareVersion.query.filter_by(id=version_name).first() if not version: return jsonify(None), NO_CONTENT db.session.delete(version) db.session.commit() logger.info("deleted software version %s for software %s", version.id, software.software) return jsonify(None), NO_CONTENT
def get(self, software_rq): """ A ``GET`` to this endpoint will list all known versions for this software .. http:get:: /api/v1/software/<str:softwarename>/versions/ HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/software/Autodesk%20Maya/versions/ HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "version": "2013", "id": 1, "rank": 100 }, { "version": "2014", "id": 2, "rank": 200 } ] :statuscode 200: no error :statuscode 404: the requested software tag was not found """ if isinstance(software_rq, STRING_TYPES): software = Software.query.filter_by(software=software_rq).first() else: software = Software.query.filter_by(id=software_rq).first() if not software: return jsonify(error="Requested software not found"), NOT_FOUND out = [{"version": x.version, "id": x.id, "rank": x.rank} for x in software.versions] return jsonify(out), OK
def get(self): """ A ``GET`` to this endpoint will return a list of all registered path maps, with id. It can be made with a for_agent query parameter, in which case it will return only those path maps that apply to that agent. .. http:get:: /api/v1/pathmaps/ HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/pathmaps/ HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "id": 1, "path_osx": "/mnt/nfs", "path_windows": "\\\\domains\\cifs_server", "path_linux": "/mnt/nfs" }, { "id": 7, "path_osx": "/renderout", "path_windows": "c:\\renderout", "path_linux": "/renderout" "tag": "usual", } ] :statuscode 200: no error """ query = PathMap.query for_agent = get_uuid_argument("for_agent") if for_agent: query = query.filter(or_(PathMap.tag == None, PathMap.tag.has(Tag.agents.any(Agent.id == for_agent)))) logger.debug("Query: %s", str(query)) output = [] for map in query: map_dict = map.to_dict(unpack_relationships=False) if map.tag: map_dict["tag"] = map.tag.tag del map_dict["tag_id"] output.append(map_dict) return jsonify(output), OK
def get(self, tagname=None): """ A ``GET`` to this endpoint will return the referenced tag, either by name or id, including a list of agents and jobs associated with it. .. http:get:: /api/v1/tags/<str:tagname> HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/tags/interesting HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "agents": [{ "hostname": "agent3", "href": "/api/v1/agents/94522b7e-817b-4358-95da-670b31aad624", "id": 1 }], "id": 1, "jobs": [], "tag": "interesting" } :statuscode 200: no error :statuscode 404: tag not found """ if isinstance(tagname, STRING_TYPES): tag = Tag.query.filter_by(tag=tagname).first() else: tag = Tag.query.filter_by(id=tagname).first() if tag is None: return jsonify(error="tag `%s` not found" % tagname), NOT_FOUND tag_dict = tag.to_dict(unpack_relationships=("agents", "jobs")) return jsonify(tag_dict), OK
def delete(self, queue_rq): """ A ``DELETE`` to this endpoint will delete the specified job queue .. http:delete:: /api/v1/jobqueue/[<str:name>|<int:id>] **Request** .. sourcecode:: http DELETE /api/v1/jobqueues/Test%20Queue HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 204 NO_CONTENT :statuscode 204: the job queue was deleted or didn't exist :statuscode 409: the job queue cannot be deleted because it still contains jobs or child queues """ if isinstance(queue_rq, STRING_TYPES): jobqueue = JobQueue.query.filter_by(name=queue_rq).first() else: jobqueue = JobQueue.query.filter_by(id=queue_rq).first() if not jobqueue: return jsonify(), NO_CONTENT num_sub_queues = JobQueue.query.filter_by(parent=jobqueue).count() if num_sub_queues > 0: return (jsonify(error="Cannot delete: job queue has child queues"), CONFLICT) num_jobs = Job.query.filter_by(queue=jobqueue).count() if num_jobs > 0: return (jsonify(error="Cannot delete: job queue has jobs assigned"), CONFLICT) db.session.delete(jobqueue) db.session.commit() logger.info("Deleted job queue %s", jobqueue.name) return jsonify(), NO_CONTENT
def delete(self, queue_rq): """ A ``DELETE`` to this endpoint will delete the specified job queue .. http:delete:: /api/v1/jobqueue/[<str:name>|<int:id>] **Request** .. sourcecode:: http DELETE /api/v1/jobqueues/Test%20Queue HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 204 NO_CONTENT :statuscode 204: the job queue was deleted or didn't exist :statuscode 409: the job queue cannot be deleted because it still contains jobs or child queues """ if isinstance(queue_rq, STRING_TYPES): jobqueue = JobQueue.query.filter_by(name=queue_rq).first() else: jobqueue = JobQueue.query.filter_by(id=queue_rq).first() if not jobqueue: return jsonify(), NO_CONTENT num_sub_queues = JobQueue.query.filter_by(parent=jobqueue).count() if num_sub_queues > 0: return (jsonify(error="Cannot delete: job queue has child queues"), CONFLICT) num_jobs = Job.query.filter_by(queue=jobqueue).count() if num_jobs > 0: return (jsonify( error="Cannot delete: job queue has jobs assigned"), CONFLICT) db.session.delete(jobqueue) db.session.commit() logger.info("Deleted job queue %s", jobqueue.name) return jsonify(), NO_CONTENT
def get(self, queue_rq): """ A ``GET`` to this endpoint will return the requested job queue .. http:get:: /api/v1/jobqueues/[<str:name>|<int:id>] HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/jobqueues/Test%20Queue HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "id": 1, "parent": [], "jobs": [], "weight": 10, "parent_jobqueue_id": null, "priority": 5, "minimum_agents": null, "name": "Test Queue", "maximum_agents": null } :statuscode 200: no error :statuscode 404: the requested job queue was not found """ if isinstance(queue_rq, STRING_TYPES): jobqueue = JobQueue.query.filter_by(name=queue_rq).first() else: jobqueue = JobQueue.query.filter_by(id=queue_rq).first() if not jobqueue: return (jsonify(error="Requested job queue %r not found" % queue_rq), NOT_FOUND) return jsonify(jobqueue.to_dict()), OK
def get(self, jobtype_name, version): """ A ``GET`` to this endpoint will return just the python code for this version of the specified jobtype. .. http:get:: /api/v1/jobtypes/[<str:name>|<int:id>]/versions/<int:version>/code HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/jobtypes/TestJobType/versions/1/code HTTP/1.1 Accept: text/x-python **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: text/x-python from pyfarm.jobtypes.core.jobtype import JobType class TestJobType(JobType): def get_command(self): return "/usr/bin/touch" def get_arguments(self): return [os.path.join( self.assignment_data["job"]["data"]["path"], "%04d" % self.assignment_data["tasks"][0]["frame"])] :statuscode 200: no error :statuscode 404: jobtype or version not found """ if isinstance(jobtype_name, STRING_TYPES): jt_tuple = db.session.query(JobType, JobTypeVersion).filter( JobType.id == JobTypeVersion.jobtype_id, JobType.name == jobtype_name, JobTypeVersion.version == version).first() else: jt_tuple = db.session.query(JobType, JobTypeVersion).filter( JobType.id == JobTypeVersion.jobtype_id, JobType.id == jobtype_name, JobTypeVersion.version == version).first() if not jt_tuple: return (jsonify(error="JobType %s, version %s not found" % (jobtype_name, version)), NOT_FOUND) jobtype, jobtype_version = jt_tuple return Response(jobtype_version.code, OK, mimetype="text/x-python")
def get(self, version): """ A ``GET`` to this endpoint will return the update package as a zip file the specified version .. http:get:: /api/v1/agents/updates/<string:version> HTTP/1.1 **Request** .. sourcecode:: http PUT /api/v1/agents/updates/1.2.3 HTTP/1.1 Accept: application/zip **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/zip <binary data> :statuscode 200: The update file was found and is returned :statuscode 301: The update can be found under a different URL :statuscode 400: there was something wrong with the request (such as an invalid version number specified or the mime type not being application/zip) """ if not VERSION_REGEX.match(version): return (jsonify( error="Version is not an acceptable version number"), BAD_REQUEST) filename = "pyfarm-agent-%s.zip" % version if UPDATES_WEBDIR: return redirect(join(UPDATES_WEBDIR, filename)) update_file = join(UPDATES_DIR, filename) if not isfile(update_file): return (jsonify(error="Specified update not found"), NOT_FOUND) return send_file(update_file)
def get(self, agent_id): """ A ``GET`` to this endpoint will return a list of all software versions available on this agent. .. http:get:: /api/v1/agents/<str:agent_id>/software/ HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/agents/bbf55143-f2b1-4c15-9d41-139bd8057931/software/ HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "software": "Blender", "version": "2.72" } ] :statuscode 200: no error :statuscode 404: agent not found """ agent = Agent.query.filter_by(id=agent_id).first() if agent is None: return jsonify(error="Agent %r not found" % agent_id), NOT_FOUND out = [] for version in agent.software_versions: software_dict = { "software": version.software.software, "version": version.version } out.append(software_dict) return jsonify(out), OK
def get(self, version): """ A ``GET`` to this endpoint will return the update package as a zip file the specified version .. http:get:: /api/v1/agents/updates/<string:version> HTTP/1.1 **Request** .. sourcecode:: http PUT /api/v1/agents/updates/1.2.3 HTTP/1.1 Accept: application/zip **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/zip <binary data> :statuscode 200: The update file was found and is returned :statuscode 301: The update can be found under a different URL :statuscode 400: there was something wrong with the request (such as an invalid version number specified or the mime type not being application/zip) """ if not VERSION_REGEX.match(version): return (jsonify(error="Version is not an acceptable version number"), BAD_REQUEST) filename = "pyfarm-agent-%s.zip" % version if UPDATES_WEBDIR: return redirect(join(UPDATES_WEBDIR, filename)) update_file = join(UPDATES_DIR, filename) if not isfile(update_file): return (jsonify(error="Specified update not found"), NOT_FOUND) return send_file(update_file)
def get(self, group_id): """ A ``GET`` to this endpoint will return the requested job group .. http:get:: /api/v1/jobgroups/<int:id> HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/jobgroups/2 HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "id": 2, "user": "******", "main_jobtype": "Test JobType", "jobs": [], "title": "Test Group" } :statuscode 200: no error :statuscode 404: the requested job group was not found """ jobgroup = JobGroup.query.filter_by(id=group_id).first() if not jobgroup: return (jsonify(error="Requested job group %s not found" % group_id), NOT_FOUND) jobgroup_data = jobgroup.to_dict() jobgroup_data.pop("user_id", None) jobgroup_data.pop("main_jobtype_id", None) return jsonify(jobgroup_data), OK
def delete(self, tagname=None): """ A ``DELETE`` to this endpoint will delete the tag under this URI, including all relations to tags or jobs. .. http:delete:: /api/v1/tags/<str:tagname> HTTP/1.1 **Request** .. sourcecode:: http DELETE /api/v1/tags/interesting HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 201 CREATED Content-Type: application/json { "id": 1, "tag": "interesting" } :statuscode 204: the tag was deleted or did not exist in the first place """ if isinstance(tagname, STRING_TYPES): tag = Tag.query.filter_by(tag=tagname).first() else: tag = Tag.query.filter_by(id=tagname).first() if tag is None: return jsonify(None), NO_CONTENT db.session.delete(tag) db.session.commit() logger.info("deleted tag %s", tag.tag) return jsonify(None), NO_CONTENT
def put(self, version): """ A ``PUT`` to this endpoint will upload a new version of pyfarm-agent to be used for agent auto-updates. The update must be a zip file. .. http:put:: /api/v1/agents/updates/<string:version> HTTP/1.1 **Request** .. sourcecode:: http PUT /api/v1/agents/updates/1.2.3 HTTP/1.1 Content-Type: application/zip <binary data> **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json :statuscode 201: The update was put in place :statuscode 400: there was something wrong with the request (such as an invalid version number specified or the mime type not being application/zip) """ if request.mimetype != "application/zip": return (jsonify(error="Data for agent updates must be " "application/zip"), BAD_REQUEST) if not VERSION_REGEX.match(version): return (jsonify( error="Version is not an acceptable version number"), BAD_REQUEST) path = join(UPDATES_DIR, "pyfarm-agent-%s.zip" % version) with open(path, "wb+") as zip_file: zip_file.write(request.data) return "", CREATED
def get(self, pathmap_id): """ A ``GET`` to this endpoint will return a single path map specified by pathmap_id .. http:get:: /api/v1/pathmaps/<int:pathmap_id> HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/pathmaps/1 HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "id": 1, "path_osx": "/mnt/nfs", "path_windows": "\\\\domains\\cifs_server", "path_linux": "/mnt/nfs" } :statuscode 200: no error """ pathmap = PathMap.query.filter_by(id=pathmap_id).first() if not pathmap: return jsonify(error="No pathmap with that id"), NOT_FOUND out = pathmap.to_dict(unpack_relationships=False) if pathmap.tag: out["tag"] = pathmap.tag.tag del out["tag_id"] return jsonify(out), OK
def get(self, agent_id): """ A ``GET`` to this endpoint will return a list of all software versions available on this agent. .. http:get:: /api/v1/agents/<str:agent_id>/software/ HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/agents/bbf55143-f2b1-4c15-9d41-139bd8057931/software/ HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "software": "Blender", "version": "2.72" } ] :statuscode 200: no error :statuscode 404: agent not found """ agent = Agent.query.filter_by(id=agent_id).first() if agent is None: return jsonify(error="Agent %r not found" % agent_id), NOT_FOUND out = [] for version in agent.software_versions: software_dict = {"software": version.software.software, "version": version.version} out.append(software_dict) return jsonify(out), OK
def get(self, jobtype_name): """ A ``GET`` to this endpoint will return a sorted list of of all known versions of the specified jobtype. .. http:get:: /api/v1/jobtypes/[<str:name>|<int:id>]/versions/ HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/jobtypes/TestJobType/versions/ HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [1, 2] :statuscode 200: no error :statuscode 404: jobtype not found """ if isinstance(jobtype_name, STRING_TYPES): jobtype = JobType.query.filter( JobType.name == jobtype_name).first() else: jobtype = JobType.query.filter(JobType.id == jobtype_name).first() if not jobtype: return jsonify(error="jobtype not found"), NOT_FOUND out = [x.version for x in jobtype.versions] return jsonify(sorted(out)), OK
def login_page(): """display and process the login for or action""" if request.method == "POST" and request.content_type == "application/json": user = User.get(request.json["username"]) if user and user.check_password(request.json["password"]): login_user(user, remember=True) return jsonify(None) return jsonify(None), UNAUTHORIZED form = LoginForm(request.form) if request.method == "POST" and form.validate(): login_user(form.dbuser, remember=True) return redirect(request.args.get("next") or "/") if request.content_type == "application/json": abort(BAD_REQUEST) return render_template("pyfarm/login.html", form=form, next=request.args.get("next") or "/")
def get(self, jobtype_name): """ A ``GET`` to this endpoint will return a sorted list of of all known versions of the specified jobtype. .. http:get:: /api/v1/jobtypes/[<str:name>|<int:id>]/versions/ HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/jobtypes/TestJobType/versions/ HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [1, 2] :statuscode 200: no error :statuscode 404: jobtype not found """ if isinstance(jobtype_name, STRING_TYPES): jobtype = JobType.query.filter(JobType.name == jobtype_name).first() else: jobtype = JobType.query.filter(JobType.id == jobtype_name).first() if not jobtype: return jsonify(error="jobtype not found"), NOT_FOUND out = [x.version for x in jobtype.versions] return jsonify(sorted(out)), OK
def put(self, version): """ A ``PUT`` to this endpoint will upload a new version of pyfarm-agent to be used for agent auto-updates. The update must be a zip file. .. http:put:: /api/v1/agents/updates/<string:version> HTTP/1.1 **Request** .. sourcecode:: http PUT /api/v1/agents/updates/1.2.3 HTTP/1.1 Content-Type: application/zip <binary data> **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json :statuscode 201: The update was put in place :statuscode 400: there was something wrong with the request (such as an invalid version number specified or the mime type not being application/zip) """ if request.mimetype != "application/zip": return (jsonify(error="Data for agent updates must be " "application/zip"), BAD_REQUEST) if not VERSION_REGEX.match(version): return (jsonify(error="Version is not an acceptable version number"), BAD_REQUEST) path = join(UPDATES_DIR, "pyfarm-agent-%s.zip" % version) with open(path, "wb+") as zip_file: zip_file.write(request.data) return "", CREATED
def delete(self, group_id): """ A ``DELETE`` to this endpoint will delete the specified job group .. http:delete:: /api/v1/jobgroup/<int:id> **Request** .. sourcecode:: http DELETE /api/v1/jobgroups/1 HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 204 NO_CONTENT :statuscode 204: the job group was deleted or didn't exist :statuscode 409: the job group cannot be deleted because it still contains jobs """ jobgroup = JobGroup.query.filter_by(id=group_id).first() if not jobgroup: return jsonify(), NO_CONTENT num_jobs = Job.query.filter_by(group=jobgroup).count() if num_jobs > 0: return (jsonify(error="Cannot delete: job group has jobs assigned"), CONFLICT) db.session.delete(jobgroup) db.session.commit() logger.info("Deleted job group %s", jobgroup.title) return jsonify(), NO_CONTENT
def get(self): """ A ``GET`` to this endpoint will return a list of known job queues. .. http:get:: /api/v1/jobqueues/ HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/jobqueues/ HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "priority": 5, "weight": 10, "parent_jobqueue_id": null, "name": "Test Queue", "minimum_agents": null, "id": 1, "maximum_agents": null }, { "priority": 5, "weight": 10, "parent_jobqueue_id": null, "name": "Test Queue 2", "minimum_agents": null, "id": 2, "maximum_agents": null } ] :statuscode 200: no error """ out = [] for jobqueue in JobQueue.query: out.append(jobqueue.to_dict(unpack_relationships=False)) return jsonify(out), OK
def get(self): """ A ``GET`` to this endpoint will return a list of known tags, with id. Associated agents and jobs are included for every tag :rtype : object .. http:get:: /api/v1/tags/ HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/tags/ HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "agents": [ 1 ], "jobs": [], "id": 1, "tag": "interesting" }, { "agents": [], "jobs": [], "id": 2, "tag": "boring" } ] :statuscode 200: no error """ out = [] for tag in Tag.query.all(): out.append(tag.to_dict(unpack_relationships=("agents", "jobs"))) return jsonify(out), OK
def get(self): """ A ``GET`` to this endpoint will return a list of known job groups. .. http:get:: /api/v1/jobgroups/ HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/jobgroups/ HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "id": 2, "user": "******", "main_jobtype": "Test JobType", "title": "Test Group" } ] :statuscode 200: no error """ out = [] for jobgroup in JobGroup.query: jobgroup_data = jobgroup.to_dict( unpack_relationships=["user", "main_jobtype"]) jobgroup_data.pop("user_id", None) jobgroup_data.pop("main_jobtype_id", None) out.append(jobgroup_data) return jsonify(out), OK
def schema(): """ Returns the basic schema of :class:`.JobGroup` .. http:get:: /api/v1/jobgroups/schema HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/jobgroups/schema HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "main_jobtype": "VARCHAR(64)", "title": "VARCHAR(255)", "user": "******", "id": "INTEGER" } :statuscode 200: no error """ schema_dict = JobGroup.to_schema() # In the database, we are storing the user by id, but over the wire, we are # using the username to identify the user instead. schema_dict["user"] = "******" % config.get("max_username_length") del schema_dict["user_id"] schema_dict["main_jobtype"] = \ "VARCHAR(%s)" % config.get("job_type_max_name_length") del schema_dict["main_jobtype_id"] return jsonify(schema_dict), OK
def schema(): """ Returns the basic schema of :class:`.JobType` .. http:get:: /api/v1/jobtypes/schema HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/jobtypes/schema HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "batch_contiguous": "BOOLEAN", "classname": "VARCHAR(64)", "code": "TEXT", "description": "TEXT", "id": "INTEGER", "version": "INTEGER", "max_batch": "INTEGER", "no_automatic_start_time": "INTEGER", "name": "VARCHAR(64)" } :statuscode 200: no error """ schema_dict = JobTypeVersion.to_schema() schema_dict.update(JobType.to_schema()) return jsonify(schema_dict), OK
def schema(): """ Returns the basic schema of :class:`.Agent` .. http:get:: /api/v1/agents/schema HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/agents/schema HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "ram": "INTEGER", "free_ram": "INTEGER", "time_offset": "INTEGER", "use_address": "INTEGER", "hostname": "VARCHAR(255)", "cpus": "INTEGER", "port": "INTEGER", "state": "INTEGER", "ram_allocation": "FLOAT", "cpu_allocation": "FLOAT", "id": "UUIDType", "remote_ip": "IPv4Address" } :statuscode 200: no error """ return jsonify(Agent.to_schema())
def get(self): """ A ``GET`` to this endpoint will return a list of registered jobtypes. .. http:get:: /api/v1/jobtypes/ HTTP/1.1 **Request** .. sourcecode:: http GET /api/v1/jobtypes/ HTTP/1.1 Accept: application/json **Response** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "id": 1, "name": "TestJobType" } ] :statuscode 200: no error """ out = [] q = db.session.query(JobType.id, JobType.name) for id, name in q: out.append({"id": id, "name": name}) return jsonify(out), OK
def put(self, job_id, task_id, attempt, log_identifier): """ A ``PUT`` to this endpoint will upload the request's body as the specified logfile .. http:put:: /api/v1/jobs/<job_id>/tasks/<task_id>/attempts/<attempt>/logs/<log_identifier>/logfile HTTP/1.1 **Request** .. sourcecode:: http PUT /api/v1/jobs/4/tasks/1300/attempts/5/logs/2014-09-03_10-58-59_4_4ee02475335911e4a935c86000cbf5fb.csv/logfile HTTP/1.1 <content of the logfile> **Response** .. sourcecode:: http HTTP/1.1 201 CREATED :statuscode 201: lofile was uploaded :statuscode 400: the specified logfile identifier is not acceptable :statuscode 404: task or logfile not found """ task = Task.query.filter_by(id=task_id, job_id=job_id).first() if not task: return jsonify(task_id=task_id, log=log_identifier, error="Specified task not found"), NOT_FOUND log = TaskLog.query.filter_by(identifier=log_identifier).first() if not log: return jsonify(task_id=task_id, log=log_identifier, error="Specified log not found"), NOT_FOUND association = TaskTaskLogAssociation.query.filter_by( task=task, log=log, attempt=attempt).first() if not association: return jsonify(task_id=task_id, log=log.identifier, error="Specified log not found in task"), NOT_FOUND path = realpath(join(LOGFILES_DIR, log_identifier)) if not realpath(path).startswith(LOGFILES_DIR): return jsonify(error="Identifier is not acceptable"), BAD_REQUEST logger.info("Writing task log file for task %s, attempt %s to path %s", task_id, attempt, path) try: with open(path, "wb+") as log_file: log_file.write(request.data) except (IOError, OSError) as e: logger.error("Could not write task log file: %s (%s)", e.errno, e.strerror) return (jsonify(error="Could not write file %s to disk: %s" % (path, e)), INTERNAL_SERVER_ERROR) return "", CREATED