From 06807cf84bcb40840283cbaeb9463148951c4dee Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 25 Nov 2024 11:57:06 -0800 Subject: [PATCH 01/50] change celery pool support from prefork to threads --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index acd31f390..ae79b930c 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,8 @@ run-celery: ## Run celery, TODO remove purge for staging/prod -A run_celery.notify_celery worker \ --pidfile="/tmp/celery.pid" \ --loglevel=INFO \ - --concurrency=4 + --pool=threads + --concurrency=10 .PHONY: dead-code From 738b4f063e647241769e73edf6d75c9db487bd54 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 25 Nov 2024 12:17:38 -0800 Subject: [PATCH 02/50] add adr --- ...0-adr-celery-pool-support-best-practice.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 docs/adrs/0010-adr-celery-pool-support-best-practice.md diff --git a/docs/adrs/0010-adr-celery-pool-support-best-practice.md b/docs/adrs/0010-adr-celery-pool-support-best-practice.md new file mode 100644 index 000000000..b8525e654 --- /dev/null +++ b/docs/adrs/0010-adr-celery-pool-support-best-practice.md @@ -0,0 +1,23 @@ +# Make best use of celery worker pools + +Status: N/A +Date: N/A + +### Context +Our API application started with initial celery pool support of 'prefork' (the default) and concurrency of 4. We continuously encountered instability, which we initially attributed to a resource leak. As a result of this we added the configuration `worker-max-tasks-per-child=500` which is a best practice. When we ran a load test of 25000 simulated messages, however, we continued to see stability issues, amounting to a crash of the app after 4 hours requiring a restage. Based on running `cf app notify-api-production` and observing that `cpu entitlement` was off the charts at 10000% to 12000% for the works, and after doing some further reading, we came to the conclusion that perhaps `prefork` pool support is not the best type of pool support for the API application. + +The problem with `prefork` is that each process has a tendency to hang onto the CPU allocated to it, even if it is not being used. Our application is not computationally intensive and largely consists of downloading strings from S3, parsing the strings, and sending them out as SMS messages. Based on the determination that our app is likely I/O bound, we elected to do an experiment where we changed pool support to `threads` and increased concurrency to `10`. The expectation is that memory usage will decrease and CPU usage will decrease and the app will not become unavailable. + +### Decision + +### Consequences + +### Author +@kenkehl + +### Stakeholders +@ccostino +@stvnrlly + +### Next Steps +- Run an after-hours load test with production configured to --pool=threads and --concurrency=10 (concurrency can be cautiously increased once we know it works) From 8c6f7ede0bb0043297f57c95fc0a841ea95ada0d Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 25 Nov 2024 15:26:50 -0800 Subject: [PATCH 03/50] oops add pool=threads to manifest --- manifest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.yml b/manifest.yml index a8a3e7f2b..39e842730 100644 --- a/manifest.yml +++ b/manifest.yml @@ -26,7 +26,7 @@ applications: - type: worker instances: ((worker_instances)) memory: ((worker_memory)) - command: newrelic-admin run-program celery -A run_celery.notify_celery worker --loglevel=INFO --concurrency=4 + command: newrelic-admin run-program celery -A run_celery.notify_celery worker --loglevel=INFO --pool=threads --concurrency=10 - type: scheduler instances: 1 memory: ((scheduler_memory)) From 56ebed5445e59e5bc6381a0413b089d73d981d9a Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Fri, 13 Dec 2024 14:40:37 -0800 Subject: [PATCH 04/50] switch to log insights for delivery receipts --- app/celery/scheduled_tasks.py | 9 ++++ app/clients/cloudwatch/aws_cloudwatch.py | 65 ++++++++++++++++++++++++ app/dao/notifications_dao.py | 24 +++++++++ 3 files changed, 98 insertions(+) diff --git a/app/celery/scheduled_tasks.py b/app/celery/scheduled_tasks.py index 3597bdbb7..f884de834 100644 --- a/app/celery/scheduled_tasks.py +++ b/app/celery/scheduled_tasks.py @@ -11,6 +11,7 @@ from app.celery.tasks import ( process_job, process_row, ) +from app.clients.cloudwatch.aws_cloudwatch import AwsCloudwatchClient from app.config import QueueNames from app.dao.invited_org_user_dao import ( delete_org_invitations_created_more_than_two_days_ago, @@ -231,3 +232,11 @@ def check_for_services_with_high_failure_rates_or_sending_to_tv_numbers(): technical_ticket=True, ) zendesk_client.send_ticket_to_zendesk(ticket) + + +@notify_celery.task(name="process_delivery_receipts_first_wave") +def process_delivery_receipts_first_wave(): + cloudwatch = AwsCloudwatchClient() + start_time = utc_now() - timedelta(hours=1) + end_time = utc_now() + receipts = cloudwatch.check_delivery_receipts(start_time, end_time) diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index 36bcf5dca..c914501bc 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -2,12 +2,14 @@ import json import os import re from datetime import timedelta +from time import sleep from boto3 import client from flask import current_app from app.clients import AWS_CLIENT_CONFIG, Client from app.cloudfoundry_config import cloud_config +from app.dao.notifications_dao import dao_update_delivery_receipts from app.exceptions import NotificationTechnicalFailureException from app.utils import hilite, utc_now @@ -108,6 +110,7 @@ class AwsCloudwatchClient(Client): return logline return None + # DEPRECATED def check_sms(self, message_id, notification_id, created_at): region = cloud_config.sns_region # TODO this clumsy approach to getting the account number will be fixed as part of notify-api #258 @@ -165,3 +168,65 @@ class AwsCloudwatchClient(Client): raise NotificationTechnicalFailureException( f"No event found for message_id {message_id} notification_id {notification_id}" ) + + +def do_log_insights(self): + region = cloud_config.sns_region + account_number = self._extract_account_number(cloud_config.ses_domain_arn) + + log_group_name = f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber" + log_group_name_failed = ( + f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber/Failed" + ) + + query = """ + fields @timestamp, status, delivery.providerResponse, delivery.destination, notification.messageId, delivery.phoneCarrier + | sort @timestamp asc + """ + start = utc_now() - timedelta(hours=1) + end = utc_now() + + response = client._client.start_query( + logGroupName=log_group_name, + startTime=int(start.timestamp()), + endTime=int(end.timestamp()), + queryString=query, + ) + query_id = response["queryId"] + while True: + result = client._client.get_query_results(queryId=query_id) + if result["status"] == "Complete": + break + sleep(1) + + delivery_receipts = [] + for log in result["results"]: + receipt = {field["field"]: field["value"] for field in log} + delivery_receipts.append(receipt) + print(receipt) + + delivered = delivery_receipts + + response = client._client.start_query( + logGroupName=log_group_name_failed, + startTime=int(start.timestamp()), + endTime=int(end.timestamp()), + queryString=query, + ) + query_id = response["queryId"] + while True: + result = client._client.get_query_results(queryId=query_id) + if result["status"] == "Complete": + break + sleep(1) + + delivery_receipts = [] + for log in result["results"]: + receipt = {field["field"]: field["value"] for field in log} + delivery_receipts.append(receipt) + print(receipt) + + failed = delivery_receipts + + dao_update_delivery_receipts(delivered) + dao_update_delivery_receipts(failed) diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index 052242644..d676f79f7 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -707,3 +707,27 @@ def get_service_ids_with_notifications_on_date(notification_type, date): union(notification_table_query, ft_status_table_query).subquery() ).distinct() } + + +def dao_update_delivery_receipts(receipts): + id_to_status = {r["notification.messageId"]: r["status"] for r in receipts} + id_to_carrier = { + r["notification.messageId"]: r["delivery.phoneCarrier"] for r in receipts + } + id_to_provider_response = { + r["notification.messageId"]: r["delivery.providerResponse"] for r in receipts + } + id_to_timestamp = {r["notification.messageId"]: r["@timestamp"] for r in receipts} + + stmt = ( + update(Notification) + .where(Notification.c.message_id.in_(id_to_carrier.keys())) + .values( + carrier=case(id_to_carrier), + status=case(id_to_status), + notification_status=case(id_to_status), + sent_at=case(id_to_timestamp), + ) + ) + db.session.execute(stmt) + db.session.commit() From 7529ae0153d949c50e6d2b7d54323b8e4f2d7418 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Fri, 13 Dec 2024 15:04:18 -0800 Subject: [PATCH 05/50] more --- app/celery/provider_tasks.py | 103 +--------------------- app/clients/cloudwatch/aws_cloudwatch.py | 59 ------------- tests/app/celery/test_provider_tasks.py | 106 +---------------------- 3 files changed, 4 insertions(+), 264 deletions(-) diff --git a/app/celery/provider_tasks.py b/app/celery/provider_tasks.py index a75a68c96..3bdd2d9c0 100644 --- a/app/celery/provider_tasks.py +++ b/app/celery/provider_tasks.py @@ -1,107 +1,19 @@ import json import os -from datetime import timedelta -from botocore.exceptions import ClientError from flask import current_app from sqlalchemy.orm.exc import NoResultFound -from app import aws_cloudwatch_client, notify_celery, redis_store +from app import notify_celery, redis_store from app.clients.email import EmailClientNonRetryableException from app.clients.email.aws_ses import AwsSesClientThrottlingSendRateException from app.clients.sms import SmsClientResponseException from app.config import Config, QueueNames from app.dao import notifications_dao -from app.dao.notifications_dao import ( - sanitize_successful_notification_by_id, - update_notification_status_by_id, -) +from app.dao.notifications_dao import update_notification_status_by_id from app.delivery import send_to_providers from app.enums import NotificationStatus from app.exceptions import NotificationTechnicalFailureException -from app.utils import utc_now - -# This is the amount of time to wait after sending an sms message before we check the aws logs and look for delivery -# receipts -DELIVERY_RECEIPT_DELAY_IN_SECONDS = 30 - - -@notify_celery.task( - bind=True, - name="check_sms_delivery_receipt", - max_retries=48, - default_retry_delay=300, -) -def check_sms_delivery_receipt(self, message_id, notification_id, sent_at): - """ - This is called after deliver_sms to check the status of the message. This uses the same number of - retries and the same delay period as deliver_sms. In addition, this fires five minutes after - deliver_sms initially. So the idea is that most messages will succeed and show up in the logs quickly. - Other message will resolve successfully after a retry or to. A few will fail but it will take up to - 4 hours to know for sure. The call to check_sms will raise an exception if neither a success nor a - failure appears in the cloudwatch logs, so this should keep retrying until the log appears, or until - we run out of retries. - """ - # TODO the localstack cloudwatch doesn't currently have our log groups. Possibly create them with awslocal? - if aws_cloudwatch_client.is_localstack(): - status = "success" - provider_response = "this is a fake successful localstack sms message" - carrier = "unknown" - else: - try: - status, provider_response, carrier = aws_cloudwatch_client.check_sms( - message_id, notification_id, sent_at - ) - except NotificationTechnicalFailureException as ntfe: - provider_response = "Unable to find carrier response -- still looking" - status = "pending" - carrier = "" - update_notification_status_by_id( - notification_id, - status, - carrier=carrier, - provider_response=provider_response, - ) - raise self.retry(exc=ntfe) - except ClientError as err: - # Probably a ThrottlingException but could be something else - error_code = err.response["Error"]["Code"] - provider_response = ( - f"{error_code} while checking sms receipt -- still looking" - ) - status = "pending" - carrier = "" - update_notification_status_by_id( - notification_id, - status, - carrier=carrier, - provider_response=provider_response, - ) - raise self.retry(exc=err) - - if status == "success": - status = NotificationStatus.DELIVERED - elif status == "failure": - status = NotificationStatus.FAILED - # if status is not success or failure the client raised an exception and this method will retry - - if status == NotificationStatus.DELIVERED: - sanitize_successful_notification_by_id( - notification_id, carrier=carrier, provider_response=provider_response - ) - current_app.logger.info( - f"Sanitized notification {notification_id} that was successfully delivered" - ) - else: - update_notification_status_by_id( - notification_id, - status, - carrier=carrier, - provider_response=provider_response, - ) - current_app.logger.info( - f"Updated notification {notification_id} with response '{provider_response}'" - ) @notify_celery.task( @@ -127,17 +39,8 @@ def deliver_sms(self, notification_id): ansi_green + f"AUTHENTICATION CODE: {notification.content}" + ansi_reset ) # Code branches off to send_to_providers.py - message_id = send_to_providers.send_sms_to_provider(notification) + send_to_providers.send_sms_to_provider(notification) - # DEPRECATED - # We have to put it in UTC. For other timezones, the delay - # will be ignored and it will fire immediately (although this probably only affects developer testing) - my_eta = utc_now() + timedelta(seconds=DELIVERY_RECEIPT_DELAY_IN_SECONDS) - check_sms_delivery_receipt.apply_async( - [message_id, notification_id, notification.created_at], - eta=my_eta, - queue=QueueNames.CHECK_SMS, - ) except Exception as e: update_notification_status_by_id( notification_id, diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index c914501bc..9bb52ea88 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -110,65 +110,6 @@ class AwsCloudwatchClient(Client): return logline return None - # DEPRECATED - def check_sms(self, message_id, notification_id, created_at): - region = cloud_config.sns_region - # TODO this clumsy approach to getting the account number will be fixed as part of notify-api #258 - account_number = self._extract_account_number(cloud_config.ses_domain_arn) - - time_now = utc_now() - log_group_name = f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber" - filter_pattern = '{$.notification.messageId="XXXXX"}' - filter_pattern = filter_pattern.replace("XXXXX", message_id) - all_log_events = self._get_log(filter_pattern, log_group_name, created_at) - if all_log_events and len(all_log_events) > 0: - event = all_log_events[0] - message = json.loads(event["message"]) - self.warn_if_dev_is_opted_out( - message["delivery"]["providerResponse"], notification_id - ) - # Here we map the answer from aws to the message_id. - # Previously, in send_to_providers, we mapped the job_id and row number - # to the message id. And on the admin side we mapped the csv filename - # to the job_id. So by tracing through all the logs we can go: - # filename->job_id->message_id->what really happened - current_app.logger.info( - hilite(f"DELIVERED: {message} for message_id {message_id}") - ) - return ( - "success", - message["delivery"]["providerResponse"], - message["delivery"].get("phoneCarrier", "Unknown Carrier"), - ) - - log_group_name = ( - f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber/Failure" - ) - all_failed_events = self._get_log(filter_pattern, log_group_name, created_at) - if all_failed_events and len(all_failed_events) > 0: - event = all_failed_events[0] - message = json.loads(event["message"]) - self.warn_if_dev_is_opted_out( - message["delivery"]["providerResponse"], notification_id - ) - - current_app.logger.info( - hilite(f"FAILED: {message} for message_id {message_id}") - ) - return ( - "failure", - message["delivery"]["providerResponse"], - message["delivery"].get("phoneCarrier", "Unknown Carrier"), - ) - - if time_now > (created_at + timedelta(hours=3)): - # see app/models.py Notification. This message corresponds to "permanent-failure", - # but we are copy/pasting here to avoid circular imports. - return "failure", "Unable to find carrier response." - raise NotificationTechnicalFailureException( - f"No event found for message_id {message_id} notification_id {notification_id}" - ) - def do_log_insights(self): region = cloud_config.sns_region diff --git a/tests/app/celery/test_provider_tasks.py b/tests/app/celery/test_provider_tasks.py index 80d5a0d7e..e16e9c54b 100644 --- a/tests/app/celery/test_provider_tasks.py +++ b/tests/app/celery/test_provider_tasks.py @@ -7,11 +7,7 @@ from celery.exceptions import MaxRetriesExceededError import app from app.celery import provider_tasks -from app.celery.provider_tasks import ( - check_sms_delivery_receipt, - deliver_email, - deliver_sms, -) +from app.celery.provider_tasks import deliver_email, deliver_sms from app.clients.email import EmailClientNonRetryableException from app.clients.email.aws_ses import ( AwsSesClientException, @@ -27,110 +23,10 @@ def test_should_have_decorated_tasks_functions(): assert deliver_email.__wrapped__.__name__ == "deliver_email" -def test_should_check_delivery_receipts_success(sample_notification, mocker): - mocker.patch("app.delivery.send_to_providers.send_sms_to_provider") - mocker.patch( - "app.celery.provider_tasks.aws_cloudwatch_client.is_localstack", - return_value=False, - ) - mocker.patch( - "app.celery.provider_tasks.aws_cloudwatch_client.check_sms", - return_value=("success", "okay", "AT&T"), - ) - mock_sanitize = mocker.patch( - "app.celery.provider_tasks.sanitize_successful_notification_by_id" - ) - check_sms_delivery_receipt( - "message_id", sample_notification.id, "2024-10-20 00:00:00+0:00" - ) - # This call should be made if the message was successfully delivered - mock_sanitize.assert_called_once() - - -def test_should_check_delivery_receipts_failure(sample_notification, mocker): - mocker.patch("app.delivery.send_to_providers.send_sms_to_provider") - mocker.patch( - "app.celery.provider_tasks.aws_cloudwatch_client.is_localstack", - return_value=False, - ) - mock_update = mocker.patch( - "app.celery.provider_tasks.update_notification_status_by_id" - ) - mocker.patch( - "app.celery.provider_tasks.aws_cloudwatch_client.check_sms", - return_value=("failure", "not okay", "AT&T"), - ) - mock_sanitize = mocker.patch( - "app.celery.provider_tasks.sanitize_successful_notification_by_id" - ) - check_sms_delivery_receipt( - "message_id", sample_notification.id, "2024-10-20 00:00:00+0:00" - ) - mock_sanitize.assert_not_called() - mock_update.assert_called_once() - - -def test_should_check_delivery_receipts_client_error(sample_notification, mocker): - mocker.patch("app.delivery.send_to_providers.send_sms_to_provider") - mocker.patch( - "app.celery.provider_tasks.aws_cloudwatch_client.is_localstack", - return_value=False, - ) - mock_update = mocker.patch( - "app.celery.provider_tasks.update_notification_status_by_id" - ) - error_response = {"Error": {"Code": "SomeCode", "Message": "Some Message"}} - operation_name = "SomeOperation" - mocker.patch( - "app.celery.provider_tasks.aws_cloudwatch_client.check_sms", - side_effect=ClientError(error_response, operation_name), - ) - mock_sanitize = mocker.patch( - "app.celery.provider_tasks.sanitize_successful_notification_by_id" - ) - try: - check_sms_delivery_receipt( - "message_id", sample_notification.id, "2024-10-20 00:00:00+0:00" - ) - - assert 1 == 0 - except ClientError: - mock_sanitize.assert_not_called() - mock_update.assert_called_once() - - -def test_should_check_delivery_receipts_ntfe(sample_notification, mocker): - mocker.patch("app.delivery.send_to_providers.send_sms_to_provider") - mocker.patch( - "app.celery.provider_tasks.aws_cloudwatch_client.is_localstack", - return_value=False, - ) - mock_update = mocker.patch( - "app.celery.provider_tasks.update_notification_status_by_id" - ) - mocker.patch( - "app.celery.provider_tasks.aws_cloudwatch_client.check_sms", - side_effect=NotificationTechnicalFailureException(), - ) - mock_sanitize = mocker.patch( - "app.celery.provider_tasks.sanitize_successful_notification_by_id" - ) - try: - check_sms_delivery_receipt( - "message_id", sample_notification.id, "2024-10-20 00:00:00+0:00" - ) - - assert 1 == 0 - except NotificationTechnicalFailureException: - mock_sanitize.assert_not_called() - mock_update.assert_called_once() - - def test_should_call_send_sms_to_provider_from_deliver_sms_task( sample_notification, mocker ): mocker.patch("app.delivery.send_to_providers.send_sms_to_provider") - mocker.patch("app.celery.provider_tasks.check_sms_delivery_receipt") deliver_sms(sample_notification.id) app.delivery.send_to_providers.send_sms_to_provider.assert_called_with( From b784d33173296dfa530700d8d0218cf38ee26723 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Fri, 13 Dec 2024 16:45:09 -0800 Subject: [PATCH 06/50] more work --- app/celery/scheduled_tasks.py | 12 ++++++++---- app/clients/cloudwatch/aws_cloudwatch.py | 12 ++++-------- app/config.py | 5 +++++ app/dao/notifications_dao.py | 1 + 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/celery/scheduled_tasks.py b/app/celery/scheduled_tasks.py index f884de834..562e4422d 100644 --- a/app/celery/scheduled_tasks.py +++ b/app/celery/scheduled_tasks.py @@ -23,7 +23,10 @@ from app.dao.jobs_dao import ( find_jobs_with_missing_rows, find_missing_row_for_job, ) -from app.dao.notifications_dao import notifications_not_yet_sent +from app.dao.notifications_dao import ( + dao_update_delivery_receipts, + notifications_not_yet_sent, +) from app.dao.services_dao import ( dao_find_services_sending_to_tv_numbers, dao_find_services_with_high_failure_rates, @@ -234,9 +237,10 @@ def check_for_services_with_high_failure_rates_or_sending_to_tv_numbers(): zendesk_client.send_ticket_to_zendesk(ticket) -@notify_celery.task(name="process_delivery_receipts_first_wave") -def process_delivery_receipts_first_wave(): +@notify_celery.task(name="process-delivery-receipts") +def process_delivery_receipts(): cloudwatch = AwsCloudwatchClient() - start_time = utc_now() - timedelta(hours=1) + start_time = utc_now() - timedelta(minutes=10) end_time = utc_now() receipts = cloudwatch.check_delivery_receipts(start_time, end_time) + dao_update_delivery_receipts(receipts) diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index 9bb52ea88..aa53877ea 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -1,7 +1,5 @@ -import json import os import re -from datetime import timedelta from time import sleep from boto3 import client @@ -10,8 +8,7 @@ from flask import current_app from app.clients import AWS_CLIENT_CONFIG, Client from app.cloudfoundry_config import cloud_config from app.dao.notifications_dao import dao_update_delivery_receipts -from app.exceptions import NotificationTechnicalFailureException -from app.utils import hilite, utc_now +from app.utils import utc_now class AwsCloudwatchClient(Client): @@ -111,7 +108,7 @@ class AwsCloudwatchClient(Client): return None -def do_log_insights(self): +def check_delivery_receipts(self, start, end): region = cloud_config.sns_region account_number = self._extract_account_number(cloud_config.ses_domain_arn) @@ -121,11 +118,10 @@ def do_log_insights(self): ) query = """ - fields @timestamp, status, delivery.providerResponse, delivery.destination, notification.messageId, delivery.phoneCarrier + fields @timestamp, status, delivery.providerResponse, delivery.destination, + notification.messageId, delivery.phoneCarrier | sort @timestamp asc """ - start = utc_now() - timedelta(hours=1) - end = utc_now() response = client._client.start_query( logGroupName=log_group_name, diff --git a/app/config.py b/app/config.py index 12159e289..e7f4af14d 100644 --- a/app/config.py +++ b/app/config.py @@ -196,6 +196,11 @@ class Config(object): "schedule": timedelta(minutes=63), "options": {"queue": QueueNames.PERIODIC}, }, + "process-delivery-receipts": { + "task": "process-delivery-receipts", + "schedule": timedelta(minutes=8), + "options": {"queue": QueueNames.PERIODIC}, + }, "expire-or-delete-invitations": { "task": "expire-or-delete-invitations", "schedule": timedelta(minutes=66), diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index d676f79f7..d3871c59b 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -727,6 +727,7 @@ def dao_update_delivery_receipts(receipts): status=case(id_to_status), notification_status=case(id_to_status), sent_at=case(id_to_timestamp), + provider_response=case(id_to_provider_response), ) ) db.session.execute(stmt) From b28b54776279514ec440721c760fccdf8ac1f970 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Fri, 13 Dec 2024 17:30:00 -0800 Subject: [PATCH 07/50] more --- app/celery/scheduled_tasks.py | 1 + app/clients/cloudwatch/aws_cloudwatch.py | 102 +++++++++++------------ app/config.py | 2 +- 3 files changed, 51 insertions(+), 54 deletions(-) diff --git a/app/celery/scheduled_tasks.py b/app/celery/scheduled_tasks.py index 562e4422d..d5aaa27a8 100644 --- a/app/celery/scheduled_tasks.py +++ b/app/celery/scheduled_tasks.py @@ -240,6 +240,7 @@ def check_for_services_with_high_failure_rates_or_sending_to_tv_numbers(): @notify_celery.task(name="process-delivery-receipts") def process_delivery_receipts(): cloudwatch = AwsCloudwatchClient() + cloudwatch.init_app(current_app) start_time = utc_now() - timedelta(minutes=10) end_time = utc_now() receipts = cloudwatch.check_delivery_receipts(start_time, end_time) diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index aa53877ea..825d13c5e 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -7,7 +7,6 @@ from flask import current_app from app.clients import AWS_CLIENT_CONFIG, Client from app.cloudfoundry_config import cloud_config -from app.dao.notifications_dao import dao_update_delivery_receipts from app.utils import utc_now @@ -107,63 +106,60 @@ class AwsCloudwatchClient(Client): return logline return None + def check_delivery_receipts(self, start, end): + region = cloud_config.sns_region + account_number = self._extract_account_number(cloud_config.ses_domain_arn) -def check_delivery_receipts(self, start, end): - region = cloud_config.sns_region - account_number = self._extract_account_number(cloud_config.ses_domain_arn) + log_group_name = f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber" + log_group_name_failed = ( + f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber/Failed" + ) - log_group_name = f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber" - log_group_name_failed = ( - f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber/Failed" - ) + query = """ + fields @timestamp, status, delivery.providerResponse, delivery.destination, + notification.messageId, delivery.phoneCarrier + | sort @timestamp asc + """ - query = """ - fields @timestamp, status, delivery.providerResponse, delivery.destination, - notification.messageId, delivery.phoneCarrier - | sort @timestamp asc - """ + response = self._client.start_query( + logGroupName=log_group_name, + startTime=int(start.timestamp()), + endTime=int(end.timestamp()), + queryString=query, + ) + query_id = response["queryId"] + while True: + result = client._client.get_query_results(queryId=query_id) + if result["status"] == "Complete": + break + sleep(1) - response = client._client.start_query( - logGroupName=log_group_name, - startTime=int(start.timestamp()), - endTime=int(end.timestamp()), - queryString=query, - ) - query_id = response["queryId"] - while True: - result = client._client.get_query_results(queryId=query_id) - if result["status"] == "Complete": - break - sleep(1) + delivery_receipts = [] + for log in result["results"]: + receipt = {field["field"]: field["value"] for field in log} + delivery_receipts.append(receipt) + print(receipt) - delivery_receipts = [] - for log in result["results"]: - receipt = {field["field"]: field["value"] for field in log} - delivery_receipts.append(receipt) - print(receipt) + delivered = delivery_receipts - delivered = delivery_receipts + response = client._client.start_query( + logGroupName=log_group_name_failed, + startTime=int(start.timestamp()), + endTime=int(end.timestamp()), + queryString=query, + ) + query_id = response["queryId"] + while True: + result = client._client.get_query_results(queryId=query_id) + if result["status"] == "Complete": + break + sleep(1) - response = client._client.start_query( - logGroupName=log_group_name_failed, - startTime=int(start.timestamp()), - endTime=int(end.timestamp()), - queryString=query, - ) - query_id = response["queryId"] - while True: - result = client._client.get_query_results(queryId=query_id) - if result["status"] == "Complete": - break - sleep(1) + delivery_receipts = [] + for log in result["results"]: + receipt = {field["field"]: field["value"] for field in log} + delivery_receipts.append(receipt) + print(receipt) - delivery_receipts = [] - for log in result["results"]: - receipt = {field["field"]: field["value"] for field in log} - delivery_receipts.append(receipt) - print(receipt) - - failed = delivery_receipts - - dao_update_delivery_receipts(delivered) - dao_update_delivery_receipts(failed) + failed = delivery_receipts + return delivered + failed diff --git a/app/config.py b/app/config.py index e7f4af14d..d935b08b9 100644 --- a/app/config.py +++ b/app/config.py @@ -198,7 +198,7 @@ class Config(object): }, "process-delivery-receipts": { "task": "process-delivery-receipts", - "schedule": timedelta(minutes=8), + "schedule": timedelta(minutes=1), "options": {"queue": QueueNames.PERIODIC}, }, "expire-or-delete-invitations": { From 85e100febe51e04d1a0e83b062f62f117ba80b30 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Fri, 13 Dec 2024 17:40:28 -0800 Subject: [PATCH 08/50] fix --- tests/app/clients/test_aws_cloudwatch.py | 46 ------------------------ 1 file changed, 46 deletions(-) diff --git a/tests/app/clients/test_aws_cloudwatch.py b/tests/app/clients/test_aws_cloudwatch.py index b9529037b..fce0e150c 100644 --- a/tests/app/clients/test_aws_cloudwatch.py +++ b/tests/app/clients/test_aws_cloudwatch.py @@ -2,7 +2,6 @@ import pytest from flask import current_app from app import aws_cloudwatch_client -from app.utils import utc_now def test_check_sms_no_event_error_condition(notify_api, mocker): @@ -74,51 +73,6 @@ def test_warn_if_dev_is_opted_out(response, notify_id, expected_message): assert result == expected_message -def test_check_sms_success(notify_api, mocker): - aws_cloudwatch_client.init_app(current_app) - boto_mock = mocker.patch.object(aws_cloudwatch_client, "_client", create=True) - boto_mock.filter_log_events.side_effect = side_effect - mocker.patch.dict( - "os.environ", - {"SES_DOMAIN_ARN": "arn:aws:ses:us-west-2:12345:identity/ses-xxx.xxx.xxx.xxx"}, - ) - - message_id = "succeed" - notification_id = "ccc" - created_at = utc_now() - with notify_api.app_context(): - aws_cloudwatch_client.check_sms(message_id, notification_id, created_at) - - # We check the 'success' log group first and if we find the message_id, we are done, so there is only 1 call - assert boto_mock.filter_log_events.call_count == 1 - mock_call = str(boto_mock.filter_log_events.mock_calls[0]) - assert "Failure" not in mock_call - assert "succeed" in mock_call - assert "notification.messageId" in mock_call - - -def test_check_sms_failure(notify_api, mocker): - aws_cloudwatch_client.init_app(current_app) - boto_mock = mocker.patch.object(aws_cloudwatch_client, "_client", create=True) - boto_mock.filter_log_events.side_effect = side_effect - mocker.patch.dict( - "os.environ", - {"SES_DOMAIN_ARN": "arn:aws:ses:us-west-2:12345:identity/ses-xxx.xxx.xxx.xxx"}, - ) - message_id = "fail" - notification_id = "bbb" - created_at = utc_now() - with notify_api.app_context(): - aws_cloudwatch_client.check_sms(message_id, notification_id, created_at) - - # We check the 'success' log group and find nothing, so we then check the 'fail' log group -- two calls. - assert boto_mock.filter_log_events.call_count == 2 - mock_call = str(boto_mock.filter_log_events.mock_calls[1]) - assert "Failure" in mock_call - assert "fail" in mock_call - assert "notification.messageId" in mock_call - - def test_extract_account_number_gov_cloud(): domain_arn = "arn:aws-us-gov:ses:us-gov-west-1:12345:identity/ses-abc.xxx.xxx.xxx" actual_account_number = aws_cloudwatch_client._extract_account_number(domain_arn) From 2707f757c8e286c5465f4c4ee464f4c9cf3a0193 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 16 Dec 2024 08:29:34 -0800 Subject: [PATCH 09/50] get rid of duplicated code --- app/clients/cloudwatch/aws_cloudwatch.py | 33 +++++------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index 825d13c5e..f66ed0290 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -121,6 +121,11 @@ class AwsCloudwatchClient(Client): | sort @timestamp asc """ + delivered = self.run_log_insights_query(log_group_name, start, end, query) + failed = self.run_log_insights_query(log_group_name_failed, start, end, query) + return delivered + failed + + def run_log_insights_query(self, log_group_name, start, end, query): response = self._client.start_query( logGroupName=log_group_name, startTime=int(start.timestamp()), @@ -129,7 +134,7 @@ class AwsCloudwatchClient(Client): ) query_id = response["queryId"] while True: - result = client._client.get_query_results(queryId=query_id) + result = self._client.get_query_results(queryId=query_id) if result["status"] == "Complete": break sleep(1) @@ -138,28 +143,4 @@ class AwsCloudwatchClient(Client): for log in result["results"]: receipt = {field["field"]: field["value"] for field in log} delivery_receipts.append(receipt) - print(receipt) - - delivered = delivery_receipts - - response = client._client.start_query( - logGroupName=log_group_name_failed, - startTime=int(start.timestamp()), - endTime=int(end.timestamp()), - queryString=query, - ) - query_id = response["queryId"] - while True: - result = client._client.get_query_results(queryId=query_id) - if result["status"] == "Complete": - break - sleep(1) - - delivery_receipts = [] - for log in result["results"]: - receipt = {field["field"]: field["value"] for field in log} - delivery_receipts.append(receipt) - print(receipt) - - failed = delivery_receipts - return delivered + failed + return delivery_receipts From b5881409520f7ffb471e4131c9a8d81f86db063a Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 16 Dec 2024 11:22:49 -0800 Subject: [PATCH 10/50] change schedule to every 8 minutes --- app/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config.py b/app/config.py index d935b08b9..e7f4af14d 100644 --- a/app/config.py +++ b/app/config.py @@ -198,7 +198,7 @@ class Config(object): }, "process-delivery-receipts": { "task": "process-delivery-receipts", - "schedule": timedelta(minutes=1), + "schedule": timedelta(minutes=8), "options": {"queue": QueueNames.PERIODIC}, }, "expire-or-delete-invitations": { From 87d9ae5f33c9ab5635ebd0f8e0671b5625e34523 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 16 Dec 2024 14:16:35 -0800 Subject: [PATCH 11/50] remove dead code --- app/clients/cloudwatch/aws_cloudwatch.py | 44 +++--------------------- 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index f66ed0290..0d9f58680 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -7,7 +7,6 @@ from flask import current_app from app.clients import AWS_CLIENT_CONFIG, Client from app.cloudfoundry_config import cloud_config -from app.utils import utc_now class AwsCloudwatchClient(Client): @@ -47,44 +46,6 @@ class AwsCloudwatchClient(Client): def is_localstack(self): return self._is_localstack - def _get_log(self, my_filter, log_group_name, sent_at): - # Check all cloudwatch logs from the time the notification was sent (currently 5 minutes previously) until now - now = utc_now() - beginning = sent_at - next_token = None - all_log_events = [] - current_app.logger.info(f"START TIME {beginning} END TIME {now}") - # There has been a change somewhere and the time range we were previously using has become too - # narrow or wrong in some way, so events can't be found. For the time being, adjust by adding - # a buffer on each side of 12 hours. - TWELVE_HOURS = 12 * 60 * 60 * 1000 - while True: - if next_token: - response = self._client.filter_log_events( - logGroupName=log_group_name, - filterPattern=my_filter, - nextToken=next_token, - startTime=int(beginning.timestamp() * 1000) - TWELVE_HOURS, - endTime=int(now.timestamp() * 1000) + TWELVE_HOURS, - ) - else: - response = self._client.filter_log_events( - logGroupName=log_group_name, - filterPattern=my_filter, - startTime=int(beginning.timestamp() * 1000) - TWELVE_HOURS, - endTime=int(now.timestamp() * 1000) + TWELVE_HOURS, - ) - log_events = response.get("events", []) - all_log_events.extend(log_events) - if len(log_events) > 0: - # We found it - - break - next_token = response.get("nextToken") - if not next_token: - break - return all_log_events - def _extract_account_number(self, ses_domain_arn): account_number = ses_domain_arn.split(":") return account_number @@ -107,6 +68,11 @@ class AwsCloudwatchClient(Client): return None def check_delivery_receipts(self, start, end): + + result = self._client.describe_log_groups() + print(result) + return + region = cloud_config.sns_region account_number = self._extract_account_number(cloud_config.ses_domain_arn) From 6316e266eede9cbe1ee322441f1cc74c8947153e Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 16 Dec 2024 14:33:50 -0800 Subject: [PATCH 12/50] fix batch processing --- app/celery/scheduled_tasks.py | 5 ++++- app/clients/cloudwatch/aws_cloudwatch.py | 4 ---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/celery/scheduled_tasks.py b/app/celery/scheduled_tasks.py index d5aaa27a8..94bea4bea 100644 --- a/app/celery/scheduled_tasks.py +++ b/app/celery/scheduled_tasks.py @@ -244,4 +244,7 @@ def process_delivery_receipts(): start_time = utc_now() - timedelta(minutes=10) end_time = utc_now() receipts = cloudwatch.check_delivery_receipts(start_time, end_time) - dao_update_delivery_receipts(receipts) + batch_size = 100 + for i in range(0, len(receipts), batch_size): + batch = receipts[i : i + batch_size] + dao_update_delivery_receipts(batch) diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index 0d9f58680..cd510e9dd 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -69,10 +69,6 @@ class AwsCloudwatchClient(Client): def check_delivery_receipts(self, start, end): - result = self._client.describe_log_groups() - print(result) - return - region = cloud_config.sns_region account_number = self._extract_account_number(cloud_config.ses_domain_arn) From 7cf747d35af39452e5dcb7fe0ac9044b14a84ea7 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Tue, 17 Dec 2024 15:59:30 -0800 Subject: [PATCH 13/50] go back to filter log events --- app/celery/scheduled_tasks.py | 24 +++- app/clients/cloudwatch/aws_cloudwatch.py | 155 +++++++++++++++-------- app/dao/notifications_dao.py | 66 ++++++++-- 3 files changed, 178 insertions(+), 67 deletions(-) diff --git a/app/celery/scheduled_tasks.py b/app/celery/scheduled_tasks.py index 94bea4bea..b14d50ee5 100644 --- a/app/celery/scheduled_tasks.py +++ b/app/celery/scheduled_tasks.py @@ -35,7 +35,8 @@ from app.dao.users_dao import delete_codes_older_created_more_than_a_day_ago from app.enums import JobStatus, NotificationType from app.models import Job from app.notifications.process_notifications import send_notification_to_queue -from app.utils import utc_now +from app.utils import hilite, utc_now +from notifications_utils import aware_utcnow from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket MAX_NOTIFICATION_FAILS = 10000 @@ -239,12 +240,21 @@ def check_for_services_with_high_failure_rates_or_sending_to_tv_numbers(): @notify_celery.task(name="process-delivery-receipts") def process_delivery_receipts(): + print(hilite("ENTER PROCESS DELIVERY RECEIPTS")) cloudwatch = AwsCloudwatchClient() cloudwatch.init_app(current_app) - start_time = utc_now() - timedelta(minutes=10) - end_time = utc_now() - receipts = cloudwatch.check_delivery_receipts(start_time, end_time) + start_time = aware_utcnow() - timedelta(minutes=10) + end_time = aware_utcnow() + delivered_receipts, failed_receipts = cloudwatch.check_delivery_receipts( + start_time, end_time + ) + delivered_receipts = list(delivered_receipts) batch_size = 100 - for i in range(0, len(receipts), batch_size): - batch = receipts[i : i + batch_size] - dao_update_delivery_receipts(batch) + for i in range(0, len(delivered_receipts), batch_size): + batch = delivered_receipts[i : i + batch_size] + dao_update_delivery_receipts(batch, True) + failed_receipts = list(failed_receipts) + batch_size = 100 + for i in range(0, len(failed_receipts), batch_size): + batch = failed_receipts[i : i + batch_size] + dao_update_delivery_receipts(batch, False) diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index cd510e9dd..d84044943 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -1,12 +1,12 @@ +import json import os import re -from time import sleep from boto3 import client -from flask import current_app from app.clients import AWS_CLIENT_CONFIG, Client from app.cloudfoundry_config import cloud_config +from app.utils import hilite class AwsCloudwatchClient(Client): @@ -46,63 +46,116 @@ class AwsCloudwatchClient(Client): def is_localstack(self): return self._is_localstack + def _get_log(self, log_group_name, start, end): + # Check all cloudwatch logs from the time the notification was sent (currently 5 minutes previously) until now + print(hilite(f"START {start} END {end}")) + next_token = None + all_log_events = [] + + while True: + if next_token: + response = self._client.filter_log_events( + logGroupName=log_group_name, + nextToken=next_token, + startTime=int(start.timestamp() * 1000), + endTime=int(end.timestamp() * 1000), + ) + else: + response = self._client.filter_log_events( + logGroupName=log_group_name, + startTime=int(start.timestamp() * 1000), + endTime=int(end.timestamp() * 1000), + ) + log_events = response.get("events", []) + all_log_events.extend(log_events) + next_token = response.get("nextToken") + if not next_token: + break + return all_log_events + def _extract_account_number(self, ses_domain_arn): account_number = ses_domain_arn.split(":") return account_number - def warn_if_dev_is_opted_out(self, provider_response, notification_id): - if ( - "is opted out" in provider_response.lower() - or "has blocked sms" in provider_response.lower() - ): - if os.getenv("NOTIFY_ENVIRONMENT") in ["development", "test"]: - ansi_red = "\033[31m" - ansi_reset = "\033[0m" - logline = ( - ansi_red - + f"The phone number for notification_id {notification_id} is OPTED OUT. You need to opt back in" - + ansi_reset - ) - current_app.logger.warning(logline) - return logline - return None + def event_to_db_format(self, event): + # massage the data into the form the db expects. When we switch + # from filter_log_events to log insights this will be convenient + if isinstance(event, str): + event = json.loads(event) + + return { + "notification.messageId": event["notification"]["messageId"], + "status": event["status"], + "delivery.phoneCarrier": event["delivery"]["phoneCarrier"], + "delivery.providerResponse": event["delivery"]["providerResponse"], + "@timestamp": event["notification"]["timestamp"], + } + + # Here is an example of how to get the events with log insights + # def do_log_insights(): + # query = """ + # fields @timestamp, status, message, recipient + # | filter status = "DELIVERED" + # | sort @timestamp asc + # """ + # temp_client = boto3.client( + # "logs", + # region_name="us-gov-west-1", + # aws_access_key_id=AWS_ACCESS_KEY_ID, + # aws_secret_access_key=AWS_SECRET_ACCESS_KEY, + # config=AWS_CLIENT_CONFIG, + # ) + # start = utc_now() + # end = utc_now - timedelta(hours=1) + # response = temp_client.start_query( + # logGroupName = LOG_GROUP_NAME_DELIVERED, + # startTime = int(start.timestamp()), + # endTime= int(end.timestamp()), + # queryString = query + + # ) + # query_id = response['queryId'] + # while True: + # result = temp_client.get_query_results(queryId=query_id) + # if result['status'] == 'Complete': + # break + # time.sleep(1) + + # delivery_receipts = [] + # for log in result['results']: + # receipt = {field['field']: field['value'] for field in log} + # delivery_receipts.append(receipt) + # print(receipt) + + # print(len(delivery_receipts)) + + # In the long run we want to use Log Insights because it is more efficient + # that filter_log_events. But we are blocked by a permissions issue in the broker. + # So for now, use filter_log_events and grab all log_events over a 10 minute interval, + # and run this on a schedule. def check_delivery_receipts(self, start, end): - region = cloud_config.sns_region + # TODO this clumsy approach to getting the account number will be fixed as part of notify-api #258 account_number = self._extract_account_number(cloud_config.ses_domain_arn) - + delivered_event_set = set() log_group_name = f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber" - log_group_name_failed = ( - f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber/Failed" + print(hilite(f"LOG GROUP NAME {log_group_name}")) + all_delivered_events = self._get_log(log_group_name, start, end) + print(f"ALL DELIVEREDS {len(all_delivered_events)}") + + for event in all_delivered_events: + actual_event = self.event_to_db_format(event["message"]) + delivered_event_set.add(json.dumps(actual_event)) + + failed_event_set = set() + log_group_name = ( + f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber/Failure" ) + all_failed_events = self._get_log(log_group_name, start, end) + print(f"ALL FAILEDS {len(all_failed_events)}") + for event in all_failed_events: + actual_event = self.event_to_db_format(event["message"]) + failed_event_set.add(json.dumps(actual_event)) - query = """ - fields @timestamp, status, delivery.providerResponse, delivery.destination, - notification.messageId, delivery.phoneCarrier - | sort @timestamp asc - """ - - delivered = self.run_log_insights_query(log_group_name, start, end, query) - failed = self.run_log_insights_query(log_group_name_failed, start, end, query) - return delivered + failed - - def run_log_insights_query(self, log_group_name, start, end, query): - response = self._client.start_query( - logGroupName=log_group_name, - startTime=int(start.timestamp()), - endTime=int(end.timestamp()), - queryString=query, - ) - query_id = response["queryId"] - while True: - result = self._client.get_query_results(queryId=query_id) - if result["status"] == "Complete": - break - sleep(1) - - delivery_receipts = [] - for log in result["results"]: - receipt = {field["field"]: field["value"] for field in log} - delivery_receipts.append(receipt) - return delivery_receipts + return delivered_event_set, failed_event_set diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index d3871c59b..ef338dc4d 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -1,7 +1,20 @@ +import json from datetime import timedelta from flask import current_app -from sqlalchemy import asc, delete, desc, func, or_, select, text, union, update +from sqlalchemy import ( + TIMESTAMP, + asc, + cast, + delete, + desc, + func, + or_, + select, + text, + union, + update, +) from sqlalchemy.orm import joinedload from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.sql import functions @@ -709,8 +722,25 @@ def get_service_ids_with_notifications_on_date(notification_type, date): } -def dao_update_delivery_receipts(receipts): - id_to_status = {r["notification.messageId"]: r["status"] for r in receipts} +def dao_update_delivery_receipts(receipts, delivered): + new_receipts = [] + for r in receipts: + if isinstance(r, str): + r = json.loads(r) + new_receipts.append(r) + + receipts = new_receipts + print(receipts) + + statuses = {} + for r in receipts: + if r["status"].lower() == "success": + statuses[r["notification.messageId"]] = NotificationStatus.DELIVERED + else: + statuses[r["notification.messageId"]] = NotificationStatus.FAILED + + print(f"HERE ARE STATUSES {statuses}") + id_to_carrier = { r["notification.messageId"]: r["delivery.phoneCarrier"] for r in receipts } @@ -719,16 +749,34 @@ def dao_update_delivery_receipts(receipts): } id_to_timestamp = {r["notification.messageId"]: r["@timestamp"] for r in receipts} + status_to_update_with = NotificationStatus.DELIVERED + if not delivered: + status_to_update_with = NotificationStatus.FAILED stmt = ( update(Notification) - .where(Notification.c.message_id.in_(id_to_carrier.keys())) + .where(Notification.message_id.in_(id_to_carrier.keys())) .values( - carrier=case(id_to_carrier), - status=case(id_to_status), - notification_status=case(id_to_status), - sent_at=case(id_to_timestamp), - provider_response=case(id_to_provider_response), + carrier=case( + *[ + (Notification.message_id == key, value) + for key, value in id_to_carrier.items() + ] + ), + status=status_to_update_with, + sent_at=case( + *[ + (Notification.message_id == key, cast(value, TIMESTAMP)) + for key, value in id_to_timestamp.items() + ] + ), + provider_response=case( + *[ + (Notification.message_id == key, value) + for key, value in id_to_provider_response.items() + ] + ), ) ) + print(stmt) db.session.execute(stmt) db.session.commit() From 48c3cebc7235e7b6e5ec7b9956a843def891c39f Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Wed, 18 Dec 2024 07:44:24 -0800 Subject: [PATCH 14/50] clean up --- app/celery/scheduled_tasks.py | 62 ++++++++++++++++-------- app/clients/cloudwatch/aws_cloudwatch.py | 11 +++-- app/dao/notifications_dao.py | 11 ----- 3 files changed, 48 insertions(+), 36 deletions(-) diff --git a/app/celery/scheduled_tasks.py b/app/celery/scheduled_tasks.py index b14d50ee5..25e4d5034 100644 --- a/app/celery/scheduled_tasks.py +++ b/app/celery/scheduled_tasks.py @@ -35,7 +35,7 @@ from app.dao.users_dao import delete_codes_older_created_more_than_a_day_ago from app.enums import JobStatus, NotificationType from app.models import Job from app.notifications.process_notifications import send_notification_to_queue -from app.utils import hilite, utc_now +from app.utils import utc_now from notifications_utils import aware_utcnow from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket @@ -238,23 +238,43 @@ def check_for_services_with_high_failure_rates_or_sending_to_tv_numbers(): zendesk_client.send_ticket_to_zendesk(ticket) -@notify_celery.task(name="process-delivery-receipts") -def process_delivery_receipts(): - print(hilite("ENTER PROCESS DELIVERY RECEIPTS")) - cloudwatch = AwsCloudwatchClient() - cloudwatch.init_app(current_app) - start_time = aware_utcnow() - timedelta(minutes=10) - end_time = aware_utcnow() - delivered_receipts, failed_receipts = cloudwatch.check_delivery_receipts( - start_time, end_time - ) - delivered_receipts = list(delivered_receipts) - batch_size = 100 - for i in range(0, len(delivered_receipts), batch_size): - batch = delivered_receipts[i : i + batch_size] - dao_update_delivery_receipts(batch, True) - failed_receipts = list(failed_receipts) - batch_size = 100 - for i in range(0, len(failed_receipts), batch_size): - batch = failed_receipts[i : i + batch_size] - dao_update_delivery_receipts(batch, False) +@notify_celery.task( + bind=True, max_retries=7, default_retry_delay=3600, name="process-delivery-receipts" +) +def process_delivery_receipts(self): + """ + Every eight minutes or so (see config.py) we run this task, which searches the last ten + minutes of logs for delivery receipts and batch updates the db with the results. The overlap + is intentional. We don't mind re-updating things, it is better than losing data. + + We also set this to retry with exponential backoff in the case of failure. The only way this would + fail is if, for example the db went down, or redis filled causing the app to stop processing. But if + it does fail, we need to go back over at some point when things are running again and process those results. + """ + try: + batch_size = 200 # in theory with postgresql this could be 10k to 20k? + + cloudwatch = AwsCloudwatchClient() + cloudwatch.init_app(current_app) + start_time = aware_utcnow() - timedelta(minutes=10) + end_time = aware_utcnow() + delivered_receipts, failed_receipts = cloudwatch.check_delivery_receipts( + start_time, end_time + ) + delivered_receipts = list(delivered_receipts) + for i in range(0, len(delivered_receipts), batch_size): + batch = delivered_receipts[i : i + batch_size] + dao_update_delivery_receipts(batch, True) + failed_receipts = list(failed_receipts) + for i in range(0, len(failed_receipts), batch_size): + batch = failed_receipts[i : i + batch_size] + dao_update_delivery_receipts(batch, False) + except Exception as ex: + retry_count = self.request.retries + wait_time = 3600 * 2**retry_count + try: + raise self.retry(ex=ex, countdown=wait_time) + except self.MaxRetriesExceededError: + current_app.logger.error( + "Failed process delivery receipts after max retries" + ) diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index d84044943..297052741 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -3,6 +3,7 @@ import os import re from boto3 import client +from flask import current_app from app.clients import AWS_CLIENT_CONFIG, Client from app.cloudfoundry_config import cloud_config @@ -136,13 +137,13 @@ class AwsCloudwatchClient(Client): # and run this on a schedule. def check_delivery_receipts(self, start, end): region = cloud_config.sns_region - # TODO this clumsy approach to getting the account number will be fixed as part of notify-api #258 account_number = self._extract_account_number(cloud_config.ses_domain_arn) delivered_event_set = set() log_group_name = f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber" - print(hilite(f"LOG GROUP NAME {log_group_name}")) all_delivered_events = self._get_log(log_group_name, start, end) - print(f"ALL DELIVEREDS {len(all_delivered_events)}") + current_app.logger.info( + f"Delivered count {len(all_delivered_events)} over range {start} to {end}" + ) for event in all_delivered_events: actual_event = self.event_to_db_format(event["message"]) @@ -153,7 +154,9 @@ class AwsCloudwatchClient(Client): f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber/Failure" ) all_failed_events = self._get_log(log_group_name, start, end) - print(f"ALL FAILEDS {len(all_failed_events)}") + current_app.logger.info( + f"Failed count {len(all_delivered_events)} over range {start} to {end}" + ) for event in all_failed_events: actual_event = self.event_to_db_format(event["message"]) failed_event_set.add(json.dumps(actual_event)) diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index ef338dc4d..6ce97b801 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -730,16 +730,6 @@ def dao_update_delivery_receipts(receipts, delivered): new_receipts.append(r) receipts = new_receipts - print(receipts) - - statuses = {} - for r in receipts: - if r["status"].lower() == "success": - statuses[r["notification.messageId"]] = NotificationStatus.DELIVERED - else: - statuses[r["notification.messageId"]] = NotificationStatus.FAILED - - print(f"HERE ARE STATUSES {statuses}") id_to_carrier = { r["notification.messageId"]: r["delivery.phoneCarrier"] for r in receipts @@ -777,6 +767,5 @@ def dao_update_delivery_receipts(receipts, delivered): ), ) ) - print(stmt) db.session.execute(stmt) db.session.commit() From 2c2ee415426e6764fb69ddbfcf745f3ccb97cead Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Wed, 18 Dec 2024 07:55:55 -0800 Subject: [PATCH 15/50] add back opt out --- app/clients/cloudwatch/aws_cloudwatch.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index 297052741..0184a4d90 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -74,6 +74,23 @@ class AwsCloudwatchClient(Client): break return all_log_events + def warn_if_dev_is_opted_out(self, provider_response, notification_id): + if ( + "is opted out" in provider_response.lower() + or "has blocked sms" in provider_response.lower() + ): + if os.getenv("NOTIFY_ENVIRONMENT") in ["development", "test"]: + ansi_red = "\033[31m" + ansi_reset = "\033[0m" + logline = ( + ansi_red + + f"The phone number for notification_id {notification_id} is OPTED OUT. You need to opt back in" + + ansi_reset + ) + current_app.logger.warning(logline) + return logline + return None + def _extract_account_number(self, ses_domain_arn): account_number = ses_domain_arn.split(":") return account_number From 9d9ce137c4bb6fca3d368873254b89ebf17383b6 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Wed, 18 Dec 2024 08:12:44 -0800 Subject: [PATCH 16/50] reduce code --- app/clients/cloudwatch/aws_cloudwatch.py | 29 +- loadtest_10k.csv | 10001 +++++++++++++++++++++ 2 files changed, 10011 insertions(+), 19 deletions(-) create mode 100644 loadtest_10k.csv diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index 0184a4d90..5c34cce4d 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -155,27 +155,18 @@ class AwsCloudwatchClient(Client): def check_delivery_receipts(self, start, end): region = cloud_config.sns_region account_number = self._extract_account_number(cloud_config.ses_domain_arn) - delivered_event_set = set() log_group_name = f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber" - all_delivered_events = self._get_log(log_group_name, start, end) - current_app.logger.info( - f"Delivered count {len(all_delivered_events)} over range {start} to {end}" - ) - - for event in all_delivered_events: - actual_event = self.event_to_db_format(event["message"]) - delivered_event_set.add(json.dumps(actual_event)) - - failed_event_set = set() + delivered_event_set = self._get_receipts(log_group_name, start, end) log_group_name = ( f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber/Failure" ) - all_failed_events = self._get_log(log_group_name, start, end) - current_app.logger.info( - f"Failed count {len(all_delivered_events)} over range {start} to {end}" - ) - for event in all_failed_events: - actual_event = self.event_to_db_format(event["message"]) - failed_event_set.add(json.dumps(actual_event)) - + failed_event_set = self._get_receipts(log_group_name, start, end) return delivered_event_set, failed_event_set + + def _get_receipts(self, log_group_name, start, end): + event_set = set() + all_events = self._get_log(log_group_name, start, end) + for event in all_events: + actual_event = self.event_to_db_format(event["message"]) + event_set.add(json.dumps(actual_event)) + return event_set diff --git a/loadtest_10k.csv b/loadtest_10k.csv new file mode 100644 index 000000000..86b1b5ac1 --- /dev/null +++ b/loadtest_10k.csv @@ -0,0 +1,10001 @@ +phone number ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 ++14254147755 ++14254147167 From 4bd97586b426876b9b51f723e072a08c7b796c80 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Wed, 18 Dec 2024 09:06:06 -0800 Subject: [PATCH 17/50] add test --- .../notification_dao/test_notification_dao.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/app/dao/notification_dao/test_notification_dao.py b/tests/app/dao/notification_dao/test_notification_dao.py index e2ac10032..6e09f182a 100644 --- a/tests/app/dao/notification_dao/test_notification_dao.py +++ b/tests/app/dao/notification_dao/test_notification_dao.py @@ -1,6 +1,7 @@ import uuid from datetime import date, datetime, timedelta from functools import partial +from unittest.mock import MagicMock, patch import pytest from freezegun import freeze_time @@ -19,6 +20,7 @@ from app.dao.notifications_dao import ( dao_get_notification_history_by_reference, dao_get_notifications_by_recipient_or_reference, dao_timeout_notifications, + dao_update_delivery_receipts, dao_update_notification, dao_update_notifications_by_reference, get_notification_by_id, @@ -1996,6 +1998,34 @@ def test_notifications_not_yet_sent_return_no_rows(sample_service, notification_ assert len(results) == 0 +def test_update_delivery_receipts(mocker): + mock_session = mocker.patch("app.dao.notifications_dao.db.session") + receipts = [ + '{"notification.messageId": "msg1", "delivery.phoneCarrier": "carrier1", "delivery.providerResponse": "resp1", "@timestamp": "2024-01-01T12:00:00"}', # noqa + '{"notification.messageId": "msg2", "delivery.phoneCarrier": "carrier2", "delivery.providerResponse": "resp2", "@timestamp": "2024-01-01T13:00:00"}', # noqa + ] + delivered = True + mock_update = MagicMock() + mock_where = MagicMock() + mock_values = MagicMock() + mock_update.where.return_value = mock_where + mock_where.values.return_value = mock_values + + mock_session.execute.return_value = None + with patch("app.dao.notifications_dao.update", return_value=mock_update): + dao_update_delivery_receipts(receipts, delivered) + mock_update.where.assert_called_once() + mock_where.values.assert_called_once() + mock_session.execute.assert_called_once_with(mock_values) + mock_session.commit.assert_called_once() + + args, kwargs = mock_where.values.call_args + assert "carrier" in kwargs + assert "status" in kwargs + assert "sent_at" in kwargs + assert "provider_response" in kwargs + + @pytest.mark.parametrize( "created_at_utc,date_to_check,expected_count", [ From 9dc7cfeb2ff41a81bc3a1058f726784bfd48833c Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Fri, 20 Dec 2024 07:33:28 -0800 Subject: [PATCH 18/50] code review feedback --- app/celery/scheduled_tasks.py | 2 +- app/clients/cloudwatch/aws_cloudwatch.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/celery/scheduled_tasks.py b/app/celery/scheduled_tasks.py index 25e4d5034..2c31c3f4e 100644 --- a/app/celery/scheduled_tasks.py +++ b/app/celery/scheduled_tasks.py @@ -252,7 +252,7 @@ def process_delivery_receipts(self): it does fail, we need to go back over at some point when things are running again and process those results. """ try: - batch_size = 200 # in theory with postgresql this could be 10k to 20k? + batch_size = 1000 # in theory with postgresql this could be 10k to 20k? cloudwatch = AwsCloudwatchClient() cloudwatch.init_app(current_app) diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index 5c34cce4d..c83fd6313 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -7,7 +7,6 @@ from flask import current_app from app.clients import AWS_CLIENT_CONFIG, Client from app.cloudfoundry_config import cloud_config -from app.utils import hilite class AwsCloudwatchClient(Client): @@ -49,7 +48,6 @@ class AwsCloudwatchClient(Client): def _get_log(self, log_group_name, start, end): # Check all cloudwatch logs from the time the notification was sent (currently 5 minutes previously) until now - print(hilite(f"START {start} END {end}")) next_token = None all_log_events = [] From 1bac8a82a0280cc644cb26e505131071c3892aa1 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Fri, 20 Dec 2024 09:58:03 -0800 Subject: [PATCH 19/50] introduce gaps in message sends --- app/celery/tasks.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/celery/tasks.py b/app/celery/tasks.py index c21f4e65c..77c8fca80 100644 --- a/app/celery/tasks.py +++ b/app/celery/tasks.py @@ -1,4 +1,5 @@ import json +from time import sleep from celery.signals import task_postrun from flask import current_app @@ -75,8 +76,15 @@ def process_job(job_id, sender_id=None): ) ) + # notify-api-1495 we are going to sleep periodically to give other + # jobs running at the same time a chance to get some of their messages + # sent. Sleep for 5 minutes after every 500 sends + count = 0 for row in recipient_csv.get_rows(): process_row(row, template, job, service, sender_id=sender_id) + count = count + 1 + if count % 500 == 0: + sleep(5 * 60) # End point/Exit point for message send flow. job_complete(job, start=start) From 4c5741a82e586b34bded8c19c2cab2114066e100 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 23 Dec 2024 08:03:12 -0800 Subject: [PATCH 20/50] change timing a bit --- app/celery/tasks.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/celery/tasks.py b/app/celery/tasks.py index 77c8fca80..5122688a9 100644 --- a/app/celery/tasks.py +++ b/app/celery/tasks.py @@ -78,13 +78,14 @@ def process_job(job_id, sender_id=None): # notify-api-1495 we are going to sleep periodically to give other # jobs running at the same time a chance to get some of their messages - # sent. Sleep for 5 minutes after every 500 sends + # sent. Sleep for 40 seconds after every 100 sends, which gives us throughput + # of about 9000 per hour and would keep the queue clear assuming only one sender. count = 0 for row in recipient_csv.get_rows(): process_row(row, template, job, service, sender_id=sender_id) count = count + 1 - if count % 500 == 0: - sleep(5 * 60) + if count % 100 == 0: + sleep(40) # End point/Exit point for message send flow. job_complete(job, start=start) From f4442e1ef58601c73f807bd3592f748957e0b378 Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Mon, 23 Dec 2024 11:37:36 -0700 Subject: [PATCH 21/50] Update jinja2 to 3.1.5 to address vulnerability --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index debdf9ed9..70b7733e4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -1910,13 +1910,13 @@ trio = ["async_generator", "trio"] [[package]] name = "jinja2" -version = "3.1.4" +version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, + {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, + {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, ] [package.dependencies] @@ -4947,4 +4947,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "cf18ae74630e47eec18cc6c5fea9e554476809d20589d82c54a8d761bb2c3de0" +content-hash = "fec0ba8767be575fa1818a6c396c88d2d447a7a94609b8e8680f4786e257b3d5" diff --git a/pyproject.toml b/pyproject.toml index 99858c09e..82dbab9b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ six = "^1.16.0" urllib3 = "^2.2.2" webencodings = "^0.5.1" itsdangerous = "^2.2.0" -jinja2 = "^3.1.4" +jinja2 = "3.1.5" redis = "^5.0.8" requests = "^2.32.3" From e6e336cdf72d75040ba1e5456b5a711a1d188ad8 Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Mon, 23 Dec 2024 11:42:39 -0700 Subject: [PATCH 22/50] Fix dependency syntax --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 82dbab9b2..dc568d89e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ six = "^1.16.0" urllib3 = "^2.2.2" webencodings = "^0.5.1" itsdangerous = "^2.2.0" -jinja2 = "3.1.5" +jinja2 = "^3.1.5" redis = "^5.0.8" requests = "^2.32.3" From 90ce2b2a6598b6b6a4e414f20eeb84dbb9922a91 Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Mon, 23 Dec 2024 11:46:14 -0700 Subject: [PATCH 23/50] Poetry lock --no-update command --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 70b7733e4..e0096a7de 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4947,4 +4947,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "fec0ba8767be575fa1818a6c396c88d2d447a7a94609b8e8680f4786e257b3d5" +content-hash = "271d8e0f25856f45970e5a9cc3b8871a01b732226ab3ed68ea426912b5117fcf" From 0ab56800299975c89683402d6ce2eb513550044b Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 23 Dec 2024 10:52:46 -0800 Subject: [PATCH 24/50] add expiration to redis --- app/service_invite/rest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/service_invite/rest.py b/app/service_invite/rest.py index 38bc1c404..e375b93a5 100644 --- a/app/service_invite/rest.py +++ b/app/service_invite/rest.py @@ -54,7 +54,7 @@ def _create_service_invite(invited_user, nonce, state): data["invited_user_email"] = invited_user.email_address invite_redis_key = f"invite-data-{unquote(state)}" - redis_store.set(invite_redis_key, get_user_data_url_safe(data)) + redis_store.set(invite_redis_key, get_user_data_url_safe(data), ex=2 * 24 * 60 * 60) url = os.environ["LOGIN_DOT_GOV_REGISTRATION_URL"] From 85c5268eaedf9c15037ba96baf289f04ee6c16b2 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 23 Dec 2024 13:51:21 -0800 Subject: [PATCH 25/50] change to send 3 messages per second --- app/celery/tasks.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/celery/tasks.py b/app/celery/tasks.py index 5122688a9..b2ad5abac 100644 --- a/app/celery/tasks.py +++ b/app/celery/tasks.py @@ -78,14 +78,16 @@ def process_job(job_id, sender_id=None): # notify-api-1495 we are going to sleep periodically to give other # jobs running at the same time a chance to get some of their messages - # sent. Sleep for 40 seconds after every 100 sends, which gives us throughput - # of about 9000 per hour and would keep the queue clear assuming only one sender. + # sent. Sleep for 1 second after every 3 sends, which gives us throughput + # of about 3600*3 per hour and would keep the queue clear assuming only one sender. + # It will also hopefully eliminate throttling when we send messages which we are + # currently seeing. count = 0 for row in recipient_csv.get_rows(): process_row(row, template, job, service, sender_id=sender_id) count = count + 1 - if count % 100 == 0: - sleep(40) + if count % 3 == 0: + sleep(1) # End point/Exit point for message send flow. job_complete(job, start=start) From 05755cb3178c8e1ff0c6a1f27d3a6434d5a3b406 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 23 Dec 2024 14:43:55 -0800 Subject: [PATCH 26/50] fix json structure bug --- app/clients/cloudwatch/aws_cloudwatch.py | 36 ++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index c83fd6313..43bedbb35 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -100,12 +100,20 @@ class AwsCloudwatchClient(Client): if isinstance(event, str): event = json.loads(event) + # Don't trust AWS to always send the same JSON structure back + # However, if we don't get message_id and status we might as well blow up + # because it's pointless to continue + phone_carrier = self._aws_value_or_default(event, "delivery", "phoneCarrier") + provider_response = self._aws_value_or_default( + event, "delivery", "providerResponse" + ) + my_timestamp = self._aws_value_or_default(event, "notification", "timestamp") return { "notification.messageId": event["notification"]["messageId"], "status": event["status"], - "delivery.phoneCarrier": event["delivery"]["phoneCarrier"], - "delivery.providerResponse": event["delivery"]["providerResponse"], - "@timestamp": event["notification"]["timestamp"], + "delivery.phoneCarrier": phone_carrier, + "delivery.providerResponse": provider_response, + "@timestamp": my_timestamp, } # Here is an example of how to get the events with log insights @@ -155,16 +163,34 @@ class AwsCloudwatchClient(Client): account_number = self._extract_account_number(cloud_config.ses_domain_arn) log_group_name = f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber" delivered_event_set = self._get_receipts(log_group_name, start, end) + current_app.logger.info( + (f"Delivered message count: {len(delivered_event_set)}") + ) log_group_name = ( f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber/Failure" ) failed_event_set = self._get_receipts(log_group_name, start, end) + current_app.logger.info((f"Failed message count: {len(failed_event_set)}")) + return delivered_event_set, failed_event_set def _get_receipts(self, log_group_name, start, end): event_set = set() all_events = self._get_log(log_group_name, start, end) for event in all_events: - actual_event = self.event_to_db_format(event["message"]) - event_set.add(json.dumps(actual_event)) + try: + actual_event = self.event_to_db_format(event["message"]) + event_set.add(json.dumps(actual_event)) + except Exception: + current_app.logger.exception( + f"Could not format delivery receipt {event} for db insert" + ) return event_set + + def _aws_value_or_default(self, event, top_level, second_level): + if event.get(top_level) is None or event[top_level].get(second_level) is None: + my_var = "" + else: + my_var = event[top_level][second_level] + + return my_var From 46ec1aafa88cb800f3f69cef1e72f59a15caeb61 Mon Sep 17 00:00:00 2001 From: Cliff Hill Date: Mon, 23 Dec 2024 11:17:51 -0500 Subject: [PATCH 27/50] Getting the IntegrityErrors to be OK if the data already exists in the DB. Signed-off-by: Cliff Hill --- app/celery/tasks.py | 46 ++++++++++++---------- app/dao/notifications_dao.py | 10 +++-- app/notifications/process_notifications.py | 5 +++ 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/app/celery/tasks.py b/app/celery/tasks.py index c21f4e65c..72d23531f 100644 --- a/app/celery/tasks.py +++ b/app/celery/tasks.py @@ -23,6 +23,7 @@ from app.enums import JobStatus, KeyType, NotificationType from app.errors import TotalRequestsError from app.notifications.process_notifications import ( get_notification, + notification_exists, persist_notification, ) from app.notifications.validators import check_service_over_total_message_limit @@ -218,22 +219,28 @@ def save_sms(self, service_id, notification_id, encrypted_notification, sender_i job = dao_get_job_by_id(job_id) created_by_id = job.created_by_id - saved_notification = persist_notification( - template_id=notification["template"], - template_version=notification["template_version"], - recipient=notification["to"], - service=service, - personalisation=notification.get("personalisation"), - notification_type=NotificationType.SMS, - api_key_id=None, - key_type=KeyType.NORMAL, - created_at=utc_now(), - created_by_id=created_by_id, - job_id=notification.get("job", None), - job_row_number=notification.get("row_number", None), - notification_id=notification_id, - reply_to_text=reply_to_text, - ) + try: + saved_notification = persist_notification( + template_id=notification["template"], + template_version=notification["template_version"], + recipient=notification["to"], + service=service, + personalisation=notification.get("personalisation"), + notification_type=NotificationType.SMS, + api_key_id=None, + key_type=KeyType.NORMAL, + created_at=utc_now(), + created_by_id=created_by_id, + job_id=notification.get("job", None), + job_row_number=notification.get("row_number", None), + notification_id=notification_id, + reply_to_text=reply_to_text, + ) + except IntegrityError as e: + if notification_exists(notification_id): + saved_notification = get_notification(notification_id) + else: + raise # Kick off sns process in provider_tasks.py sn = saved_notification @@ -247,11 +254,8 @@ def save_sms(self, service_id, notification_id, encrypted_notification, sender_i ) current_app.logger.debug( - "SMS {} created at {} for job {}".format( - saved_notification.id, - saved_notification.created_at, - notification.get("job", None), - ) + f"SMS {saved_notification.id} created at {saved_notification.created_at} " + f"for job {notification.get('job', None)}" ) except SQLAlchemyError as e: diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index 6ce97b801..b9c3118fa 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -65,6 +65,12 @@ def dao_get_last_date_template_was_used(template_id, service_id): return last_date +def dao_notification_exists(notification_id) -> bool: + stmt = select(Notification).where(Notification.id == notification_id) + result = db.session.execute(stmt).scalar() + return result is not None + + @autocommit def dao_create_notification(notification): if not notification.id: @@ -86,9 +92,7 @@ def dao_create_notification(notification): notification.normalised_to = "1" # notify-api-1454 insert only if it doesn't exist - stmt = select(Notification).where(Notification.id == notification.id) - result = db.session.execute(stmt).scalar() - if result is None: + if not dao_notification_exists(notification.id): db.session.add(notification) diff --git a/app/notifications/process_notifications.py b/app/notifications/process_notifications.py index 8568b8894..5f1c6676d 100644 --- a/app/notifications/process_notifications.py +++ b/app/notifications/process_notifications.py @@ -8,6 +8,7 @@ from app.config import QueueNames from app.dao.notifications_dao import ( dao_create_notification, dao_delete_notifications_by_id, + dao_notification_exists, get_notification_by_id, ) from app.enums import KeyType, NotificationStatus, NotificationType @@ -153,6 +154,10 @@ def persist_notification( return notification +def notification_exists(notification_id): + return dao_notification_exists(notification_id) + + def send_notification_to_queue_detached( key_type, notification_type, notification_id, queue=None ): From 122195d01040965adf02d8e095c682c03c66614f Mon Sep 17 00:00:00 2001 From: Cliff Hill Date: Thu, 26 Dec 2024 08:35:58 -0500 Subject: [PATCH 28/50] flake8 stuff Signed-off-by: Cliff Hill --- app/celery/scheduled_tasks.py | 4 +--- app/celery/tasks.py | 13 +++++-------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/app/celery/scheduled_tasks.py b/app/celery/scheduled_tasks.py index 2c31c3f4e..bb5d80f6f 100644 --- a/app/celery/scheduled_tasks.py +++ b/app/celery/scheduled_tasks.py @@ -174,9 +174,7 @@ def check_for_missing_rows_in_completed_jobs(): for row_to_process in missing_rows: row = recipient_csv[row_to_process.missing_row] current_app.logger.info( - "Processing missing row: {} for job: {}".format( - row_to_process.missing_row, job.id - ) + f"Processing missing row: {row_to_process.missing_row} for job: {job.id}" ) process_row(row, template, job, job.service, sender_id=sender_id) diff --git a/app/celery/tasks.py b/app/celery/tasks.py index 72d23531f..87579f560 100644 --- a/app/celery/tasks.py +++ b/app/celery/tasks.py @@ -39,9 +39,7 @@ def process_job(job_id, sender_id=None): start = utc_now() job = dao_get_job_by_id(job_id) current_app.logger.info( - "Starting process-job task for job id {} with status: {}".format( - job_id, job.job_status - ) + f"Starting process-job task for job id {job_id} with status: {job.job_status}" ) if job.job_status != JobStatus.PENDING: @@ -57,7 +55,7 @@ def process_job(job_id, sender_id=None): job.job_status = JobStatus.CANCELLED dao_update_job(job) current_app.logger.warning( - "Job {} has been cancelled, service {} is inactive".format( + f"Job {job_id} has been cancelled, service {service.id} is inactive".format( job_id, service.id ) ) @@ -71,9 +69,7 @@ def process_job(job_id, sender_id=None): ) current_app.logger.info( - "Starting job {} processing {} notifications".format( - job_id, job.notification_count - ) + f"Starting job {job_id} processing {job.notification_count} notifications" ) for row in recipient_csv.get_rows(): @@ -236,9 +232,10 @@ def save_sms(self, service_id, notification_id, encrypted_notification, sender_i notification_id=notification_id, reply_to_text=reply_to_text, ) - except IntegrityError as e: + except IntegrityError: if notification_exists(notification_id): saved_notification = get_notification(notification_id) + else: raise From 8579efccbcd695bfae82be1abc697d5cd897cef9 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 26 Dec 2024 08:50:26 -0800 Subject: [PATCH 29/50] add placehodler for new test --- tests/app/clients/test_aws_cloudwatch.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/app/clients/test_aws_cloudwatch.py b/tests/app/clients/test_aws_cloudwatch.py index fce0e150c..bb3d36cb0 100644 --- a/tests/app/clients/test_aws_cloudwatch.py +++ b/tests/app/clients/test_aws_cloudwatch.py @@ -87,3 +87,7 @@ def test_extract_account_number_gov_staging(): assert len(actual_account_number) == 6 expected_account_number = "12345" assert actual_account_number[4] == expected_account_number + + +def test_check_delivery_receipts(): + pass From 9f3aec755f1a2ed858a4378401cfbc4f5f5870ed Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 26 Dec 2024 09:23:00 -0800 Subject: [PATCH 30/50] add tests --- tests/app/clients/test_aws_cloudwatch.py | 60 ++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/app/clients/test_aws_cloudwatch.py b/tests/app/clients/test_aws_cloudwatch.py index bb3d36cb0..7a0379454 100644 --- a/tests/app/clients/test_aws_cloudwatch.py +++ b/tests/app/clients/test_aws_cloudwatch.py @@ -1,3 +1,5 @@ +import json + import pytest from flask import current_app @@ -91,3 +93,61 @@ def test_extract_account_number_gov_staging(): def test_check_delivery_receipts(): pass + + +def test_aws_value_or_default(): + event = { + "delivery": {"phoneCarrier": "AT&T"}, + "notification": {"timestamp": "2024-01-01T:12:00:00Z"}, + } + assert ( + aws_cloudwatch_client._aws_value_or_default(event, "delivery", "phoneCarrier") + == "AT&T" + ) + assert ( + aws_cloudwatch_client._aws_value_or_default( + event, "delivery", "providerResponse" + ) + == "" + ) + assert ( + aws_cloudwatch_client._aws_value_or_default(event, "notification", "timestamp") + == "2024-01-01T:12:00:00Z" + ) + assert ( + aws_cloudwatch_client._aws_value_or_default(event, "nonexistent", "field") == "" + ) + + +def test_event_to_db_format_with_missing_fields(): + event = { + "notification": {"messageId": "12345"}, + "status": "UNKNOWN", + "delivery": {}, + } + result = aws_cloudwatch_client.event_to_db_format(event) + assert result == { + "notification.messageId": "12345", + "status": "UNKNOWN", + "delivery.phoneCarrier": "", + "delivery.providerResponse": "", + "@timestamp": "", + } + + +def test_event_to_db_format_with_string_input(): + event = json.dumps( + { + "notification": {"messageId": "67890", "timestamp": "2024-01-01T14:00:00Z"}, + "status": "FAILED", + "delivery": {"phoneCarrier": "Verizon", "providerResponse": "Error"}, + } + ) + result = aws_cloudwatch_client.event_to_db_format(event) + assert result == { + "notification.messageId": "67890", + "status": "FAILED", + "delivery.phoneCarrier": "Verizon", + "delivery.providerResponse": "Error", + "@timestamp": "2024-01-01T14:00:00Z", + } From f1e8ff3328e3868becc072eeaa3c907af63006d4 Mon Sep 17 00:00:00 2001 From: Cliff Hill Date: Fri, 27 Dec 2024 12:17:44 -0500 Subject: [PATCH 31/50] Fixing IntegrityError problems, again. Signed-off-by: Cliff Hill --- app/celery/tasks.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app/celery/tasks.py b/app/celery/tasks.py index cbb9e22b6..0e36d6c84 100644 --- a/app/celery/tasks.py +++ b/app/celery/tasks.py @@ -214,9 +214,7 @@ def save_sms(self, service_id, notification_id, encrypted_notification, sender_i f"service not allowed to send for job_id {notification.get('job', None)}, aborting" ) ) - current_app.logger.debug( - "SMS {} failed as restricted service".format(notification_id) - ) + current_app.logger.debug(f"SMS {notification_id} failed as restricted service") return try: @@ -244,11 +242,12 @@ def save_sms(self, service_id, notification_id, encrypted_notification, sender_i reply_to_text=reply_to_text, ) except IntegrityError: - if notification_exists(notification_id): - saved_notification = get_notification(notification_id) - - else: - raise + current_app.logger.warning( + f"{NotificationType.SMS}: {notification_id} already exists." + ) + # If we don't have the return statement here, we will fall through and end + # up retrying because IntegrityError is a subclass of SQLAlchemyError + return # Kick off sns process in provider_tasks.py sn = saved_notification From e2413bac76dbda6f41059f0cf01580d37129078e Mon Sep 17 00:00:00 2001 From: Cliff Hill Date: Fri, 27 Dec 2024 12:21:43 -0500 Subject: [PATCH 32/50] Flake8 fixes. Signed-off-by: Cliff Hill --- app/celery/tasks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/celery/tasks.py b/app/celery/tasks.py index 0e36d6c84..3743aa294 100644 --- a/app/celery/tasks.py +++ b/app/celery/tasks.py @@ -24,7 +24,6 @@ from app.enums import JobStatus, KeyType, NotificationType from app.errors import TotalRequestsError from app.notifications.process_notifications import ( get_notification, - notification_exists, persist_notification, ) from app.notifications.validators import check_service_over_total_message_limit From 85a55dbaed9cbfcf5b5a72b6034f063409fc6990 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Fri, 27 Dec 2024 10:13:28 -0800 Subject: [PATCH 33/50] change time range for incomplete jobs --- app/celery/scheduled_tasks.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/celery/scheduled_tasks.py b/app/celery/scheduled_tasks.py index bb5d80f6f..3a3fa696e 100644 --- a/app/celery/scheduled_tasks.py +++ b/app/celery/scheduled_tasks.py @@ -100,27 +100,29 @@ def check_job_status(): select from jobs where job_status == 'in progress' - and processing started between 30 and 35 minutes ago + and processing started some time ago OR where the job_status == 'pending' - and the job scheduled_for timestamp is between 30 and 35 minutes ago. + and the job scheduled_for timestamp is some time ago. if any results then update the job_status to 'error' process the rows in the csv that are missing (in another task) just do the check here. """ - thirty_minutes_ago = utc_now() - timedelta(minutes=30) - thirty_five_minutes_ago = utc_now() - timedelta(minutes=35) + START_MINUTES = 245 + END_MINUTES = 240 + end_minutes_ago = utc_now() - timedelta(minutes=END_MINUTES) + start_minutes_ago = utc_now() - timedelta(minutes=START_MINUTES) incomplete_in_progress_jobs = Job.query.filter( Job.job_status == JobStatus.IN_PROGRESS, - between(Job.processing_started, thirty_five_minutes_ago, thirty_minutes_ago), + between(Job.processing_started, start_minutes_ago, end_minutes_ago), ) incomplete_pending_jobs = Job.query.filter( Job.job_status == JobStatus.PENDING, Job.scheduled_for.isnot(None), - between(Job.scheduled_for, thirty_five_minutes_ago, thirty_minutes_ago), + between(Job.scheduled_for, start_minutes_ago, end_minutes_ago), ) - jobs_not_complete_after_30_minutes = ( + jobs_not_complete_after_allotted_time = ( incomplete_in_progress_jobs.union(incomplete_pending_jobs) .order_by(Job.processing_started, Job.scheduled_for) .all() @@ -129,7 +131,7 @@ def check_job_status(): # temporarily mark them as ERROR so that they don't get picked up by future check_job_status tasks # if they haven't been re-processed in time. job_ids = [] - for job in jobs_not_complete_after_30_minutes: + for job in jobs_not_complete_after_allotted_time: job.job_status = JobStatus.ERROR dao_update_job(job) job_ids.append(str(job.id)) From 152496fc91d83f56010afb9438d03ab404c8285e Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Fri, 27 Dec 2024 10:28:08 -0800 Subject: [PATCH 34/50] fix tests --- tests/app/celery/test_scheduled_tasks.py | 70 ++++++++++++++---------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/tests/app/celery/test_scheduled_tasks.py b/tests/app/celery/test_scheduled_tasks.py index ae9ea571d..f436aacf2 100644 --- a/tests/app/celery/test_scheduled_tasks.py +++ b/tests/app/celery/test_scheduled_tasks.py @@ -23,6 +23,8 @@ from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTick from tests.app import load_example_csv from tests.app.db import create_job, create_notification, create_template +CHECK_JOB_STATUS_TOO_OLD_MINUTES = 241 + def test_should_call_delete_codes_on_delete_verify_codes_task( notify_db_session, mocker @@ -108,8 +110,9 @@ def test_check_job_status_task_calls_process_incomplete_jobs(mocker, sample_temp job = create_job( template=sample_template, notification_count=3, - created_at=utc_now() - timedelta(minutes=31), - processing_started=utc_now() - timedelta(minutes=31), + created_at=utc_now() - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), + processing_started=utc_now() + - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), job_status=JobStatus.IN_PROGRESS, ) create_notification(template=sample_template, job=job) @@ -125,9 +128,10 @@ def test_check_job_status_task_calls_process_incomplete_jobs_when_scheduled_job_ job = create_job( template=sample_template, notification_count=3, - created_at=utc_now() - timedelta(hours=2), - scheduled_for=utc_now() - timedelta(minutes=31), - processing_started=utc_now() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=5), + scheduled_for=utc_now() - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), + processing_started=utc_now() + - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), job_status=JobStatus.IN_PROGRESS, ) check_job_status() @@ -142,8 +146,8 @@ def test_check_job_status_task_calls_process_incomplete_jobs_for_pending_schedul job = create_job( template=sample_template, notification_count=3, - created_at=utc_now() - timedelta(hours=2), - scheduled_for=utc_now() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=5), + scheduled_for=utc_now() - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), job_status=JobStatus.PENDING, ) @@ -175,17 +179,19 @@ def test_check_job_status_task_calls_process_incomplete_jobs_for_multiple_jobs( job = create_job( template=sample_template, notification_count=3, - created_at=utc_now() - timedelta(hours=2), - scheduled_for=utc_now() - timedelta(minutes=31), - processing_started=utc_now() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=5), + scheduled_for=utc_now() - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), + processing_started=utc_now() + - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), job_status=JobStatus.IN_PROGRESS, ) job_2 = create_job( template=sample_template, notification_count=3, - created_at=utc_now() - timedelta(hours=2), - scheduled_for=utc_now() - timedelta(minutes=31), - processing_started=utc_now() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=5), + scheduled_for=utc_now() - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), + processing_started=utc_now() + - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), job_status=JobStatus.IN_PROGRESS, ) check_job_status() @@ -200,23 +206,24 @@ def test_check_job_status_task_only_sends_old_tasks(mocker, sample_template): job = create_job( template=sample_template, notification_count=3, - created_at=utc_now() - timedelta(hours=2), - scheduled_for=utc_now() - timedelta(minutes=31), - processing_started=utc_now() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=5), + scheduled_for=utc_now() - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), + processing_started=utc_now() + - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), job_status=JobStatus.IN_PROGRESS, ) create_job( template=sample_template, notification_count=3, - created_at=utc_now() - timedelta(minutes=31), - processing_started=utc_now() - timedelta(minutes=29), + created_at=utc_now() - timedelta(minutes=300), + processing_started=utc_now() - timedelta(minutes=239), job_status=JobStatus.IN_PROGRESS, ) create_job( template=sample_template, notification_count=3, - created_at=utc_now() - timedelta(minutes=50), - scheduled_for=utc_now() - timedelta(minutes=29), + created_at=utc_now() - timedelta(minutes=300), + scheduled_for=utc_now() - timedelta(minutes=239), job_status=JobStatus.PENDING, ) check_job_status() @@ -230,16 +237,17 @@ def test_check_job_status_task_sets_jobs_to_error(mocker, sample_template): job = create_job( template=sample_template, notification_count=3, - created_at=utc_now() - timedelta(hours=2), - scheduled_for=utc_now() - timedelta(minutes=31), - processing_started=utc_now() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=5), + scheduled_for=utc_now() - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), + processing_started=utc_now() + - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), job_status=JobStatus.IN_PROGRESS, ) job_2 = create_job( template=sample_template, notification_count=3, - created_at=utc_now() - timedelta(minutes=31), - processing_started=utc_now() - timedelta(minutes=29), + created_at=utc_now() - timedelta(minutes=300), + processing_started=utc_now() - timedelta(minutes=239), job_status=JobStatus.IN_PROGRESS, ) check_job_status() @@ -311,16 +319,18 @@ def test_check_job_status_task_does_not_raise_error(sample_template): create_job( template=sample_template, notification_count=3, - created_at=utc_now() - timedelta(hours=2), - scheduled_for=utc_now() - timedelta(minutes=31), - processing_started=utc_now() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=5), + scheduled_for=utc_now() - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), + processing_started=utc_now() + - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), job_status=JobStatus.FINISHED, ) create_job( template=sample_template, notification_count=3, - created_at=utc_now() - timedelta(minutes=31), - processing_started=utc_now() - timedelta(minutes=31), + created_at=utc_now() - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), + processing_started=utc_now() + - timedelta(minutes=CHECK_JOB_STATUS_TOO_OLD_MINUTES), job_status=JobStatus.FINISHED, ) From f8e30f80b8b1c06935fef75be94ee2411f9ec3ff Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 2 Jan 2025 09:29:30 -0800 Subject: [PATCH 35/50] add performance testing debug info --- app/dao/notifications_dao.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index b9c3118fa..68c1268e3 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -1,5 +1,6 @@ import json from datetime import timedelta +from time import time from flask import current_app from sqlalchemy import ( @@ -727,6 +728,7 @@ def get_service_ids_with_notifications_on_date(notification_type, date): def dao_update_delivery_receipts(receipts, delivered): + start_time_millis = time() * 1000 new_receipts = [] for r in receipts: if isinstance(r, str): @@ -773,3 +775,7 @@ def dao_update_delivery_receipts(receipts, delivered): ) db.session.execute(stmt) db.session.commit() + elapsed_time = (time() * 1000) - start_time_millis + current_app.logger.info( + f"#loadtestperformance batch update query time: {elapsed_time} ms" + ) From e2d64c073872176049f455a1d1ec635e50f2c365 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 2 Jan 2025 11:07:20 -0800 Subject: [PATCH 36/50] add number of notifications processed --- app/dao/notifications_dao.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index 68c1268e3..139f7ae8a 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -777,5 +777,6 @@ def dao_update_delivery_receipts(receipts, delivered): db.session.commit() elapsed_time = (time() * 1000) - start_time_millis current_app.logger.info( - f"#loadtestperformance batch update query time: {elapsed_time} ms" + f"#loadtestperformance batch update query time: \ + updated {len(receipts)} notification in {elapsed_time} ms" ) From 0f52efd742eb9dc6a826feee56cfd0fb1dfc4e2e Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Fri, 3 Jan 2025 12:27:00 -0800 Subject: [PATCH 37/50] fix BOM issue again --- app/aws/s3.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/aws/s3.py b/app/aws/s3.py index d97c421f2..7ad471ed8 100644 --- a/app/aws/s3.py +++ b/app/aws/s3.py @@ -402,12 +402,7 @@ def extract_phones(job): phone_index = 0 for item in first_row: # Note: may contain a BOM and look like \ufeffphone number - if item.lower() in [ - "phone number", - "\\ufeffphone number", - "\\ufeffphone number\n", - "phone number\n", - ]: + if item.lower().lstrip("\ufeff") == "phone number": break phone_index = phone_index + 1 @@ -419,7 +414,8 @@ def extract_phones(job): if phone_index >= len(row): phones[job_row] = "Unavailable" current_app.logger.error( - "Corrupt csv file, missing columns or possibly a byte order mark in the file", + f"Corrupt csv file, missing columns or\ + possibly a byte order mark in the file, row looks like {row}", ) else: From cd13984e3ec42c3e8fe7a80f5a7bff88630309bb Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 6 Jan 2025 07:19:54 -0800 Subject: [PATCH 38/50] placeholder --- app/delivery/send_to_providers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/delivery/send_to_providers.py b/app/delivery/send_to_providers.py index e41062b41..74be6429c 100644 --- a/app/delivery/send_to_providers.py +++ b/app/delivery/send_to_providers.py @@ -158,7 +158,7 @@ def _get_verify_code(notification): recipient = recipient.decode("utf-8") return recipient - +# PUT THE FIX HERE??????? def get_sender_numbers(notification): possible_senders = dao_get_sms_senders_by_service_id(notification.service_id) sender_numbers = [] From a4d8e4fd4cdf455cd366fa8022463a89f802268d Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 6 Jan 2025 07:29:41 -0800 Subject: [PATCH 39/50] pin poetry version to 1.8.5 --- .github/actions/setup-project/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-project/action.yml b/.github/actions/setup-project/action.yml index c095bd595..b2821a5b6 100644 --- a/.github/actions/setup-project/action.yml +++ b/.github/actions/setup-project/action.yml @@ -15,4 +15,4 @@ runs: python-version: "3.12.3" - name: Install poetry shell: bash - run: pip install --upgrade poetry + run: pip install poetry==1.8.5 From 7c8a543629926d9797278651b61cca87e5fbf6e7 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 6 Jan 2025 07:37:21 -0800 Subject: [PATCH 40/50] pin poetry version to 1.8.5 --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index acd31f390..1ffc0a725 100644 --- a/Makefile +++ b/Makefile @@ -9,10 +9,12 @@ GIT_COMMIT ?= $(shell git rev-parse HEAD) ## DEVELOPMENT +## TODO this line should go under `make generate-version-file` +## poetry self update + .PHONY: bootstrap bootstrap: ## Set up everything to run the app make generate-version-file - poetry self update poetry self add poetry-dotenv-plugin poetry lock --no-update poetry install --sync --no-root From 628c6eb84820f83b928aed12086f2d3c47f7cf3f Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 6 Jan 2025 11:45:31 -0800 Subject: [PATCH 41/50] do minimalistic fix first --- app/delivery/send_to_providers.py | 2 +- app/models.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/delivery/send_to_providers.py b/app/delivery/send_to_providers.py index 74be6429c..e41062b41 100644 --- a/app/delivery/send_to_providers.py +++ b/app/delivery/send_to_providers.py @@ -158,7 +158,7 @@ def _get_verify_code(notification): recipient = recipient.decode("utf-8") return recipient -# PUT THE FIX HERE??????? + def get_sender_numbers(notification): possible_senders = dao_get_sms_senders_by_service_id(notification.service_id) sender_numbers = [] diff --git a/app/models.py b/app/models.py index ec6eac335..fc7b855e4 100644 --- a/app/models.py +++ b/app/models.py @@ -577,7 +577,16 @@ class Service(db.Model, Versioned): return self.inbound_number.number def get_default_sms_sender(self): - default_sms_sender = [x for x in self.service_sms_senders if x.is_default] + # notify-api-1513 let's try a minimalistic fix + # to see if we can get the right numbers back + default_sms_sender = [ + x + for x in self.service_sms_senders + if x.is_default and x.service_id == self.id + ] + current_app.logger.info( + f"#notify-api-1513 senders for service {self.name} are {self.service_sms_senders}" + ) return default_sms_sender[0].sms_sender def get_default_reply_to_email_address(self): From 7b5c8401079a41455872cefc4daa9e67e51c85c5 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 6 Jan 2025 13:10:13 -0800 Subject: [PATCH 42/50] code review feedback --- app/aws/s3.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/aws/s3.py b/app/aws/s3.py index 7ad471ed8..e736cdcb0 100644 --- a/app/aws/s3.py +++ b/app/aws/s3.py @@ -1,4 +1,6 @@ +import csv import datetime +from io import StringIO import re import time from concurrent.futures import ThreadPoolExecutor @@ -395,21 +397,19 @@ def get_job_from_s3(service_id, job_id): def extract_phones(job): - job = job.split("\r\n") - first_row = job[0] - job.pop(0) - first_row = first_row.split(",") + job_csv_data = StringIO(job) + csv_reader = csv.reader(job_csv_data) + first_row = next(csv_reader) + phone_index = 0 - for item in first_row: - # Note: may contain a BOM and look like \ufeffphone number + for i, item in enumerate(first_row): if item.lower().lstrip("\ufeff") == "phone number": + phone_index = i break - phone_index = phone_index + 1 phones = {} job_row = 0 - for row in job: - row = row.split(",") + for row in csv_reader: if phone_index >= len(row): phones[job_row] = "Unavailable" From 803a29333d7cb20543b1f7edd6bee83505a1ecaa Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 6 Jan 2025 13:25:13 -0800 Subject: [PATCH 43/50] try oscrypto 1.3.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dc568d89e..bffe8a25a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ marshmallow = "==3.22.0" marshmallow-sqlalchemy = "==1.0.0" newrelic = "*" notifications-python-client = "==10.0.0" -oscrypto = "==1.3.0" +oscrypto = { git = "https://github.com/wbond/oscrypto.git", tag = "v.1.3.1" } packaging = "==24.1" poetry-dotenv-plugin = "==0.2.0" psycopg2-binary = "==2.9.9" From e5e5959db40db33e1de8732cd5af8e02d784dd3f Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 6 Jan 2025 13:27:54 -0800 Subject: [PATCH 44/50] try oscrypto 1.3.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bffe8a25a..d29ff84f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ marshmallow = "==3.22.0" marshmallow-sqlalchemy = "==1.0.0" newrelic = "*" notifications-python-client = "==10.0.0" -oscrypto = { git = "https://github.com/wbond/oscrypto.git", tag = "v.1.3.1" } +oscrypto = { git = "https://github.com/wbond/oscrypto.git", rev = "1547f53" } packaging = "==24.1" poetry-dotenv-plugin = "==0.2.0" psycopg2-binary = "==2.9.9" From 7088f3b9762d0b276fd07ca3dc5fb540cc675f4d Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 6 Jan 2025 13:29:44 -0800 Subject: [PATCH 45/50] try oscrypto 1.3.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d29ff84f1..8da60795a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ marshmallow = "==3.22.0" marshmallow-sqlalchemy = "==1.0.0" newrelic = "*" notifications-python-client = "==10.0.0" -oscrypto = { git = "https://github.com/wbond/oscrypto.git", rev = "1547f53" } +oscrypto = { git = "https://github.com/wbond/oscrypto.git" } packaging = "==24.1" poetry-dotenv-plugin = "==0.2.0" psycopg2-binary = "==2.9.9" From 64e0354f01de7cb8d30ae400ada6225d10067757 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 6 Jan 2025 13:35:32 -0800 Subject: [PATCH 46/50] try oscrypto 1.3.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8da60795a..e65ab47d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ marshmallow = "==3.22.0" marshmallow-sqlalchemy = "==1.0.0" newrelic = "*" notifications-python-client = "==10.0.0" -oscrypto = { git = "https://github.com/wbond/oscrypto.git" } +oscrypto = { git = "https://github.com/wbond/oscrypto.git", rev = "main" } packaging = "==24.1" poetry-dotenv-plugin = "==0.2.0" psycopg2-binary = "==2.9.9" From 879ed2da0a418cdfbc0dff88cb9e9ff54e4df117 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 6 Jan 2025 13:47:11 -0800 Subject: [PATCH 47/50] ugh specify rev --- poetry.lock | 14 +++++++++----- pyproject.toml | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index e0096a7de..fadc6cde7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2829,14 +2829,18 @@ version = "1.3.0" description = "TLS (SSL) sockets, key generation, encryption, decryption, signing, verification and KDFs using the OS crypto libraries. Does not require a compiler, and relies on the OS for patching. Works on Windows, OS X and Linux/BSD." optional = false python-versions = "*" -files = [ - {file = "oscrypto-1.3.0-py2.py3-none-any.whl", hash = "sha256:2b2f1d2d42ec152ca90ccb5682f3e051fb55986e1b170ebde472b133713e7085"}, - {file = "oscrypto-1.3.0.tar.gz", hash = "sha256:6f5fef59cb5b3708321db7cca56aed8ad7e662853351e7991fcf60ec606d47a4"}, -] +files = [] +develop = false [package.dependencies] asn1crypto = ">=1.5.1" +[package.source] +type = "git" +url = "https://github.com/wbond/oscrypto.git" +reference = "1547f53" +resolved_reference = "1547f535001ba568b239b8797465536759c742a3" + [[package]] name = "packageurl-python" version = "0.16.0" @@ -4947,4 +4951,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "271d8e0f25856f45970e5a9cc3b8871a01b732226ab3ed68ea426912b5117fcf" +content-hash = "81a109693e74d2ffa3be7098e629050f25090c6a08bab57056b9a4a35283ea6f" diff --git a/pyproject.toml b/pyproject.toml index e65ab47d7..d29ff84f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ marshmallow = "==3.22.0" marshmallow-sqlalchemy = "==1.0.0" newrelic = "*" notifications-python-client = "==10.0.0" -oscrypto = { git = "https://github.com/wbond/oscrypto.git", rev = "main" } +oscrypto = { git = "https://github.com/wbond/oscrypto.git", rev = "1547f53" } packaging = "==24.1" poetry-dotenv-plugin = "==0.2.0" psycopg2-binary = "==2.9.9" From 7fab332ab9e884629f4de77dfa6d207cc75a86c9 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Mon, 6 Jan 2025 13:50:10 -0800 Subject: [PATCH 48/50] fix isort --- app/aws/s3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/aws/s3.py b/app/aws/s3.py index e736cdcb0..c33366a2c 100644 --- a/app/aws/s3.py +++ b/app/aws/s3.py @@ -1,9 +1,9 @@ import csv import datetime -from io import StringIO import re import time from concurrent.futures import ThreadPoolExecutor +from io import StringIO import botocore from boto3 import Session From 2b3c9c869e550bc640ae797b946dea9fcc1e70df Mon Sep 17 00:00:00 2001 From: Andrew Shumway Date: Tue, 7 Jan 2025 09:27:04 -0700 Subject: [PATCH 49/50] Change delivery receipts tasks time to help UI lag --- app/celery/scheduled_tasks.py | 2 +- app/config.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/celery/scheduled_tasks.py b/app/celery/scheduled_tasks.py index 3a3fa696e..2dcd570cc 100644 --- a/app/celery/scheduled_tasks.py +++ b/app/celery/scheduled_tasks.py @@ -256,7 +256,7 @@ def process_delivery_receipts(self): cloudwatch = AwsCloudwatchClient() cloudwatch.init_app(current_app) - start_time = aware_utcnow() - timedelta(minutes=10) + start_time = aware_utcnow() - timedelta(minutes=3) end_time = aware_utcnow() delivered_receipts, failed_receipts = cloudwatch.check_delivery_receipts( start_time, end_time diff --git a/app/config.py b/app/config.py index 53a2f9a0d..d3f2a5197 100644 --- a/app/config.py +++ b/app/config.py @@ -200,7 +200,7 @@ class Config(object): }, "process-delivery-receipts": { "task": "process-delivery-receipts", - "schedule": timedelta(minutes=8), + "schedule": timedelta(minutes=2), "options": {"queue": QueueNames.PERIODIC}, }, "expire-or-delete-invitations": { From 1c67478d5e50ba74cad8487c0f8a67a99a3e8071 Mon Sep 17 00:00:00 2001 From: Carlo Costino Date: Tue, 7 Jan 2025 15:27:05 -0500 Subject: [PATCH 50/50] Update daily_check GitHub Action This changeset updates the reference of the upload_artifacts action from GitHub to be v4 instead of v3. v3 is being deprecated at the end of January 2025. Signed-off-by: Carlo Costino --- .github/workflows/daily_checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/daily_checks.yml b/.github/workflows/daily_checks.yml index 21374e219..d8e19de98 100644 --- a/.github/workflows/daily_checks.yml +++ b/.github/workflows/daily_checks.yml @@ -46,7 +46,7 @@ jobs: - name: Run scan run: bandit -r app/ -f txt -o /tmp/bandit-output.txt --confidence-level medium - name: Upload bandit artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: bandit-report path: /tmp/bandit-output.txt