Browse Source

Use PATCH operations for Azure tag updates

The previous tag-update functions called begin_create_or_update (HTTP
PUT, full-replace) with a partial body. Older Azure SDKs tolerated this
as a partial update, but azure-mgmt 38.x sends the body verbatim, so
PUTs with only {tags, location} were replacing resources with an empty
shell — wiping security rules from NSGs and clearing other properties
in NetworkSecurityGroup / VirtualNetwork / PublicIPAddress /
VirtualMachine / RouteTable / Image.

Switch each update_*_tags to the proper PATCH operation:
network_security_groups.update_tags + TagsObject for NSG/VNet/FIP/RT,
virtual_machines.begin_update + VirtualMachineUpdate for VMs, and
images.begin_update + ImageUpdate for images. Callers that previously
passed a full SDK model (so PUT preserved properties) now pass the
tags dict directly, matching the PATCH contract.
Nuwan Goonasekera 3 days ago
parent
commit
efafd17d37

+ 17 - 23
cloudbridge/providers/azure/azure_client.py

@@ -15,14 +15,14 @@ from azure.data.tables import TableServiceClient
 from azure.identity import ClientSecretCredential
 from azure.identity import ClientSecretCredential
 from azure.mgmt.compute import ComputeManagementClient
 from azure.mgmt.compute import ComputeManagementClient
 from azure.mgmt.compute.models import (CreationData, Disk, DiskUpdate, Image,
 from azure.mgmt.compute.models import (CreationData, Disk, DiskUpdate, Image,
-                                       Snapshot, SnapshotUpdate,
-                                       VirtualMachine)
+                                       ImageUpdate, Snapshot, SnapshotUpdate,
+                                       VirtualMachine, VirtualMachineUpdate)
 from azure.mgmt.devtestlabs.models import GalleryImageReference
 from azure.mgmt.devtestlabs.models import GalleryImageReference
 from azure.mgmt.network import NetworkManagementClient
 from azure.mgmt.network import NetworkManagementClient
 from azure.mgmt.network.models import (NetworkInterface,
 from azure.mgmt.network.models import (NetworkInterface,
                                        NetworkSecurityGroup, PublicIPAddress,
                                        NetworkSecurityGroup, PublicIPAddress,
                                        RouteTable, SecurityRule, SubResource,
                                        RouteTable, SecurityRule, SubResource,
-                                       Subnet, VirtualNetwork)
+                                       Subnet, TagsObject, VirtualNetwork)
 from azure.mgmt.resource import ResourceManagementClient
 from azure.mgmt.resource import ResourceManagementClient
 from azure.mgmt.resource.resources.models import ResourceGroup
 from azure.mgmt.resource.resources.models import ResourceGroup
 from azure.mgmt.storage import StorageManagementClient
 from azure.mgmt.storage import StorageManagementClient
@@ -384,10 +384,7 @@ class AzureClient(object):
                                              fw_id)
                                              fw_id)
         name = url_params.get(VM_FIREWALL_NAME, "")
         name = url_params.get(VM_FIREWALL_NAME, "")
         return self.network_management_client.network_security_groups. \
         return self.network_management_client.network_security_groups. \
