IoT: Added Rules implementation (#3552)
* feat(iot): Added IoT Rules implementation * fix(iot): Split IoT Rules tests into multiple test cases * fix(iot): Updated rule creation to work with python 2 Co-authored-by: Szymon Zmilczak <szymon.zmilczak@machiq.com>
This commit is contained in:
parent
d1696a37e5
commit
c8151e1bb4
@ -4406,7 +4406,7 @@
|
|||||||
- [X] create_thing
|
- [X] create_thing
|
||||||
- [X] create_thing_group
|
- [X] create_thing_group
|
||||||
- [X] create_thing_type
|
- [X] create_thing_type
|
||||||
- [ ] create_topic_rule
|
- [X] create_topic_rule
|
||||||
- [ ] create_topic_rule_destination
|
- [ ] create_topic_rule_destination
|
||||||
- [ ] delete_account_audit_configuration
|
- [ ] delete_account_audit_configuration
|
||||||
- [ ] delete_authorizer
|
- [ ] delete_authorizer
|
||||||
@ -4432,7 +4432,7 @@
|
|||||||
- [X] delete_thing
|
- [X] delete_thing
|
||||||
- [X] delete_thing_group
|
- [X] delete_thing_group
|
||||||
- [X] delete_thing_type
|
- [X] delete_thing_type
|
||||||
- [ ] delete_topic_rule
|
- [X] delete_topic_rule
|
||||||
- [ ] delete_topic_rule_destination
|
- [ ] delete_topic_rule_destination
|
||||||
- [ ] delete_v2_logging_level
|
- [ ] delete_v2_logging_level
|
||||||
- [ ] deprecate_thing_type
|
- [ ] deprecate_thing_type
|
||||||
@ -4467,8 +4467,8 @@
|
|||||||
- [X] detach_principal_policy
|
- [X] detach_principal_policy
|
||||||
- [ ] detach_security_profile
|
- [ ] detach_security_profile
|
||||||
- [X] detach_thing_principal
|
- [X] detach_thing_principal
|
||||||
- [ ] disable_topic_rule
|
- [X] disable_topic_rule
|
||||||
- [ ] enable_topic_rule
|
- [X] enable_topic_rule
|
||||||
- [ ] get_cardinality
|
- [ ] get_cardinality
|
||||||
- [ ] get_effective_policies
|
- [ ] get_effective_policies
|
||||||
- [ ] get_indexing_configuration
|
- [ ] get_indexing_configuration
|
||||||
@ -4480,7 +4480,7 @@
|
|||||||
- [X] get_policy_version
|
- [X] get_policy_version
|
||||||
- [ ] get_registration_code
|
- [ ] get_registration_code
|
||||||
- [ ] get_statistics
|
- [ ] get_statistics
|
||||||
- [ ] get_topic_rule
|
- [X] get_topic_rule
|
||||||
- [ ] get_topic_rule_destination
|
- [ ] get_topic_rule_destination
|
||||||
- [ ] get_v2_logging_options
|
- [ ] get_v2_logging_options
|
||||||
- [ ] list_active_violations
|
- [ ] list_active_violations
|
||||||
@ -4528,7 +4528,7 @@
|
|||||||
- [ ] list_things_in_billing_group
|
- [ ] list_things_in_billing_group
|
||||||
- [X] list_things_in_thing_group
|
- [X] list_things_in_thing_group
|
||||||
- [ ] list_topic_rule_destinations
|
- [ ] list_topic_rule_destinations
|
||||||
- [ ] list_topic_rules
|
- [X] list_topic_rules
|
||||||
- [ ] list_v2_logging_levels
|
- [ ] list_v2_logging_levels
|
||||||
- [ ] list_violation_events
|
- [ ] list_violation_events
|
||||||
- [ ] register_ca_certificate
|
- [ ] register_ca_certificate
|
||||||
@ -4538,7 +4538,7 @@
|
|||||||
- [ ] reject_certificate_transfer
|
- [ ] reject_certificate_transfer
|
||||||
- [ ] remove_thing_from_billing_group
|
- [ ] remove_thing_from_billing_group
|
||||||
- [X] remove_thing_from_thing_group
|
- [X] remove_thing_from_thing_group
|
||||||
- [ ] replace_topic_rule
|
- [X] replace_topic_rule
|
||||||
- [ ] search_index
|
- [ ] search_index
|
||||||
- [ ] set_default_authorizer
|
- [ ] set_default_authorizer
|
||||||
- [X] set_default_policy_version
|
- [X] set_default_policy_version
|
||||||
|
@ -425,6 +425,57 @@ class FakeEndpoint(BaseModel):
|
|||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class FakeRule(BaseModel):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
rule_name,
|
||||||
|
description,
|
||||||
|
created_at,
|
||||||
|
rule_disabled,
|
||||||
|
topic_pattern,
|
||||||
|
actions,
|
||||||
|
error_action,
|
||||||
|
sql,
|
||||||
|
aws_iot_sql_version,
|
||||||
|
region_name,
|
||||||
|
):
|
||||||
|
self.region_name = region_name
|
||||||
|
self.rule_name = rule_name
|
||||||
|
self.description = description or ""
|
||||||
|
self.created_at = created_at
|
||||||
|
self.rule_disabled = bool(rule_disabled)
|
||||||
|
self.topic_pattern = topic_pattern
|
||||||
|
self.actions = actions or []
|
||||||
|
self.error_action = error_action or {}
|
||||||
|
self.sql = sql
|
||||||
|
self.aws_iot_sql_version = aws_iot_sql_version or "2016-03-23"
|
||||||
|
self.arn = "arn:aws:iot:%s:1:rule/%s" % (self.region_name, rule_name)
|
||||||
|
|
||||||
|
def to_get_dict(self):
|
||||||
|
return {
|
||||||
|
"rule": {
|
||||||
|
"actions": self.actions,
|
||||||
|
"awsIotSqlVersion": self.aws_iot_sql_version,
|
||||||
|
"createdAt": self.created_at,
|
||||||
|
"description": self.description,
|
||||||
|
"errorAction": self.error_action,
|
||||||
|
"ruleDisabled": self.rule_disabled,
|
||||||
|
"ruleName": self.rule_name,
|
||||||
|
"sql": self.sql,
|
||||||
|
},
|
||||||
|
"ruleArn": self.arn,
|
||||||
|
}
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"ruleName": self.rule_name,
|
||||||
|
"createdAt": self.created_at,
|
||||||
|
"ruleArn": self.arn,
|
||||||
|
"ruleDisabled": self.rule_disabled,
|
||||||
|
"topicPattern": self.topic_pattern,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class IoTBackend(BaseBackend):
|
class IoTBackend(BaseBackend):
|
||||||
def __init__(self, region_name=None):
|
def __init__(self, region_name=None):
|
||||||
super(IoTBackend, self).__init__()
|
super(IoTBackend, self).__init__()
|
||||||
@ -438,6 +489,7 @@ class IoTBackend(BaseBackend):
|
|||||||
self.policies = OrderedDict()
|
self.policies = OrderedDict()
|
||||||
self.principal_policies = OrderedDict()
|
self.principal_policies = OrderedDict()
|
||||||
self.principal_things = OrderedDict()
|
self.principal_things = OrderedDict()
|
||||||
|
self.rules = OrderedDict()
|
||||||
self.endpoint = None
|
self.endpoint = None
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
@ -1275,6 +1327,47 @@ class IoTBackend(BaseBackend):
|
|||||||
|
|
||||||
return job_executions, next_token
|
return job_executions, next_token
|
||||||
|
|
||||||
|
def list_topic_rules(self):
|
||||||
|
return [r.to_dict() for r in self.rules.values()]
|
||||||
|
|
||||||
|
def get_topic_rule(self, rule_name):
|
||||||
|
if rule_name not in self.rules:
|
||||||
|
raise ResourceNotFoundException()
|
||||||
|
return self.rules[rule_name].to_get_dict()
|
||||||
|
|
||||||
|
def create_topic_rule(self, rule_name, sql, **kwargs):
|
||||||
|
if rule_name in self.rules:
|
||||||
|
raise ResourceAlreadyExistsException("Rule with given name already exists")
|
||||||
|
result = re.search(r"FROM\s+([^\s]*)", sql)
|
||||||
|
topic = result.group(1).strip("'") if result else None
|
||||||
|
self.rules[rule_name] = FakeRule(
|
||||||
|
rule_name=rule_name,
|
||||||
|
created_at=int(time.time()),
|
||||||
|
topic_pattern=topic,
|
||||||
|
sql=sql,
|
||||||
|
region_name=self.region_name,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def replace_topic_rule(self, rule_name, **kwargs):
|
||||||
|
self.delete_topic_rule(rule_name)
|
||||||
|
self.create_topic_rule(rule_name, **kwargs)
|
||||||
|
|
||||||
|
def delete_topic_rule(self, rule_name):
|
||||||
|
if rule_name not in self.rules:
|
||||||
|
raise ResourceNotFoundException()
|
||||||
|
del self.rules[rule_name]
|
||||||
|
|
||||||
|
def enable_topic_rule(self, rule_name):
|
||||||
|
if rule_name not in self.rules:
|
||||||
|
raise ResourceNotFoundException()
|
||||||
|
self.rules[rule_name].rule_disabled = False
|
||||||
|
|
||||||
|
def disable_topic_rule(self, rule_name):
|
||||||
|
if rule_name not in self.rules:
|
||||||
|
raise ResourceNotFoundException()
|
||||||
|
self.rules[rule_name].rule_disabled = True
|
||||||
|
|
||||||
|
|
||||||
iot_backends = {}
|
iot_backends = {}
|
||||||
for region in Session().get_available_regions("iot"):
|
for region in Session().get_available_regions("iot"):
|
||||||
|
@ -635,3 +635,47 @@ class IoTResponse(BaseResponse):
|
|||||||
thing_groups_to_remove=thing_groups_to_remove,
|
thing_groups_to_remove=thing_groups_to_remove,
|
||||||
)
|
)
|
||||||
return json.dumps(dict())
|
return json.dumps(dict())
|
||||||
|
|
||||||
|
def list_topic_rules(self):
|
||||||
|
return json.dumps(dict(rules=self.iot_backend.list_topic_rules()))
|
||||||
|
|
||||||
|
def get_topic_rule(self):
|
||||||
|
return json.dumps(
|
||||||
|
self.iot_backend.get_topic_rule(rule_name=self._get_param("ruleName"))
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_topic_rule(self):
|
||||||
|
self.iot_backend.create_topic_rule(
|
||||||
|
rule_name=self._get_param("ruleName"),
|
||||||
|
description=self._get_param("description"),
|
||||||
|
rule_disabled=self._get_param("ruleDisabled"),
|
||||||
|
actions=self._get_param("actions"),
|
||||||
|
error_action=self._get_param("errorAction"),
|
||||||
|
sql=self._get_param("sql"),
|
||||||
|
aws_iot_sql_version=self._get_param("awsIotSqlVersion"),
|
||||||
|
)
|
||||||
|
return json.dumps(dict())
|
||||||
|
|
||||||
|
def replace_topic_rule(self):
|
||||||
|
self.iot_backend.replace_topic_rule(
|
||||||
|
rule_name=self._get_param("ruleName"),
|
||||||
|
description=self._get_param("description"),
|
||||||
|
rule_disabled=self._get_param("ruleDisabled"),
|
||||||
|
actions=self._get_param("actions"),
|
||||||
|
error_action=self._get_param("errorAction"),
|
||||||
|
sql=self._get_param("sql"),
|
||||||
|
aws_iot_sql_version=self._get_param("awsIotSqlVersion"),
|
||||||
|
)
|
||||||
|
return json.dumps(dict())
|
||||||
|
|
||||||
|
def delete_topic_rule(self):
|
||||||
|
self.iot_backend.delete_topic_rule(rule_name=self._get_param("ruleName"))
|
||||||
|
return json.dumps(dict())
|
||||||
|
|
||||||
|
def enable_topic_rule(self):
|
||||||
|
self.iot_backend.enable_topic_rule(rule_name=self._get_param("ruleName"))
|
||||||
|
return json.dumps(dict())
|
||||||
|
|
||||||
|
def disable_topic_rule(self):
|
||||||
|
self.iot_backend.disable_topic_rule(rule_name=self._get_param("ruleName"))
|
||||||
|
return json.dumps(dict())
|
||||||
|
@ -1991,3 +1991,166 @@ def test_list_job_executions_for_thing():
|
|||||||
job_execution["executionSummaries"][0].should.have.key("jobId").which.should.equal(
|
job_execution["executionSummaries"][0].should.have.key("jobId").which.should.equal(
|
||||||
job_id
|
job_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTopicRules:
|
||||||
|
name = "my-rule"
|
||||||
|
payload = {
|
||||||
|
"sql": "SELECT * FROM 'topic/*' WHERE something > 0",
|
||||||
|
"actions": [
|
||||||
|
{"dynamoDBv2": {"putItem": {"tableName": "my-table"}, "roleArn": "my-role"}}
|
||||||
|
],
|
||||||
|
"errorAction": {
|
||||||
|
"republish": {"qos": 0, "roleArn": "my-role", "topic": "other-topic"}
|
||||||
|
},
|
||||||
|
"description": "my-description",
|
||||||
|
"ruleDisabled": False,
|
||||||
|
"awsIotSqlVersion": "2016-03-23",
|
||||||
|
}
|
||||||
|
|
||||||
|
@mock_iot
|
||||||
|
def test_topic_rule_create(self):
|
||||||
|
client = boto3.client("iot", region_name="ap-northeast-1")
|
||||||
|
|
||||||
|
client.create_topic_rule(ruleName=self.name, topicRulePayload=self.payload)
|
||||||
|
|
||||||
|
# duplicated rule name
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
client.create_topic_rule(ruleName=self.name, topicRulePayload=self.payload)
|
||||||
|
error_code = ex.value.response["Error"]["Code"]
|
||||||
|
error_code.should.equal("ResourceAlreadyExistsException")
|
||||||
|
|
||||||
|
@mock_iot
|
||||||
|
def test_topic_rule_list(self):
|
||||||
|
client = boto3.client("iot", region_name="ap-northeast-1")
|
||||||
|
|
||||||
|
# empty response
|
||||||
|
res = client.list_topic_rules()
|
||||||
|
res.should.have.key("rules").which.should.have.length_of(0)
|
||||||
|
|
||||||
|
client.create_topic_rule(ruleName=self.name, topicRulePayload=self.payload)
|
||||||
|
client.create_topic_rule(ruleName="my-rule-2", topicRulePayload=self.payload)
|
||||||
|
|
||||||
|
res = client.list_topic_rules()
|
||||||
|
res.should.have.key("rules").which.should.have.length_of(2)
|
||||||
|
for rule, name in zip(res["rules"], [self.name, "my-rule-2"]):
|
||||||
|
rule.should.have.key("ruleName").which.should.equal(name)
|
||||||
|
rule.should.have.key("createdAt").which.should_not.be.none
|
||||||
|
rule.should.have.key("ruleArn").which.should_not.be.none
|
||||||
|
rule.should.have.key("ruleDisabled").which.should.equal(
|
||||||
|
self.payload["ruleDisabled"]
|
||||||
|
)
|
||||||
|
rule.should.have.key("topicPattern").which.should.equal("topic/*")
|
||||||
|
|
||||||
|
@mock_iot
|
||||||
|
def test_topic_rule_get(self):
|
||||||
|
client = boto3.client("iot", region_name="ap-northeast-1")
|
||||||
|
|
||||||
|
# no such rule
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
client.get_topic_rule(ruleName=self.name)
|
||||||
|
error_code = ex.value.response["Error"]["Code"]
|
||||||
|
error_code.should.equal("ResourceNotFoundException")
|
||||||
|
|
||||||
|
client.create_topic_rule(ruleName=self.name, topicRulePayload=self.payload)
|
||||||
|
|
||||||
|
rule = client.get_topic_rule(ruleName=self.name)
|
||||||
|
|
||||||
|
rule.should.have.key("ruleArn").which.should_not.be.none
|
||||||
|
rule.should.have.key("rule")
|
||||||
|
rrule = rule["rule"]
|
||||||
|
rrule.should.have.key("actions").which.should.equal(self.payload["actions"])
|
||||||
|
rrule.should.have.key("awsIotSqlVersion").which.should.equal(
|
||||||
|
self.payload["awsIotSqlVersion"]
|
||||||
|
)
|
||||||
|
rrule.should.have.key("createdAt").which.should_not.be.none
|
||||||
|
rrule.should.have.key("description").which.should.equal(
|
||||||
|
self.payload["description"]
|
||||||
|
)
|
||||||
|
rrule.should.have.key("errorAction").which.should.equal(
|
||||||
|
self.payload["errorAction"]
|
||||||
|
)
|
||||||
|
rrule.should.have.key("ruleDisabled").which.should.equal(
|
||||||
|
self.payload["ruleDisabled"]
|
||||||
|
)
|
||||||
|
rrule.should.have.key("ruleName").which.should.equal(self.name)
|
||||||
|
rrule.should.have.key("sql").which.should.equal(self.payload["sql"])
|
||||||
|
|
||||||
|
@mock_iot
|
||||||
|
def test_topic_rule_replace(self):
|
||||||
|
client = boto3.client("iot", region_name="ap-northeast-1")
|
||||||
|
|
||||||
|
# no such rule
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
client.replace_topic_rule(ruleName=self.name, topicRulePayload=self.payload)
|
||||||
|
error_code = ex.value.response["Error"]["Code"]
|
||||||
|
error_code.should.equal("ResourceNotFoundException")
|
||||||
|
|
||||||
|
client.create_topic_rule(ruleName=self.name, topicRulePayload=self.payload)
|
||||||
|
|
||||||
|
payload = self.payload.copy()
|
||||||
|
payload["description"] = "new-description"
|
||||||
|
client.replace_topic_rule(
|
||||||
|
ruleName=self.name, topicRulePayload=payload,
|
||||||
|
)
|
||||||
|
|
||||||
|
rule = client.get_topic_rule(ruleName=self.name)
|
||||||
|
rule["rule"]["ruleName"].should.equal(self.name)
|
||||||
|
rule["rule"]["description"].should.equal(payload["description"])
|
||||||
|
|
||||||
|
@mock_iot
|
||||||
|
def test_topic_rule_disable(self):
|
||||||
|
client = boto3.client("iot", region_name="ap-northeast-1")
|
||||||
|
|
||||||
|
# no such rule
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
client.disable_topic_rule(ruleName=self.name)
|
||||||
|
error_code = ex.value.response["Error"]["Code"]
|
||||||
|
error_code.should.equal("ResourceNotFoundException")
|
||||||
|
|
||||||
|
client.create_topic_rule(ruleName=self.name, topicRulePayload=self.payload)
|
||||||
|
|
||||||
|
client.disable_topic_rule(ruleName=self.name)
|
||||||
|
|
||||||
|
rule = client.get_topic_rule(ruleName=self.name)
|
||||||
|
rule["rule"]["ruleName"].should.equal(self.name)
|
||||||
|
rule["rule"]["ruleDisabled"].should.equal(True)
|
||||||
|
|
||||||
|
@mock_iot
|
||||||
|
def test_topic_rule_enable(self):
|
||||||
|
client = boto3.client("iot", region_name="ap-northeast-1")
|
||||||
|
|
||||||
|
# no such rule
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
client.enable_topic_rule(ruleName=self.name)
|
||||||
|
error_code = ex.value.response["Error"]["Code"]
|
||||||
|
error_code.should.equal("ResourceNotFoundException")
|
||||||
|
|
||||||
|
payload = self.payload.copy()
|
||||||
|
payload["ruleDisabled"] = True
|
||||||
|
client.create_topic_rule(ruleName=self.name, topicRulePayload=payload)
|
||||||
|
|
||||||
|
client.enable_topic_rule(ruleName=self.name)
|
||||||
|
|
||||||
|
rule = client.get_topic_rule(ruleName=self.name)
|
||||||
|
rule["rule"]["ruleName"].should.equal(self.name)
|
||||||
|
rule["rule"]["ruleDisabled"].should.equal(False)
|
||||||
|
|
||||||
|
@mock_iot
|
||||||
|
def test_topic_rule_delete(self):
|
||||||
|
client = boto3.client("iot", region_name="ap-northeast-1")
|
||||||
|
|
||||||
|
# no such rule
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
client.delete_topic_rule(ruleName=self.name)
|
||||||
|
error_code = ex.value.response["Error"]["Code"]
|
||||||
|
error_code.should.equal("ResourceNotFoundException")
|
||||||
|
|
||||||
|
client.create_topic_rule(ruleName=self.name, topicRulePayload=self.payload)
|
||||||
|
|
||||||
|
client.enable_topic_rule(ruleName=self.name)
|
||||||
|
|
||||||
|
client.delete_topic_rule(ruleName=self.name)
|
||||||
|
|
||||||
|
res = client.list_topic_rules()
|
||||||
|
res.should.have.key("rules").which.should.have.length_of(0)
|
||||||
|
Loading…
Reference in New Issue
Block a user