circuitbreaker.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. # ----------------------------------------------------------------------------------
  2. # MIT License
  3. #
  4. # Copyright(c) Microsoft Corporation. All rights reserved.
  5. #
  6. # Permission is hereby granted, free of charge, to any person obtaining a copy
  7. # of this software and associated documentation files (the "Software"), to deal
  8. # in the Software without restriction, including without limitation the rights
  9. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. # copies of the Software, and to permit persons to whom the Software is
  11. # furnished to do so, subject to the following conditions:
  12. # ----------------------------------------------------------------------------------
  13. # The above copyright notice and this permission notice shall be included in all
  14. # copies or substantial portions of the Software.
  15. #
  16. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. # SOFTWARE.
  23. # ----------------------------------------------------------------------------------
  24. import os
  25. import uuid
  26. import time
  27. import sys
  28. from azure.storage.blob import BlockBlobService
  29. from azure.storage.common.models import LocationMode
  30. from azure.storage.common.retry import LinearRetry
  31. # ----------------------------------------------------------------------------------
  32. # Azure Storage Circuit Breaker Demo
  33. # INSTRUCTIONS
  34. # Please see the README.md file for an overview explaining this application and how to run it.
  35. # ----------------------------------------------------------------------------------
  36. # Documentation References:
  37. # Associated Article - https://docs.microsoft.com/en-us/azure/storage/blobs/storage-create-geo-redundant-storage-python
  38. # Designing HA Apps with RA-GRS storage -https://docs.microsoft.com/azure/storage/storage-designing-ha-apps-with-ra-grs/
  39. # Getting Started with Blobs-https://docs.microsoft.com/en-us/azure/storage/blobs/storage-python-how-to-use-blob-storage
  40. # Azure Storage Replication - https://docs.microsoft.com/azure/storage/storage-redundancy
  41. # ----------------------------------------------------------------------------------
  42. account_name = os.environ.get('accountname')
  43. account_key = os.environ.get('accountkey')
  44. # Track how many times retry events occur.
  45. retry_count = 0 # Number of retries that have occurred
  46. retry_threshold = 5 # Threshold number of retries before switching to secondary
  47. secondary_read_count = 0 # Number of reads from secondary that have occurred
  48. secondary_threshold = 20 # Threshold number of reads from secondary before switching back to primary
  49. # This is the CloudBlobClient object used to access the blob service
  50. blob_client = None
  51. # This is the container used to store and access the blob to be used for testing
  52. container_name = None
  53. '''
  54. Main method. Sets up the objects needed, the performs a loop to perform blob
  55. operation repeatedly, responding to the Retry and Response Received events.
  56. '''
  57. def run_circuit_breaker():
  58. # Name of image to use for testing.
  59. image_to_upload = "HelloWorld.png"
  60. global blob_client
  61. global container_name
  62. try:
  63. # Create a reference to the blob client and container using the storage account name and key
  64. blob_client = BlockBlobService(account_name, account_key)
  65. # Make the container unique by using a UUID in the name.
  66. container_name = "democontainer" + str(uuid.uuid4())
  67. blob_client.create_container(container_name)
  68. except Exception as ex:
  69. print("Please make sure you have put the correct storage account name and key.")
  70. print(ex)
  71. # Define a reference to the actual blob and upload the block_blob to the newly created container
  72. full_path_to_file = os.path.join(os.path.dirname(__file__), image_to_upload)
  73. blob_client.create_blob_from_path(container_name, image_to_upload, full_path_to_file)
  74. # Set the location mode to secondary, so you can check just the secondary data center.
  75. blob_client.location_mode = LocationMode.SECONDARY
  76. blob_client.retry = LinearRetry(backoff=0).retry
  77. # Before proceeding, wait until the blob has been replicated to the secondary data center.
  78. # Loop and check for the presence of the blob once in a second until it hits 60 seconds
  79. # or until it finds it
  80. counter = 0
  81. while counter < 60:
  82. counter += 1
  83. sys.stdout.write("\nAttempt {0} to see if the blob has replicated to the secondary storage yet.".format(counter))
  84. sys.stdout.flush()
  85. if blob_client.exists(container_name, image_to_upload):
  86. break
  87. # Wait a second, then loop around and try again
  88. # When it's finished replicating to the secondary, continue.
  89. time.sleep(1)
  90. # Set the starting LocationMode to Primary, then Secondary.
  91. # Here we use the linear retry by default, but allow it to retry to secondary if
  92. # the initial request to primary fails.
  93. # Note that the default is Primary. You must have RA-GRS enabled to use this
  94. blob_client.location_mode = LocationMode.PRIMARY
  95. blob_client.retry = LinearRetry(max_attempts=retry_threshold, backoff=1).retry
  96. '''
  97. ************INSTRUCTIONS**************k
  98. To perform the test, first replace the 'accountname' and 'accountkey' with your storage account name and key.
  99. Every time it calls get_blob_to_path it will hit the response_callback function.
  100. Next, run this app. While this loop is running, pause the program by pressing any key, and
  101. put the intercept code in Fiddler (that will intercept and return a 503).
  102. For instructions on modifying Fiddler, look at the Fiddler_script.text file in this project.
  103. There are also full instructions in the ReadMe_Instructions.txt file included in this project.
  104. After adding the custom script to Fiddler, calls to primary storage will fail with a retryable
  105. error which will trigger the Retrying event (above).
  106. Then it will switch over and read the secondary. It will do that 20 times, then try to
  107. switch back to the primary.
  108. After seeing that happen, pause this again and remove the intercepting Fiddler code
  109. Then you'll see it return to the primary and finish.
  110. '''
  111. print("\n\nThe application will pause at 200 unit interval")
  112. for i in range(0, 1000):
  113. if blob_client.location_mode == LocationMode.SECONDARY:
  114. sys.stdout.write("S{0} ".format(str(i)))
  115. else:
  116. sys.stdout.write("P{0} ".format(str(i)))
  117. sys.stdout.flush()
  118. try:
  119. # These function is called immediately after retry evaluation is performed.
  120. # It is used to trigger the change from primary to secondary and back
  121. blob_client.retry_callback = retry_callback
  122. # Download the file
  123. blob_client.get_blob_to_path(container_name, image_to_upload,
  124. str.replace(full_path_to_file, ".png", "Copy.png"))
  125. # Set the application to pause at 200 unit intervals to implement simulated failures
  126. if i == 200 or i == 400 or i == 600 or i == 800:
  127. sys.stdout.write("\nPress the Enter key to resume")
  128. sys.stdout.flush()
  129. if sys.version_info[0] < 3:
  130. raw_input()
  131. else:
  132. input()
  133. except Exception as ex:
  134. print(ex)
  135. finally:
  136. # Force an exists call to succeed by resetting the status
  137. blob_client.response_callback = response_callback
  138. # Clean up resources
  139. blob_client.delete_container(container_name)
  140. '''
  141. RequestCompleted Event handler
  142. If it's not pointing at the secondary, let it go through. It was either successful,
  143. or it failed with a non-retryable event.
  144. If it's pointing at the secondary, increment the read count.
  145. If the number of reads has hit the threshold of how many reads you want to do against the secondary,
  146. before you switch back to primary, switch back and reset the secondary_read_count.
  147. '''
  148. def response_callback(response):
  149. global secondary_read_count
  150. if blob_client.location_mode == LocationMode.SECONDARY:
  151. # You're reading the secondary. Let it read the secondary [secondaryThreshold] times,
  152. # then switch back to the primary and see if it is available now.
  153. secondary_read_count += 1
  154. if secondary_read_count >= secondary_threshold:
  155. blob_client.location_mode = LocationMode.PRIMARY
  156. secondary_read_count = 0
  157. '''
  158. Retry Event handler
  159. If it has retried more times than allowed, and it's not already pointed to the secondary,
  160. flip it to the secondary and reset the retry count.
  161. If it has retried more times than allowed, and it's already pointed to the secondary throw an exception.
  162. '''
  163. def retry_callback(retry_context):
  164. global retry_count
  165. retry_count = retry_context.count
  166. sys.stdout.write("\nRetrying event because of failure reading the primary. RetryCount= {0}".format(retry_count))
  167. sys.stdout.flush()
  168. # Check if we have more than n-retries in which case switch to secondary
  169. if retry_count >= retry_threshold:
  170. # Check to see if we can fail over to secondary.
  171. if blob_client.location_mode != LocationMode.SECONDARY:
  172. blob_client.location_mode = LocationMode.SECONDARY
  173. retry_count = 0
  174. else:
  175. raise Exception("Both primary and secondary are unreachable. "
  176. "Check your application's network connection.")
  177. if __name__ == '__main__':
  178. print("Azure storage Circuit Breaker Sample \n")
  179. try:
  180. run_circuit_breaker()
  181. except Exception as e:
  182. print("Error thrown = {0}".format(e))
  183. sys.stdout.write("\nPress any key to exit.")
  184. sys.stdout.flush()
  185. if sys.version_info[0]<3:
  186. raw_input()
  187. else:
  188. input()