def sms_notification(self, body, to): try: nexmo_number_out = settings.ENDAGA['NEXMO_NOTIFICATION_NUMBER'] except KeyError: return # Do nothing if not configured nexmo_provider = NexmoProvider(settings.ENDAGA['NEXMO_ACCT_SID'], settings.ENDAGA['NEXMO_AUTH_TOKEN'], settings.ENDAGA['NEXMO_INBOUND_SMS_URL'], None, #outbound_sms_url settings.ENDAGA['NEXMO_INBOUND_VOICE_HOST']) nexmo_provider.send(to, nexmo_number_out, body)
def get(self, request, bts_uuid=None, format=None): """Return a number that's usable by a BTS. We first check for "available" numbers in our database. If there are none, we buy a number from a provider, set it up, and return here. We have to specify a specific BTS to avoid a race condition when multiple BTS register for a number at once. """ if not bts_uuid: return Response("No BTS UUID specified.", status=status.HTTP_400_BAD_REQUEST) network = get_network_from_user(request.user) try: bts = models.BTS.objects.get(uuid=bts_uuid, network=network) except models.BTS.DoesNotExist: return Response("The specified BTS does not belong to the user.", status=status.HTTP_403_FORBIDDEN) # First check for available numbers. If a number is available, it's up # for grabs by anyone. with transaction.atomic(): q = models.Number.objects.filter(state__exact="available") for n in q: # We do this here rather than in the db query since at this # time some numbers don't have a country field to query on. Can # probably be removed later. -- SH (2014 aug 21) # TODO(matt): this potentially sets a lot of numbers as # pending.. if n.country() == network.number_country: n.state = "pending" n.network = network n.save() n.charge() return Response(int(n.number), status=status.HTTP_200_OK) # No number available, so we have to buy one from Nexmo. # TODO: Try to buy from multiple vendors np = NexmoProvider( settings.ENDAGA['NEXMO_ACCT_SID'], settings.ENDAGA['NEXMO_AUTH_TOKEN'], settings.ENDAGA['NEXMO_INBOUND_SMS_URL'], None, #outbound_sms_url settings.ENDAGA['NEXMO_INBOUND_VOICE_HOST'], country=network.number_country) try: # This call creates the new number in the DB as a side effect. new_number = np.get_number(bts.network) print "New number is %s" % new_number return Response(new_number, status=status.HTTP_200_OK) except ValueError: return Response("Number not available", status=status.HTTP_404_NOT_FOUND)
def post(self, request, msisdn): network = get_network_from_user(request.user) number = models.Number.objects.get(number=msisdn) if (number.network and number.network != network and not request.user.is_staff): return Response("User is not associated with that Number %s %s." % (number.network.pk, network.pk), status=status.HTTP_403_FORBIDDEN) # Must post a valid 'state'. valid_states = ('available', 'released') if request.POST.get('state', None) not in valid_states: return Response("Must post a valid state.", status=status.HTTP_400_BAD_REQUEST) # This is a valid request, begin processing. First check if this is a # number-deactivation request. if (number.state == 'inuse' and request.POST.get('state') == 'available'): # Refuse to deactivate a subscriber's last number. if (len(models.Number.objects.filter(subscriber=number.subscriber)) <= 1): message = ("Cannot deactivate a subscriber's last number." " Instead, delete the subscriber.") return Response(message, status=status.HTTP_400_BAD_REQUEST) # If it's not the subscriber's only number, send an async post to # the BTS to deactivate the number. Sign the request using JWT. bts = number.subscriber.bts url = '%s/config/deactivate_number' % bts.inbound_url data = { 'number': msisdn, # Add a UUID as a nonce for the message. 'msgid': str(uuid.uuid4()), } serializer = itsdangerous.JSONWebSignatureSerializer(bts.secret) signed_data = { 'jwt': serializer.dumps(data), } tasks.async_post.delay(url, signed_data) # Create a 'deactivate_number' UsageEvent. now = datetime.datetime.now(pytz.utc) reason = 'deactivated phone number: %s' % number.number event = models.UsageEvent.objects.create( subscriber=number.subscriber, date=now, bts=bts, kind='deactivate_number', to_number=number.number, reason=reason, oldamt=number.subscriber.balance, newamt=number.subscriber.balance, change=0) event.save() # Diassociate the Number from its former Subscriber and Network. number.subscriber = None number.network = None number.state = request.POST.get('state') number.save() return Response("") # Check if this is a number-release request. if request.POST.get('state') == 'released': # User must be staff to do this. if not request.user.is_staff: return Response("", status=status.HTTP_404_NOT_FOUND) # The number must not be 'inuse.' if number.state == 'inuse': return Response("", status=status.HTTP_400_BAD_REQUEST) # The number cannot be associated with a Sub. if number.subscriber: return Response("", status=status.HTTP_400_BAD_REQUEST) # Validation passes, release (cancel) the number. nexmo_provider = NexmoProvider( settings.ENDAGA['NEXMO_ACCT_SID'], settings.ENDAGA['NEXMO_AUTH_TOKEN'], settings.ENDAGA['NEXMO_INBOUND_SMS_URL'], None, settings.ENDAGA['NEXMO_INBOUND_VOICE_HOST'], country=number.country_id) if nexmo_provider.cancel_number(number.number): # Success, delete the number. number.delete() return Response("", status=status.HTTP_200_OK) else: print 'deleting number %s failed' % number.number return Response("", status=status.HTTP_500_INTERNAL_SERVER_ERROR) # Invalid request. return Response("", status=status.HTTP_400_BAD_REQUEST)
def downtime_notify(self): """Sends out notifcation to a user if a BTS has gone down. Runs every `BTS_INACTIVE_TIMEOUT_SECS` """ timeout_secs = settings.ENDAGA['BTS_INACTIVE_TIMEOUT_SECS'] # get nexmo config try: nexmo_number_out = settings.ENDAGA['NEXMO_NOTIFICATION_NUMBER'] nexmo_provider = NexmoProvider( settings.ENDAGA['NEXMO_ACCT_SID'], settings.ENDAGA['NEXMO_AUTH_TOKEN'], settings.ENDAGA['NEXMO_INBOUND_SMS_URL'], None, #outbound_sms_url settings.ENDAGA['NEXMO_INBOUND_VOICE_HOST']) except KeyError: nexmo_number_out = None nexmo_provider = None # get mailgun config try: support_email = settings.TEMPLATE_CONSTANTS['SUPPORT_EMAIL'] except KeyError: support_email = None for bts in BTS.objects.filter(status='active'): # Safety check - should not be hit if not bts.last_active: continue checkin_secs = (django.utils.timezone.now() - bts.last_active).total_seconds() # Only send out notifications after one period of no activity, if BTS has 'active' status if timeout_secs < checkin_secs: data = { 'bts_uuid_short': bts.uuid[:6], 'bts_uuid': bts.uuid, 'bts_nickname': bts.nickname, } if bts.nickname: email_subj = ("Alert open: BTS %s (%s...) offline" % (bts.nickname, bts.uuid[:6])) else: email_subj = "Alert open: BTS %s offline" % (bts.uuid) email_msg = render_to_string("internal/bts_down_email.html", data) sms_msg = render_to_string("internal/bts_down_sms.html", data) for email in bts.network.notify_emails.split(','): email = email.strip() if support_email and email: try: send_mail(email_subj, email_msg, support_email, [email]) except Exception as e: # log the error, but ignore it. print( "email fail sub: '%s' msg: '%s' frm: '%s' " "to: '%s' exception: %s" % (email_subj, email_msg, support_email, email, e)) # We blindly assume the SMS is <140 char for number in bts.network.notify_numbers.split(','): number = number.strip() if nexmo_number_out and nexmo_provider and number: try: nexmo_provider.send(number, nexmo_number_out, sms_msg) except Exception as e: print "sms fail: to: %s from: %s msg: %s" % ( number, nexmo_number_out, sms_msg) # Mark BTS as inactive bts.status = 'inactive' bts.save() down_event = SystemEvent(date=django.utils.timezone.now(), bts=bts, type='bts down') down_event.save()