-            begin_create_or_update(
-                self.resource_group, name,
-                NetworkSecurityGroup(
-                    tags=tags, location=self.region_name)).result()
+            update_tags(self.resource_group, name, TagsObject(tags=tags))
 
 
     def get_vm_firewall(self, fw_id):
     def get_vm_firewall(self, fw_id):
         url_params = azure_helpers.parse_url(VM_FIREWALL_RESOURCE_ID,
         url_params = azure_helpers.parse_url(VM_FIREWALL_RESOURCE_ID,
@@ -622,11 +619,9 @@ class AzureClient(object):
             return True
             return True
         else:
         else:
             name = url_params.get(IMAGE_NAME, "")
             name = url_params.get(IMAGE_NAME, "")
-            return self.compute_client.images. \
-                begin_create_or_update(
-                    self.resource_group, name,
-                    Image(tags=tags,
-                          location=self.region_name)).result()
+            return self.compute_client.images.begin_update(
+                self.resource_group, name,
+                ImageUpdate(tags=tags)).result()
 
 
     def list_vm_types(self):
     def list_vm_types(self):
         return self.compute_client.virtual_machine_sizes. \
         return self.compute_client.virtual_machine_sizes. \
@@ -659,7 +654,8 @@ class AzureClient(object):
         url_params = azure_helpers.parse_url(NETWORK_RESOURCE_ID, network_id)
         url_params = azure_helpers.parse_url(NETWORK_RESOURCE_ID, network_id)
         network_name = url_params.get(NETWORK_NAME, "")
         network_name = url_params.get(NETWORK_NAME, "")
         return self.network_management_client.virtual_networks. \
         return self.network_management_client.virtual_networks. \
-            begin_create_or_update(self.networking_resource_group, network_name, tags).result()
+            update_tags(self.networking_resource_group, network_name,
+                        TagsObject(tags=tags))
 
 
     def get_network_id_for_subnet(self, subnet_id):
     def get_network_id_for_subnet(self, subnet_id):
         url_params = azure_helpers.parse_url(SUBNET_RESOURCE_ID, subnet_id)
         url_params = azure_helpers.parse_url(SUBNET_RESOURCE_ID, subnet_id)
@@ -751,13 +747,9 @@ class AzureClient(object):
         url_params = azure_helpers.parse_url(PUBLIC_IP_RESOURCE_ID,
         url_params = azure_helpers.parse_url(PUBLIC_IP_RESOURCE_ID,
                                              fip_id)
                                              fip_id)
         fip_name = url_params.get(PUBLIC_IP_NAME, "")
         fip_name = url_params.get(PUBLIC_IP_NAME, "")
-        # Accept either a full PublicIPAddress model (refresh-and-update flow)
-        # or a raw tags dict.
-        fip = (tags if isinstance(tags, PublicIPAddress)
-               else PublicIPAddress(tags=tags, location=self.region_name))
         self.network_management_client.public_ip_addresses. \
         self.network_management_client.public_ip_addresses. \
-            begin_create_or_update(
-                self.networking_resource_group, fip_name, fip).result()
+            update_tags(self.networking_resource_group, fip_name,
+                        TagsObject(tags=tags))
 
 
     def list_floating_ips(self):
     def list_floating_ips(self):
         return self.network_management_client.public_ip_addresses.list(
         return self.network_management_client.public_ip_addresses.list(
@@ -839,8 +831,9 @@ class AzureClient(object):
         url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
         url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
                                              vm_id)
                                              vm_id)
         vm_name = url_params.get(VM_NAME, "")
         vm_name = url_params.get(VM_NAME, "")
-        self.compute_client.virtual_machines. \
-            begin_create_or_update(self.resource_group, vm_name, tags).result()
+        self.compute_client.virtual_machines.begin_update(
+            self.resource_group, vm_name,
+            VirtualMachineUpdate(tags=tags)).result()
 
 
     def delete_nic(self, nic_id):
     def delete_nic(self, nic_id):
         nic_params = azure_helpers.\
         nic_params = azure_helpers.\
@@ -978,5 +971,6 @@ class AzureClient(object):
              route_table_name, RouteTable(**params)).result()
              route_table_name, RouteTable(**params)).result()
 
 
     def update_route_table_tags(self, route_table_name, tags):
     def update_route_table_tags(self, route_table_name, tags):
-        self.network_management_client.route_tables. \
-            begin_create_or_update(self.resource_group, route_table_name, tags).result()
+        self.network_management_client.route_tables.update_tags(
+            self.resource_group, route_table_name,
+            TagsObject(tags=tags))

+ 4 - 4
cloudbridge/providers/azure/resources.py

@@ -766,7 +766,7 @@ class AzureNetwork(BaseNetwork):
         self.assert_valid_resource_label(value)
         self.assert_valid_resource_label(value)
         self._network.tags.update(Label=value or "")
         self._network.tags.update(Label=value or "")
         self._provider.azure_client. \
         self._provider.azure_client. \
-            update_network_tags(self.id, self._network)
+            update_network_tags(self.id, self._network.tags)
 
 
     @property
     @property
     def external(self):
     def external(self):
@@ -964,7 +964,7 @@ class AzureSubnet(BaseSubnet):
         kwargs = {self.tag_name: value or ""}
         kwargs = {self.tag_name: value or ""}
         az_network.tags.update(**kwargs)
         az_network.tags.update(**kwargs)
         self._provider.azure_client.update_network_tags(
         self._provider.azure_client.update_network_tags(
-            az_network.id, az_network)
+            az_network.id, az_network.tags)
 
 
     @property
     @property
     def tag_name(self):
     def tag_name(self):
@@ -1090,7 +1090,7 @@ class AzureInstance(BaseInstance):
         self.assert_valid_resource_label(value)
         self.assert_valid_resource_label(value)
         self._vm.tags.update(Label=value or "")
         self._vm.tags.update(Label=value or "")
         self._provider.azure_client. \
         self._provider.azure_client. \
-            update_vm_tags(self.id, self._vm)
+            update_vm_tags(self.id, self._vm.tags)
 
 
     @property
     @property
     def public_ips(self):
     def public_ips(self):
@@ -1477,7 +1477,7 @@ class AzureRouter(BaseRouter):
         self._route_table.tags.update(Label=value or "")
         self._route_table.tags.update(Label=value or "")
         self._provider.azure_client. \
         self._provider.azure_client. \
             update_route_table_tags(self._route_table.name,
             update_route_table_tags(self._route_table.name,
-                                    self._route_table)
+                                    self._route_table.tags)
 
 
     def refresh(self):
     def refresh(self):
         self._route_table = self._provider.azure_client. \
         self._route_table = self._provider.azure_client. \

+ 1 - 1
cloudbridge/providers/azure/services.py

@@ -1233,7 +1233,7 @@ class AzureSubnetService(BaseSubnetService):
             az_network = self.provider.azure_client.get_network(net_id)
             az_network = self.provider.azure_client.get_network(net_id)
             az_network.tags.pop(sn.tag_name)
             az_network.tags.pop(sn.tag_name)
             self.provider.azure_client.update_network_tags(
             self.provider.azure_client.update_network_tags(
-                az_network.id, az_network)
+                az_network.id, az_network.tags)
 
 
 
 
 class AzureRouterService(BaseRouterService):
 class AzureRouterService(BaseRouterService):