Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions azext_iot/digitaltwins/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,11 @@ def load_digitaltwins_help():
az dt twin create -n {instance_or_hostname} --dtmi "dtmi:com:example:Room;1"
--twin-id {twin_id}

- name: Create a digital twin from an existing (prior-created) model with if-none-match tag.
text: >
az dt twin create -n {instance_or_hostname} --dtmi "dtmi:com:example:Room;1"
--twin-id {twin_id} --if-none-match

- name: Create a digital twin from an existing (prior-created) model. Instantiate with property values.
text: >
az dt twin create -n {instance_or_hostname} --dtmi "dtmi:com:example:DeviceInformation;1"
Expand Down Expand Up @@ -478,6 +483,11 @@ def load_digitaltwins_help():
az dt twin update -n {instance_or_hostname} --twin-id {twin_id}
--json-patch '{"op":"replace", "path":"/Temperature", "value": 20.5}'

- name: Update a digital twin via JSON patch specification and using etag.
text: >
az dt twin update -n {instance_or_hostname} --twin-id {twin_id} --etag {etag}
--json-patch '{"op":"replace", "path":"/Temperature", "value": 20.5}'

- name: Update a digital twin via JSON patch specification.
text: >
az dt twin update -n {instance_or_hostname} --twin-id {twin_id}
Expand Down Expand Up @@ -524,6 +534,10 @@ def load_digitaltwins_help():
- name: Remove a digital twin by Id.
text: >
az dt twin delete -n {instance_or_hostname} --twin-id {twin_id}

- name: Remove a digital twin by Id using the etag.
text: >
az dt twin delete -n {instance_or_hostname} --twin-id {twin_id} --etag {etag}
"""

helps["dt twin relationship"] = """
Expand All @@ -542,6 +556,11 @@ def load_digitaltwins_help():
az dt twin relationship create -n {instance_or_hostname} --relationship-id {relationship_id} --relationship contains
--twin-id {source_twin_id} --target {target_twin_id}

- name: Create a relationship between two digital twins with if-none-match tag
text: >
az dt twin relationship create -n {instance_or_hostname} --relationship-id {relationship_id} --relationship contains
--twin-id {source_twin_id} --target {target_twin_id} --if-none-match

- name: Create a relationship with initialized properties between two digital twins.
text: >
az dt twin relationship create -n {instance_or_hostname} --relationship-id {relationship_id} --relationship contains
Expand Down Expand Up @@ -593,6 +612,11 @@ def load_digitaltwins_help():
az dt twin relationship update -n {instance_or_hostname} --twin-id {twin_id} --relationship-id {relationship_id}
--relationship contains --json-patch '{"op":"replace", "path":"/Temperature", "value": 20.5}'

- name: Update a digital twin relationship via JSON patch specification and using etag.
text: >
az dt twin relationship update -n {instance_or_hostname} --twin-id {twin_id} --relationship-id {relationship_id}
--relationship contains --json-patch '{"op":"replace", "path":"/Temperature", "value": 20.5}' --etag {etag}

- name: Update a digital twin relationship via JSON patch specification.
text: >
az dt twin relationship update -n {instance_or_hostname} --twin-id {twin_id} --relationship-id {relationship_id}
Expand All @@ -615,6 +639,10 @@ def load_digitaltwins_help():
- name: Delete a digital twin relationship.
text: >
az dt twin relationship delete -n {instance_or_hostname} --twin-id {twin_id} --relationship-id {relationship_id}

- name: Delete a digital twin relationship using the etag.
text: >
az dt twin relationship delete -n {instance_or_hostname} --twin-id {twin_id} --relationship-id {relationship_id} --etag {etag}
"""

helps["dt twin telemetry"] = """
Expand Down
8 changes: 4 additions & 4 deletions azext_iot/digitaltwins/commands_twins.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ def create_twin(
name_or_hostname,
twin_id,
model_id,
replace=False,
if_none_match=False,
properties=None,
resource_group_name=None
):
twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name)
return twin_provider.create(
twin_id=twin_id, model_id=model_id, replace=replace, properties=properties
twin_id=twin_id, model_id=model_id, if_none_match=if_none_match, properties=properties
)


