Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 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
20 changes: 20 additions & 0 deletions azext_iot/digitaltwins/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,16 @@ def load_digitaltwins_help():
az dt twin delete -n {instance_or_hostname} --twin-id {twin_id} --etag {etag}
"""

helps["dt twin delete-all"] = """
type: command
short-summary: Deletes all digital twins within the specified resource, including all relationships for those twins.
Comment thread
vilit1 marked this conversation as resolved.
Outdated

examples:
- name: Delete all digital twins. Any relationships referencing the twins will also be deleted.
text: >
az dt twin delete-all -n {instance_or_hostname}
"""

helps["dt twin relationship"] = """
type: group
short-summary: Manage and configure the digital twin relationships of a Digital Twins instance.
Expand Down Expand Up @@ -645,6 +655,16 @@ def load_digitaltwins_help():
az dt twin relationship delete -n {instance_or_hostname} --twin-id {twin_id} --relationship-id {relationship_id} --etag {etag}
"""

helps["dt twin relationship delete-all"] = """
type: command
short-summary: Deletes all digital twin relationships on a Digital Twins instance, including incomming relationships.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small typo for incoming.


examples:
- name: Delete all digital twin relationships associated with the twin.
text: >
az dt twin relationship delete-all -n {instance_or_hostname} --twin-id {twin_id}
Comment thread
vilit1 marked this conversation as resolved.
"""

helps["dt twin telemetry"] = """
type: group
short-summary: Test and validate the event routes and endpoints of a Digital Twins instance.
Expand Down
2 changes: 2 additions & 0 deletions azext_iot/digitaltwins/command_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def load_digitaltwins_commands(self, _):
cmd_group.show_command("show", "show_twin")
cmd_group.command("update", "update_twin")
cmd_group.command("delete", "delete_twin")
cmd_group.command("delete-all", "delete_all_twin", confirmation=True, supports_no_wait=True)
Comment thread
vilit1 marked this conversation as resolved.
Outdated

