|
| 1 | +# /* |
| 2 | +# * Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| 3 | +# * |
| 4 | +# * Licensed under the Apache License, Version 2.0 (the "License"). |
| 5 | +# * You may not use this file except in compliance with the License. |
| 6 | +# * A copy of the License is located at |
| 7 | +# * |
| 8 | +# * http://aws.amazon.com/apache2.0 |
| 9 | +# * |
| 10 | +# * or in the "license" file accompanying this file. This file is distributed |
| 11 | +# * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either |
| 12 | +# * express or implied. See the License for the specific language governing |
| 13 | +# * permissions and limitations under the License. |
| 14 | +# */ |
| 15 | + |
| 16 | +import json |
| 17 | + |
| 18 | +_BASE_THINGS_TOPIC = "$aws/things/" |
| 19 | +_NOTIFY_OPERATION = "notify" |
| 20 | +_NOTIFY_NEXT_OPERATION = "notify-next" |
| 21 | +_GET_OPERATION = "get" |
| 22 | +_START_NEXT_OPERATION = "start-next" |
| 23 | +_WILDCARD_OPERATION = "+" |
| 24 | +_UPDATE_OPERATION = "update" |
| 25 | +_ACCEPTED_REPLY = "accepted" |
| 26 | +_REJECTED_REPLY = "rejected" |
| 27 | +_WILDCARD_REPLY = "#" |
| 28 | + |
| 29 | +#Members of this enum are tuples |
| 30 | +_JOB_ID_REQUIRED_INDEX = 1 |
| 31 | +_JOB_OPERATION_INDEX = 2 |
| 32 | + |
| 33 | +_STATUS_KEY = 'status' |
| 34 | +_STATUS_DETAILS_KEY = 'statusDetails' |
| 35 | +_EXPECTED_VERSION_KEY = 'expectedVersion' |
| 36 | +_EXEXCUTION_NUMBER_KEY = 'executionNumber' |
| 37 | +_INCLUDE_JOB_EXECUTION_STATE_KEY = 'includeJobExecutionState' |
| 38 | +_INCLUDE_JOB_DOCUMENT_KEY = 'includeJobDocument' |
| 39 | +_CLIENT_TOKEN_KEY = 'clientToken' |
| 40 | + |
| 41 | +#The type of job topic. |
| 42 | +class jobExecutionTopicType(object): |
| 43 | + JOB_UNRECOGNIZED_TOPIC = (0, False, '') |
| 44 | + JOB_GET_PENDING_TOPIC = (1, False, _GET_OPERATION) |
| 45 | + JOB_START_NEXT_TOPIC = (2, False, _START_NEXT_OPERATION) |
| 46 | + JOB_DESCRIBE_TOPIC = (3, True, _GET_OPERATION) |
| 47 | + JOB_UPDATE_TOPIC = (4, True, _UPDATE_OPERATION) |
| 48 | + JOB_NOTIFY_TOPIC = (5, False, _NOTIFY_OPERATION) |
| 49 | + JOB_NOTIFY_NEXT_TOPIC = (6, False, _NOTIFY_NEXT_OPERATION) |
| 50 | + JOB_WILDCARD_TOPIC = (7, False, _WILDCARD_OPERATION) |
| 51 | + |
| 52 | +#Members of this enum are tuples |
| 53 | +_JOB_SUFFIX_INDEX = 1 |
| 54 | +#The type of reply topic, or #JOB_REQUEST_TYPE for topics that are not replies. |
| 55 | +class jobExecutionTopicReplyType(object): |
| 56 | + JOB_UNRECOGNIZED_TOPIC_TYPE = (0, '') |
| 57 | + JOB_REQUEST_TYPE = (1, '') |
| 58 | + JOB_ACCEPTED_REPLY_TYPE = (2, '/' + _ACCEPTED_REPLY) |
| 59 | + JOB_REJECTED_REPLY_TYPE = (3, '/' + _REJECTED_REPLY) |
| 60 | + JOB_WILDCARD_REPLY_TYPE = (4, '/' + _WILDCARD_REPLY) |
| 61 | + |
| 62 | +_JOB_STATUS_INDEX = 1 |
| 63 | +class jobExecutionStatus(object): |
| 64 | + JOB_EXECUTION_STATUS_NOT_SET = (0, None) |
| 65 | + JOB_EXECUTION_QUEUED = (1, 'QUEUED') |
| 66 | + JOB_EXECUTION_IN_PROGRESS = (2, 'IN_PROGRESS') |
| 67 | + JOB_EXECUTION_FAILED = (3, 'FAILED') |
| 68 | + JOB_EXECUTION_SUCCEEDED = (4, 'SUCCEEDED') |
| 69 | + JOB_EXECUTION_CANCELED = (5, 'CANCELED') |
| 70 | + JOB_EXECUTION_REJECTED = (6, 'REJECTED') |
| 71 | + JOB_EXECUTION_UNKNOWN_STATUS = (99, None) |
| 72 | + |
| 73 | +def _getExecutionStatus(jobStatus): |
| 74 | + try: |
| 75 | + return jobStatus[_JOB_STATUS_INDEX] |
| 76 | + except KeyError: |
| 77 | + return None |
| 78 | + |
| 79 | +def _isWithoutJobIdTopicType(srcJobExecTopicType): |
| 80 | + return (srcJobExecTopicType == jobExecutionTopicType.JOB_GET_PENDING_TOPIC or srcJobExecTopicType == jobExecutionTopicType.JOB_START_NEXT_TOPIC |
| 81 | + or srcJobExecTopicType == jobExecutionTopicType.JOB_NOTIFY_TOPIC or srcJobExecTopicType == jobExecutionTopicType.JOB_NOTIFY_NEXT_TOPIC) |
| 82 | + |
| 83 | +class thingJobManager: |
| 84 | + def __init__(self, thingName, clientToken = None): |
| 85 | + self._thingName = thingName |
| 86 | + self._clientToken = clientToken |
| 87 | + |
| 88 | + def getJobTopic(self, srcJobExecTopicType, srcJobExecTopicReplyType=jobExecutionTopicReplyType.JOB_REQUEST_TYPE, jobId=None): |
| 89 | + if self._thingName is None: |
| 90 | + return None |
| 91 | + |
| 92 | + #Verify topics that only support request type, actually have request type specified for reply |
| 93 | + if (srcJobExecTopicType == jobExecutionTopicType.JOB_NOTIFY_TOPIC or srcJobExecTopicType == jobExecutionTopicType.JOB_NOTIFY_NEXT_TOPIC) and srcJobExecTopicReplyType != jobExecutionTopicReplyType.JOB_REQUEST_TYPE: |
| 94 | + return None |
| 95 | + |
| 96 | + #Verify topics that explicitly do not want a job ID do not have one specified |
| 97 | + if (jobId is not None and _isWithoutJobIdTopicType(srcJobExecTopicType)): |
| 98 | + return None |
| 99 | + |
| 100 | + #Verify job ID is present if the topic requires one |
| 101 | + if jobId is None and srcJobExecTopicType[_JOB_ID_REQUIRED_INDEX]: |
| 102 | + return None |
| 103 | + |
| 104 | + #Ensure the job operation is a non-empty string |
| 105 | + if srcJobExecTopicType[_JOB_OPERATION_INDEX] == '': |
| 106 | + return None |
| 107 | + |
| 108 | + if srcJobExecTopicType[_JOB_ID_REQUIRED_INDEX]: |
| 109 | + return '{0}{1}/jobs/{2}/{3}{4}'.format(_BASE_THINGS_TOPIC, self._thingName, str(jobId), srcJobExecTopicType[_JOB_OPERATION_INDEX], srcJobExecTopicReplyType[_JOB_SUFFIX_INDEX]) |
| 110 | + elif srcJobExecTopicType == jobExecutionTopicType.JOB_WILDCARD_TOPIC: |
| 111 | + return '{0}{1}/jobs/#'.format(_BASE_THINGS_TOPIC, self._thingName) |
| 112 | + else: |
| 113 | + return '{0}{1}/jobs/{2}{3}'.format(_BASE_THINGS_TOPIC, self._thingName, srcJobExecTopicType[_JOB_OPERATION_INDEX], srcJobExecTopicReplyType[_JOB_SUFFIX_INDEX]) |
| 114 | + |
| 115 | + def serializeJobExecutionUpdatePayload(self, status, statusDetails=None, expectedVersion=0, executionNumber=0, includeJobExecutionState=False, includeJobDocument=False): |
| 116 | + executionStatus = _getExecutionStatus(status) |
| 117 | + if executionStatus is None: |
| 118 | + return None |
| 119 | + payload = {_STATUS_KEY: executionStatus} |
| 120 | + if statusDetails: |
| 121 | + payload[_STATUS_DETAILS_KEY] = statusDetails |
| 122 | + if expectedVersion > 0: |
| 123 | + payload[_EXPECTED_VERSION_KEY] = str(expectedVersion) |
| 124 | + if executionNumber > 0: |
| 125 | + payload[_EXEXCUTION_NUMBER_KEY] = str(executionNumber) |
| 126 | + if includeJobExecutionState: |
| 127 | + payload[_INCLUDE_JOB_EXECUTION_STATE_KEY] = True |
| 128 | + if includeJobDocument: |
| 129 | + payload[_INCLUDE_JOB_DOCUMENT_KEY] = True |
| 130 | + if self._clientToken is not None: |
| 131 | + payload[_CLIENT_TOKEN_KEY] = self._clientToken |
| 132 | + return json.dumps(payload) |
| 133 | + |
| 134 | + def serializeDescribeJobExecutionPayload(self, executionNumber=0, includeJobDocument=True): |
| 135 | + payload = {_INCLUDE_JOB_DOCUMENT_KEY: includeJobDocument} |
| 136 | + if executionNumber > 0: |
| 137 | + payload[_EXEXCUTION_NUMBER_KEY] = executionNumber |
| 138 | + if self._clientToken is not None: |
| 139 | + payload[_CLIENT_TOKEN_KEY] = self._clientToken |
| 140 | + return json.dumps(payload) |
| 141 | + |
| 142 | + def serializeStartNextPendingJobExecutionPayload(self, statusDetails=None): |
| 143 | + payload = {} |
| 144 | + if self._clientToken is not None: |
| 145 | + payload[_CLIENT_TOKEN_KEY] = self._clientToken |
| 146 | + if statusDetails is not None: |
| 147 | + payload[_STATUS_DETAILS_KEY] = statusDetails |
| 148 | + return json.dumps(payload) |
| 149 | + |
| 150 | + def serializeClientTokenPayload(self): |
| 151 | + return json.dumps({_CLIENT_TOKEN_KEY: self._clientToken}) if self._clientToken is not None else '{}' |
0 commit comments