Expand All @@ -54,7 +54,7 @@ def create_relationship(
target_twin_id,
relationship_id,
relationship,
replace=False,
if_none_match=False,
properties=None,
resource_group_name=None,
):
Expand All @@ -64,7 +64,7 @@ def create_relationship(
target_twin_id=target_twin_id,
relationship_id=relationship_id,
relationship=relationship,
replace=replace,
if_none_match=if_none_match,
properties=properties,
)

Expand Down
9 changes: 5 additions & 4 deletions azext_iot/digitaltwins/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,17 +279,18 @@ def load_digitaltwins_arguments(self, _):
"Operations are limited to add, replace and remove. Provide file path or inline JSON.",
)
context.argument(
"etag", options_list=["--etag", "-e"], help="Entity tag value."
"etag", options_list=["--etag", "-e"], help="Entity tag value. The command will succeed if "
"the etag matches the current etag for the resource."
)
context.argument(
"component_path",
options_list=["--component"],
help="The path to the DTDL component.",
)
context.argument(
"replace",
options_list=["--replace"],
help="Indicates the operation should replace an existing twin if it exists."
"if_none_match",
options_list=["--if-none-match"],
help="Indicates the create operation should fail if an existing twin with the same id exists."
)

with self.argument_context("dt twin create") as context:
Expand Down
8 changes: 4 additions & 4 deletions azext_iot/digitaltwins/providers/twin.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def invoke_query(self, query, show_cost):

return query_result

