def test_rest_client_post(self, mock_call): """Test post""" auth = mock.create_autospec(Credentials) with self.assertRaises(RestCallException): rest_client.post(auth, "http://test", {}) mock_call.return_value.text = '{"key":"value"}' val = rest_client.post(auth, "http://test", {}) mock_call.assert_called_with(auth, 'POST', "http://test", headers={}, data=None) self.assertEqual(val, {"key":"value"}) val = rest_client.post(auth, "http://test", {}, message={"msg":"test"}) mock_call.assert_called_with(auth, 'POST', "http://test", headers={}, data='{"msg": "test"}') del mock_call.return_value.text with self.assertRaises(RestCallException): rest_client.post(auth, "http://test", {}) mock_call.side_effect = RestCallException(None, "Boom!", None) with self.assertRaises(RestCallException): rest_client.post(auth, "http://test", {})
def test_rest_client_post(self, mock_call): """Test post""" auth = mock.create_autospec(Credentials) with self.assertRaises(RestCallException): rest_client.post(auth, "http://test", {}) mock_call.return_value.text = '{"key":"value"}' val = rest_client.post(auth, "http://test", {}) mock_call.assert_called_with(auth, 'POST', "http://test", headers={}, data=None) self.assertEqual(val, {"key": "value"}) val = rest_client.post(auth, "http://test", {}, message={"msg": "test"}) mock_call.assert_called_with(auth, 'POST', "http://test", headers={}, data='{"msg": "test"}') del mock_call.return_value.text with self.assertRaises(RestCallException): rest_client.post(auth, "http://test", {}) mock_call.side_effect = RestCallException(None, "Boom!", None) with self.assertRaises(RestCallException): rest_client.post(auth, "http://test", {})
def cancel_task(self, job_id, task): """Cancel a running task of a job in progress. :Args: - job_id (str): The ID of the job whose task will be cancelled. - task (int, str): The ID of the task to be cancelled. :Returns: - A :class:`.Response` object with the POST response, however this is not required if the call is successful. The call will only be successful if the task can be and is cancelled. - If the task is not running (and therefore cannot be cancelled), the call will fail and the :class:`.RestCallException` will be returned in the :class:`.Response` object. - Any other communication failures will also return a :class:`.RestCallException`. """ self._log.debug("cancel_task, job_id={0}, task={1}".format(job_id, task)) url = self.url("jobs/{jobid}/tasks/{taskid}/actions/cancel") url = url.format(url, jobid=job_id, taskid=task) try: resp = rest_client.post(self._auth, url, self.headers) except RestCallException as exp: return Response(False, exp) else: return Response(True, resp)
def reprocess(self, job_id): """ Reprocesses any failed tasks in the job. This call will also re-activate a job if it has a 'Failed' status. :Args: - job_id (str): ID of the job to be reprocessed. :Returns: - A :class:`.Response` object containing a dictionary with the job ID of the reprocessed job and a URL to retrieve the job information (see :meth:`.BatchAppsApi.get_job()`). - If the call failed the response will hold the :class:`.RestCallException`. """ self._log.debug("reprocess, job_id={0}".format(job_id)) url = self.url("jobs/{jobid}/actions/reprocess").format(jobid=job_id) try: post_resp = rest_client.post(self._auth, url, self.headers) except RestCallException as exp: return Response(False, exp) else: return Response(True, post_resp)
def cancel(self, job_id): """Cancels a running job. :Args: - job_id (str): The ID of the job to be cancelled. :Returns: - A :class:`.Response` object with the POST response, however this is not required if the call is successful. The call will only be successful if the job can be and is cancelled. - If the job is not running (and therefore cannot be cancelled), the call will fail and the :class:`.RestCallException` will be returned in the :class:`.Response` object. - Any other communication failures will also return a :class:`.RestCallException`. """ self._log.debug("cancel, job_id={0}".format(job_id)) url = self.url("jobs/{jobid}/actions/cancel").format(jobid=job_id) try: post_resp = rest_client.post(self._auth, url, self.headers) except RestCallException as exp: return Response(False, exp) else: return Response(True, post_resp)
def send_job(self, job_message): """Submits a job. :Args: - job_message (dict): A job specification formatted as a dictionary. :Returns: - A :class:`.Response` object containing a dictionary of the newly submitted job's ID and URL if successful. Otherwise the Response will contain the :exc:`.RestCallException`. :Raises: - :class:`.RestCallException` if new job dictionary is malformed / missing necessary keys. """ self._log.debug("send_job, job_message={0}".format(job_message)) url = self.url("jobs") try: post_resp = rest_client.post(self._auth, url, self.headers, message=job_message) except RestCallException as exp: return Response(False, exp) else: if utils.valid_keys(post_resp, ["jobId", "link"]): return Response(True, post_resp) return Response(False, RestCallException(KeyError, "incorrectly formatted job response", post_resp))
def resize_pool(self, pool_id, target_size): """ Resize an existing pool. :Args: - pool_id (str): The ID of the pool to be resized. - target_size (int): The new size of the pool. :Returns: - :class:`.Response` with the POST response, however this is not required if the call was successful. - If the call failed a :class:`.Response` object with a :class:`.RestCallException`. """ self._log.debug("resize_pool, pool_id={0}, " "target_size={1}".format(pool_id, target_size)) url = self.url("pools/{poolid}/actions/resize") url = url.format(url, poolid=pool_id) message = {"targetDedicated": str(target_size)} try: resp = rest_client.post(self._auth, url, self.headers, message) except RestCallException as exp: return Response(False, exp) return Response(True, resp)
def add_pool(self, target_size=0, max_tasks=1, communication=False, certs=[]): """ Add a new pool. :Kwargs: - target_size (int): The target size of the pool. The default is 0. - max_tasks (int): Max tasks that can run on a single TVM. The default is 1. - communication (bool): Indicates whether tasks running on TVMs in the pool need to ba able to communicate directly with each other. The default is ``False``. - certs (list): A list of certificates that need to be installed on the TVMs of the pool. The maximum number of certs that can be installed on a pool is 10. :Returns: - A :class:`.Response` object a dict with the new pool id and a link to the newly created pool. ``{'id': '', 'link': ''}`` - If the call failed or if the response is incomplete/malformed a :class:`.Response` object with a :class:`.RestCallException`. """ self._log.debug("add_pool") url = self.url("pools") if len(certs) > 10: certs = certs[0:10] try: message = { "targetDedicated": str(int(target_size)), "maxTasksPerTVM": str(int(max_tasks)), "communication": bool(communication), "certificateReferences": list(certs), } except ValueError as exp: return Response(False, RestCallException(ValueError, str(exp), exp)) try: resp = rest_client.post(self._auth, url, self.headers, message) except RestCallException as exp: return Response(False, exp) if utils.valid_keys(resp, ["poolId", "link"]): return Response(True, resp) return Response(False, RestCallException(KeyError, "incorrectly formatted pool response", resp))
def query_missing_files(self, files): """ Checks whether user files are present in the cloud. As opposed to :meth:`.query_files()`, this call returns the files that are **not** present in the cloud. :Args: - files (dict, list): Either a file specification dictionary, or a list of file spec dictionaries. :Returns: - A :class:`.Response` object containing a list of the files that don't yet exist in the cloud. The files are represented as a dict with only a 'name' key. - If the call failed, a :class:`.Response` object containing a :class:`.RestCallException` is returned. """ # TODO: Check whether 'FileHash' is supported. self._log.debug("query_missing_files, files={0}".format(files)) url = self.url("files/query/missing") if type(files) == dict: files = [files] elif not (type(files) == list and len(files) >= 1 and type(files[0]) == dict): error = ( "File query can be done with single userfile " "spec dict, or list of userfile spec dicts. " "Not {t}".format(t=type(files)) ) return Response(False, RestCallException(TypeError, error, None)) message = {"Specifications": files} try: resp = rest_client.post(self._auth, url, self.headers, message) except RestCallException as exp: return Response(False, exp) if "files" not in resp or not isinstance(resp["files"], list): return Response(False, RestCallException(KeyError, "files key not in response message", resp)) return Response(True, resp["files"])
def query_files(self, files): """ Queries for user files matching specified criteria. This is used to detect whether user's files already exist in the cloud, and if they're up-to-date. Any number of files can be queried in a single call. :Args: - files (list, dict, str): The files to query. If this is in the form of a single filename, or list of filenames, the call will query for user files that match that filename. If this is in the form of a dict, or list of dicts, the call will query for a more specific match. Query dict should have the keys ``{'fileName', 'timestamp'}`` and optionally ``{'originalPath'}``. :Returns: - If the query was by filename, a :class:`.Response` containing a list of all the files (as dicts) with that name will be returned. - If the query was by specification, a :class:`.Response` containing a list of all the matching files (as dicts) will be returned. - If the call failed, a :class:`.Response` object containing a :class:`.RestCallException` will be returned. """ self._log.debug("query_files, files={0}".format(files)) url = self.url("files/query/{queryby}") operations = {str: "byname", dict: "byspecification"} optype = type(files) if optype == list and len(files) >= 1: optype = type(files[0]) elif optype == list and len(files) < 1: return Response(False, RestCallException(ValueError, "File list empty", ValueError("File list empty"))) else: files = [files] if optype not in operations: error = ( "File query can be done with single " "file name, list of names, or userfile " "spec dict. Not {t}".format(t=type(files)) ) return Response(False, RestCallException(TypeError, error, TypeError(error))) req_type = operations[optype] self._log.info("Querying files using {0}".format(req_type)) url = url.format(queryby=req_type) if req_type == "byspecification": message = {"Specifications": files} else: message = {"Names": files} self._log.debug("File query url={0}, message={1}".format(url, message)) try: resp = rest_client.post(self._auth, url, self.headers, message) except RestCallException as exp: return Response(False, exp) if "files" not in resp or not isinstance(resp["files"], list): return Response(False, RestCallException(KeyError, "files key not in response message", resp)) return Response(True, resp["files"])