with self.command_group(
"dt twin component", command_type=digitaltwins_twin_ops
Expand All @@ -115,6 +116,7 @@ def load_digitaltwins_commands(self, _):
cmd_group.command("list", "list_relationships")
cmd_group.command("update", "update_relationship")
cmd_group.command("delete", "delete_relationship")
cmd_group.command("delete-all", "delete_all_relationship", confirmation=True, supports_no_wait=True)
Comment thread
vilit1 marked this conversation as resolved.
Outdated

with self.command_group(
"dt twin telemetry", command_type=digitaltwins_twin_ops
Expand Down
14 changes: 13 additions & 1 deletion azext_iot/digitaltwins/commands_twins.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ def update_twin(cmd, name_or_hostname, twin_id, json_patch, resource_group_name=

def delete_twin(cmd, name_or_hostname, twin_id, resource_group_name=None, etag=None):
twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name)
return twin_provider.delete(twin_id, etag=etag)
return twin_provider.delete(twin_id=twin_id, etag=etag)


def delete_all_twin(cmd, name_or_hostname, resource_group_name=None, etag=None):
Comment thread
vilit1 marked this conversation as resolved.
Outdated
twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name)
return twin_provider.delete_all(etag=etag)


def create_relationship(
Expand Down Expand Up @@ -118,6 +123,13 @@ def delete_relationship(
)


def delete_all_relationship(
cmd, name_or_hostname, twin_id, resource_group_name=None, etag=None
Comment thread
vilit1 marked this conversation as resolved.
Outdated
):
twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name)
return twin_provider.delete_all_relationship(twin_id=twin_id, etag=etag)


def send_telemetry(
cmd,
name_or_hostname,
Expand Down
48 changes: 47 additions & 1 deletion azext_iot/digitaltwins/providers/twin.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,29 @@ def update(self, twin_id, json_patch, etag=None):
raise CLIError(unpack_msrest_error(e))

def delete(self, twin_id, etag=None):
# Not a json response
try:
options = TwinOptions(if_match=(etag if etag else "*"))
self.twins_sdk.delete(id=twin_id, digital_twins_delete_options=options, raw=True)
except ErrorResponseException as e:
raise CLIError(unpack_msrest_error(e))

def delete_all(self, etag=None):
# need to get all twins
query = "select * from digitaltwins"
twins = self.invoke_query(query=query, show_cost=False)["result"]
print(f"Found {len(twins)} twin(s).")

# go through and delete all
for twin in twins:
try:
self.delete_all_relationship(
twin_id=twin["$dtId"],
etag=etag,
)
self.delete(twin_id=twin["$dtId"], etag=etag)
except CLIError as e:
logger.warn(f"Could not delete twin {twin['$dtId']}. The error is {e}")

def add_relationship(
self,
twin_id,
Expand Down Expand Up @@ -214,6 +230,36 @@ def delete_relationship(self, twin_id, relationship_id, etag=None):
except ErrorResponseException as e:
raise CLIError(unpack_msrest_error(e))

def delete_all_relationship(self, twin_id, etag=None):
relationships = self.list_relationships(twin_id, incoming_relationships=True)
incoming_pager = self.list_relationships(twin_id)

# relationships pager needs to be advanced to get relationships
try:
while True:
relationships.extend(incoming_pager.advance_page())
except StopIteration:
pass

print(f"Found {len(relationships)} relationship(s) associated with twin {twin_id}.")

for relationship in relationships:
try:
if type(relationship) == dict:
Comment thread
vilit1 marked this conversation as resolved.
Outdated
self.delete_relationship(
twin_id=twin_id,
relationship_id=relationship['$relationshipId'],
etag=etag
)
else:
Comment thread
vilit1 marked this conversation as resolved.
self.delete_relationship(
twin_id=relationship.source_id,
relationship_id=relationship.relationship_id,
etag=etag
)
except CLIError as e:
logger.warn(f"Could not delete relationship {relationship}. The error is {e}.")

def get_component(self, twin_id, component_path):
try:
return self.twins_sdk.get_component(
Expand Down
182 changes: 174 additions & 8 deletions azext_iot/tests/digitaltwins/test_dt_twin_lifecycle_int.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ def test_dt_twin(self):
).get_output_in_json()
assert len(twin_query_result["result"]) == 2

# Relationship Tests
relationship_id = "myedge"
relationship = "contains"
self.kwargs["relationshipJson"] = json.dumps(
Expand Down Expand Up @@ -500,16 +501,181 @@ def test_dt_twin(self):
)
)

for twin_tuple in twins_id_list:
# No output from API for delete twin
self.cmd(
"dt twin delete -n {} --twin-id {} {}".format(
instance_name,
twin_tuple[0],
"-g {}".format(self.rg) if twins_id_list[-1] == twin_tuple else "",
)
self.cmd(
"dt twin delete-all -n {} --yes".format(
instance_name,
)
)
sleep(10) # Wait for API to catch up

twin_query_result = self.cmd(
"dt twin query -n {} -g {} -q 'select * from digitaltwins' --cost".format(
instance_name, self.rg
)
).get_output_in_json()
assert len(twin_query_result["result"]) == 0
assert twin_query_result["cost"]

def test_dt_twin_delete(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - I think we should label these tests as bulk delete

self.wait_for_capacity()
instance_name = generate_resource_id()
models_directory = "./models"
floor_dtmi = "dtmi:com:example:Floor;1"
floor_twin_id = "myfloor"
room_dtmi = "dtmi:com:example:Room;1"
room_twin_id = "myroom"
thermostat_component_id = "Thermostat"

create_output = self.cmd(
"dt create -n {} -g {} -l {}".format(instance_name, self.rg, self.region)
).get_output_in_json()
self.track_instance(create_output)
self.wait_for_hostname(create_output)

self.cmd(
"dt role-assignment create -n {} -g {} --assignee {} --role '{}'".format(
instance_name, self.rg, self.current_user, self.role_map["owner"]
)
)
# Wait for RBAC to catch-up
sleep(60)

self.cmd(
"dt model create -n {} --from-directory '{}'".format(
instance_name, models_directory
)
)

self.kwargs["tempAndThermostatComponentJson"] = json.dumps(
{
"Temperature": 10.2,
"Thermostat": {
"$metadata": {},
"setPointTemp": 23.12,
},
}
)

floor_twin = self.cmd(
"dt twin create -n {} --dtmi {} --twin-id {}".format(
instance_name, floor_dtmi, floor_twin_id
)
).get_output_in_json()

assert_twin_attributes(
twin=floor_twin,
expected_twin_id=floor_twin_id,
expected_dtmi=floor_dtmi,
)

room_twin = self.cmd(
"dt twin create -n {} -g {} --dtmi {} --twin-id {} --properties '{}'".format(
instance_name,
self.rg,
room_dtmi,
room_twin_id,
"{tempAndThermostatComponentJson}",
)
).get_output_in_json()

assert_twin_attributes(
twin=room_twin,
expected_twin_id=room_twin_id,
expected_dtmi=room_dtmi,
properties=self.kwargs["tempAndThermostatComponentJson"],
component_name=thermostat_component_id,
)

twin_query_result = self.cmd(
"dt twin query -n {} -g {} -q 'select * from digitaltwins'".format(
instance_name, self.rg
)
).get_output_in_json()
assert len(twin_query_result["result"]) == 2

# Relationship Tests
relationship_id = "myedge"
relationship = "contains"
self.kwargs["relationshipJson"] = json.dumps(
{"ownershipUser": "me", "ownershipDepartment": "mydepartment"}
)

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

assert_twin_relationship_attributes(
twin_relationship_obj=twin_relationship_create_result,
expected_relationship=relationship,
relationship_id=relationship_id,
source_id=floor_twin_id,
target_id=room_twin_id,
properties=self.kwargs["relationshipJson"],
)

twin_relationship_list_result = self.cmd(
"dt twin relationship list -n {} --twin-id {}".format(
instance_name,
floor_twin_id,
)
).get_output_in_json()
assert len(twin_relationship_list_result) == 1

# Delete all relationships
self.cmd(
"dt twin relationship delete-all -n {} --twin-id {} --yes".format(
instance_name,
floor_twin_id,
)
)

twin_relationship_list_result = self.cmd(
"dt twin relationship list -n {} --twin-id {}".format(
instance_name,
floor_twin_id,
)
).get_output_in_json()
assert len(twin_relationship_list_result) == 0

# Recreate relationship for delete all twins
twin_relationship_create_result = self.cmd(
"dt twin relationship create -n {} -g {} --relationship-id {} --relationship {} --twin-id {} "
"--target-twin-id {} --properties '{}'".format(
instance_name,
self.rg,
relationship_id,
relationship,
floor_twin_id,
room_twin_id,
"{relationshipJson}",
)
).get_output_in_json()

assert_twin_relationship_attributes(
twin_relationship_obj=twin_relationship_create_result,
expected_relationship=relationship,
relationship_id=relationship_id,
source_id=floor_twin_id,
target_id=room_twin_id,
properties=self.kwargs["relationshipJson"],
)

self.cmd(
"dt twin delete-all -n {} --yes".format(
instance_name,
)
)
sleep(10) # Wait for API to catch up

twin_query_result = self.cmd(
"dt twin query -n {} -g {} -q 'select * from digitaltwins' --cost".format(
instance_name, self.rg
Expand Down
Loading