def create(self, twin_id, model_id, replace=False, properties=None):
def create(self, twin_id, model_id, if_none_match=False, properties=None):
twin_request = {
"$dtId": twin_id,
"$metadata": {"$model": model_id},
Expand All @@ -72,7 +72,7 @@ def create(self, twin_id, model_id, replace=False, properties=None):
logger.info("Twin payload %s", json.dumps(twin_request))

try:
options = TwinOptions(if_none_match=(None if replace else "*"))
options = TwinOptions(if_none_match=("*" if if_none_match else None))
return self.twins_sdk.add(id=twin_id, twin=twin_request, digital_twins_add_options=options)
except ErrorResponseException as e:
raise CLIError(unpack_msrest_error(e))
Expand Down Expand Up @@ -117,7 +117,7 @@ def add_relationship(
target_twin_id,
relationship_id,
relationship,
replace=False,
if_none_match=False,
properties=None,
):
relationship_request = {
Expand All @@ -133,7 +133,7 @@ def add_relationship(

logger.info("Relationship payload %s", json.dumps(relationship_request))
try:
options = TwinOptions(if_none_match=(None if replace else "*"))
options = TwinOptions(if_none_match=("*" if if_none_match else None))
return self.twins_sdk.add_relationship(
id=twin_id,
relationship_id=relationship_id,
Expand Down
2 changes: 1 addition & 1 deletion azext_iot/operations/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -2223,7 +2223,7 @@ def http_wrap(target, device_id, generator):
max_runs=msg_count,
return_handle=True,
)
while True and op.is_alive():
while op.is_alive():
_handle_c2d_msg(target, device_id, receive_settle)
sleep(SIM_RECEIVE_SLEEP_SEC)

Expand Down
110 changes: 107 additions & 3 deletions azext_iot/tests/digitaltwins/test_dt_twin_lifecycle_int.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def test_dt_twin(self):
room_dtmi = "dtmi:com:example:Room;1"
room_twin_id = "myroom"
thermostat_component_id = "Thermostat"
etag = 'AAAA=='

create_output = self.cmd(
"dt create -n {} -g {} -l {}".format(instance_name, self.rg, self.region)
Expand Down Expand Up @@ -112,7 +113,7 @@ def test_dt_twin(self):
)

replaced_room_twin = self.cmd(
"dt twin create -n {} -g {} --dtmi {} --twin-id {} --replace --properties '{}'".format(
"dt twin create -n {} -g {} --dtmi {} --twin-id {} --properties '{}'".format(
instance_name,
self.rg,
room_dtmi,
Expand All @@ -129,9 +130,9 @@ def test_dt_twin(self):
component_name=thermostat_component_id,
)

# new twin cannot be created with same twin_id if replace not provided
# new twin cannot be created with same twin_id if if-none-match provided
self.cmd(
"dt twin create -n {} -g {} --dtmi {} --twin-id {} --properties '{}'".format(
"dt twin create -n {} -g {} --dtmi {} --twin-id {} --if-none-match --properties '{}'".format(
instance_name,
self.rg,
room_dtmi,
Expand All @@ -141,6 +142,17 @@ def test_dt_twin(self):
expect_failure=True
)

# delete command should fail if etag is different
self.cmd(
"dt twin delete -n {} -g {} --twin-id {} --etag '{}'".format(
instance_name,
self.rg,
room_twin_id,
etag
),
expect_failure=True
)

self.cmd(
"dt twin delete -n {} -g {} --twin-id {}".format(
instance_name,
Expand Down Expand Up @@ -241,6 +253,30 @@ def test_dt_twin(self):
== json.loads(self.kwargs["temperatureJsonPatch"])["value"]
)

self.cmd(
"dt twin update -n {} --twin-id {} --json-patch '{}' --etag '{}'".format(
instance_name,
room_twin_id,
"{temperatureJsonPatch}",
etag
),
expect_failure=True
)

update_twin_result = self.cmd(
"dt twin update -n {} --twin-id {} --json-patch '{}' --etag '{}'".format(
instance_name,
room_twin_id,
"{temperatureJsonPatch}",
update_twin_result["$etag"]
)
).get_output_in_json()

assert (
update_twin_result["Temperature"]
== json.loads(self.kwargs["temperatureJsonPatch"])["value"]
)

twin_query_result = self.cmd(
"dt twin query -n {} -g {} -q 'select * from digitaltwins'".format(
instance_name, self.rg
Expand All @@ -257,6 +293,18 @@ def test_dt_twin(self):
{"op": "replace", "path": "/ownershipUser", "value": "meme"}
)

twin_relationship_create_result = self.cmd(
"dt twin relationship create -n {} -g {} --relationship-id {} --relationship {} --twin-id {} "
"--target-twin-id {}".format(
instance_name,
self.rg,
relationship_id,
relationship,
floor_twin_id,
room_twin_id,
)
).get_output_in_json()

twin_relationship_create_result = self.cmd(
"dt twin relationship create -n {} -g {} --relationship-id {} --relationship {} --twin-id {} "
"--target-twin-id {} --properties '{}'".format(
Expand All @@ -279,6 +327,21 @@ def test_dt_twin(self):
properties=self.kwargs["relationshipJson"],
)

# new twin cannot be created with same twin_id if if-none-match provided
twin_relationship_create_result = self.cmd(
"dt twin relationship create -n {} -g {} --relationship-id {} --relationship {} --twin-id {} "
"--target-twin-id {} --if-none-match --properties '{}'".format(
instance_name,
self.rg,
relationship_id,
relationship,
floor_twin_id,
room_twin_id,
"{relationshipJson}",
),
expect_failure=True
)

twin_relationship_show_result = self.cmd(
"dt twin relationship show -n {} -g {} --twin-id {} --relationship-id {}".format(
instance_name,
Expand Down Expand Up @@ -313,6 +376,37 @@ def test_dt_twin(self):
== json.loads(self.kwargs["relationshipJsonPatch"])["value"]
)

# Fail to update if the etag if different
self.cmd(
"dt twin relationship update -n {} -g {} --relationship-id {} --twin-id {} "
"--json-patch '{}' --etag '{}'".format(
instance_name,
self.rg,
relationship_id,
floor_twin_id,
"{relationshipJsonPatch}",
etag
),
expect_failure=True
)

twin_edge_update_result = self.cmd(
"dt twin relationship update -n {} -g {} --relationship-id {} --twin-id {} "
"--json-patch '{}' --etag '{}'".format(
instance_name,
self.rg,
relationship_id,
floor_twin_id,
"{relationshipJsonPatch}",
twin_edge_update_result["$etag"]
)
).get_output_in_json()

assert (
twin_edge_update_result["ownershipUser"]
== json.loads(self.kwargs["relationshipJsonPatch"])["value"]
)

twin_relationship_list_result = self.cmd(
"dt twin relationship list -n {} --twin-id {}".format(
instance_name,
Expand Down Expand Up @@ -354,6 +448,16 @@ def test_dt_twin(self):
).get_output_in_json()
assert len(twin_relationship_list_result) == 1

self.cmd(
"dt twin relationship delete -n {} --twin-id {} -r {} --etag '{}'".format(
instance_name,
floor_twin_id,
relationship_id,
etag
),
expect_failure=True
)

# No output from API for delete edge
self.cmd(
"dt twin relationship delete -n {} --twin-id {} -r {}".format(
Expand Down
Loading