ovh: update credit left (#46316)

This commit is contained in:
Valentin Deniaud 2020-10-07 11:22:46 +02:00
parent fa1f64f85f
commit 153a91373d
4 changed files with 124 additions and 26 deletions

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.18 on 2020-10-08 09:26
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ovh', '0009_auto_20200730_1047'),
]
operations = [
migrations.AlterField(
model_name='ovhsmsgateway',
name='credit_left',
field=models.PositiveIntegerField(default=0, editable=False, verbose_name='Credit left'),
),
]

View File

@ -2,6 +2,7 @@ import hashlib
import json
import requests
import time
from urllib.parse import urljoin
from django.db import models
from django.utils.encoding import force_text
@ -13,8 +14,8 @@ from passerelle.utils.jsonresponse import APIError
class OVHSMSGateway(SMSResource):
documentation_url = 'https://doc-publik.entrouvert.com/admin-fonctionnel/les-tutos/configuration-envoi-sms/'
hide_description_fields = ['account']
API_URL = 'https://eu.api.ovh.com/1.0/sms/%(serviceName)s/users/%(login)s/jobs/'
hide_description_fields = ['account', 'credit_left']
API_URL = 'https://eu.api.ovh.com/1.0/sms/%(serviceName)s/'
URL = 'https://www.ovh.com/cgi-bin/sms/http2sms.cgi'
MESSAGES_CLASSES = (
(0, _('Message are directly shown to users on phone screen '
@ -53,23 +54,24 @@ class OVHSMSGateway(SMSResource):
)
username = models.CharField(
verbose_name=_('Username'),
verbose_name=_('Username (deprecated)'),
max_length=64,
help_text=_('API user created on the SMS account.'),
help_text=_('API user created on the SMS account. This field is obsolete once keys and secret '
'fields above are filled.'),
)
password = models.CharField(
verbose_name=_('Password (deprecated)'),
max_length=64,
blank=True,
help_text=_(
'Password for legacy API. This field is obsolete once keys and secret fields below are filled.'
'Password for legacy API. This field is obsolete once keys and secret fields above are filled.'
),
)
msg_class = models.IntegerField(choices=MESSAGES_CLASSES, default=1,
verbose_name=_('Message class'))
credit_threshold_alert = models.PositiveIntegerField(verbose_name=_('Credit alert threshold'),
default=100)
credit_left = models.PositiveIntegerField(verbose_name=_('Credit left'), default=0)
credit_left = models.PositiveIntegerField(verbose_name=_('Credit left'), default=0, editable=False)
TEST_DEFAULTS = {
'create_kwargs': {
@ -114,24 +116,19 @@ class OVHSMSGateway(SMSResource):
verbose_name = 'OVH'
db_table = 'sms_ovh'
def send_msg(self, text, sender, destinations, **kwargs):
if not (self.application_key and self.consumer_key and self.application_secret):
return self.send_msg_legacy(text, sender, destinations, **kwargs)
@property
def uses_new_api(self):
return self.application_key and self.consumer_key and self.application_secret
def request(self, method, endpoint, **kwargs):
url = self.API_URL % {'serviceName': self.account, 'login': self.username}
body = {
'sender': sender,
'receivers': destinations,
'message': text,
'class': self.NEW_MESSAGES_CLASSES[self.msg_class],
}
if not kwargs['stop']:
body.update({'noStopClause': True})
url = urljoin(url, endpoint)
# sign request
body = json.dumps(kwargs['json']) if 'json' in kwargs else ''
now = str(int(time.time()))
signature = hashlib.sha1()
to_sign = "+".join((self.application_secret, self.consumer_key, 'POST', url, json.dumps(body), now))
to_sign = "+".join((self.application_secret, self.consumer_key, method.upper(), url, body, now))
signature.update(to_sign.encode())
headers = {
@ -142,7 +139,7 @@ class OVHSMSGateway(SMSResource):
}
try:
response = self.requests.post(url, headers=headers, json=body)
response = self.requests.request(method, url, headers=headers, **kwargs)
except requests.RequestException as e:
raise APIError('OVH error: POST failed, %s' % e)
else:
@ -154,6 +151,22 @@ class OVHSMSGateway(SMSResource):
response.raise_for_status()
except requests.RequestException as e:
raise APIError('OVH error: %s "%s"' % (e, result))
return result
def send_msg(self, text, sender, destinations, **kwargs):
if not self.uses_new_api:
return self.send_msg_legacy(text, sender, destinations, **kwargs)
body = {
'sender': sender,
'receivers': destinations,
'message': text,
'class': self.NEW_MESSAGES_CLASSES[self.msg_class],
}
if not kwargs['stop']:
body.update({'noStopClause': True})
result = self.request('post', 'jobs/', json=body)
ret = {}
credits_removed = result['totalCreditsRemoved']
@ -161,7 +174,7 @@ class OVHSMSGateway(SMSResource):
self.credit_left -= credits_removed
if self.credit_left < 0:
self.credit_left = 0
self.save()
self.save(update_credit=False)
if self.credit_left < self.credit_threshold_alert:
ret['warning'] = 'credit level too low for %s: %s (threshold %s)' % (
self.slug,
@ -174,6 +187,22 @@ class OVHSMSGateway(SMSResource):
return ret
def update_credit_left(self):
if not self.uses_new_api:
return
result = self.request('get', endpoint='')
self.credit_left = result['creditsLeft']
self.save(update_credit=False)
def hourly(self):
super().hourly()
self.update_credit_left()
def save(self, *args, update_credit=True, **kwargs):
super().save(*args, **kwargs)
if update_credit:
self.add_job('update_credit_left')
def send_msg_legacy(self, text, sender, destinations, **kwargs):
"""Send a SMS using the HTTP2 endpoint"""
if not self.password:

View File

@ -0,0 +1,15 @@
{% extends "passerelle/manage/messages_service_view.html" %}
{% load i18n passerelle %}
{% block description %}
{{ block.super }}
{% if object.uses_new_api %}
<p>
{% if object.credit_left %}
<b>{% trans "Credit left:" %}</b> {{ object.credit_left }}
{% else %}
<b>{% trans "There is no credit left." %}</b>
{% endif %}
</p>
{% endif %}
{% endblock %}

View File

@ -69,14 +69,14 @@ def test_connectors(app, connector, freezer):
test_vectors = getattr(connector, 'TEST_DEFAULTS', {}).get('test_vectors', [])
total = len(test_vectors)
nb_failed = 0
assert Job.objects.count() == 0
assert Job.objects.filter(method_name='send_job').count() == 0
for test_vector in test_vectors:
# register job
freezer.move_to('2019-01-01 00:00:00')
result = app.post_json(path, params=payload)
assert result.json['err'] == 0
job_id = Job.objects.get(status='registered').id
job_id = Job.objects.get(method_name='send_job', status='registered').id
# perform job
freezer.move_to('2019-01-01 01:00:03')
@ -92,7 +92,7 @@ def test_connectors(app, connector, freezer):
nb_failed += 1
else:
assert job.status == 'completed'
assert Job.objects.count() == total
assert Job.objects.filter(method_name='send_job').count() == total
assert SMSLog.objects.count() == total - nb_failed
@ -186,7 +186,7 @@ def test_sms_nostop_parameter(app, connector):
def test_ovh_new_api(app, freezer):
connector = OVHSMSGateway.objects.create(
slug='ovh', account='sms-test42', username='john',
slug='ovh', account='sms-test42',
application_key='RHrTdU2oTsrVC0pu',
application_secret='CLjtS69tTcPgCKxedeoZlgMSoQGSiXMa',
consumer_key='iF0zi0MJrbjNcI3hvuvwkhNk8skrigxz'
@ -208,7 +208,7 @@ def test_ovh_new_api(app, freezer):
path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug)
result = app.post_json(path, params=payload)
assert result.json['err'] == 0
job_id = Job.objects.get(status='registered').id
job_id = Job.objects.get(method_name='send_job', status='registered').id
# perform job
freezer.move_to('2019-01-01 01:00:03')
@ -218,7 +218,8 @@ def test_ovh_new_api(app, freezer):
'ids': [241615100],
'invalidReceivers': []
}
url = connector.API_URL % {'serviceName': 'sms-test42', 'login': 'john'}
base_url = connector.API_URL % {'serviceName': 'sms-test42'}
url = base_url + 'jobs/'
with utils.mock_url(url, resp, 200) as mocked:
connector.jobs()
job = Job.objects.get(id=job_id)
@ -255,3 +256,36 @@ def test_sms_test_send(admin_user, app, connector):
}
assert resp.status_code == 302
assert resp.location == url
def test_ovh_new_api_credit(app, freezer):
connector = OVHSMSGateway.objects.create(
slug='ovh', account='sms-test42',
application_key='RHrTdU2oTsrVC0pu',
application_secret='CLjtS69tTcPgCKxedeoZlgMSoQGSiXMa',
consumer_key='iF0zi0MJrbjNcI3hvuvwkhNk8skrigxz'
)
manager_url = '/%s/%s/' % (connector.get_connector_slug(), connector.slug)
resp = app.get(manager_url)
assert 'no credit left' in resp.text
# a job to update credit was added on connector creation
resp = {
'creditsLeft': 123,
}
ovh_url = connector.API_URL % {'serviceName': 'sms-test42'}
with utils.mock_url(ovh_url, resp, 200) as mocked:
connector.jobs()
assert connector.credit_left == 123
resp = app.get(manager_url)
assert '123' in resp.text
# hourly update
resp = {
'creditsLeft': 456,
}
with utils.mock_url(ovh_url, resp, 200) as mocked:
connector.hourly()
assert connector.credit_left == 456