Move Payment logic into Collector process.

* Adds storage layer to Collector
* All code from Central related to payments, credit cards etc moved

Change-Id: Ibd2d7d54ded239d7a3b8423c25b3291ca54792b2
This commit is contained in:
Endre Karlson 2013-08-04 15:42:48 +02:00
parent b0f909200f
commit f9ab0b1633
24 changed files with 1122 additions and 899 deletions

View File

@ -19,7 +19,7 @@ import wsmeext.pecan as wsme_pecan
from billingstack.api.base import Query, _query_to_criterion, RestController
from billingstack.api.v2 import models
from billingstack.central.rpcapi import central_api
from billingstack.collector.rpcapi import collector_api
class PGProviders(RestController):
@ -27,7 +27,7 @@ class PGProviders(RestController):
def get_all(self, q=[]):
criterion = _query_to_criterion(q)
rows = central_api.list_pg_providers(
rows = collector_api.list_pg_providers(
request.ctxt, criterion=criterion)
return map(models.PGProvider.from_db, rows)
@ -39,14 +39,14 @@ class PGConfigController(RestController):
@wsme_pecan.wsexpose(models.PGConfig)
def get_all(self):
row = central_api.get_pg_config(request.ctxt, self.id_)
row = collector_api.get_pg_config(request.ctxt, self.id_)
return models.PGConfig.from_db(row)
@wsme.validate(models.PGConfig)
@wsme_pecan.wsexpose(models.PGConfig, body=models.PGConfig)
def patch(self, body):
row = central_api.update_pg_config(
row = collector_api.update_pg_config(
request.ctxt,
self.id_,
body.to_db())
@ -55,7 +55,7 @@ class PGConfigController(RestController):
@wsme_pecan.wsexpose(None, status_code=204)
def delete(self):
central_api.delete_pg_config(request.ctxt, self.id_)
collector_api.delete_pg_config(request.ctxt, self.id_)
class PGConfigsController(RestController):
@ -67,10 +67,12 @@ class PGConfigsController(RestController):
@wsme_pecan.wsexpose(models.PGConfig, body=models.PGConfig,
status_code=202)
def post(self, body):
row = central_api.create_pg_config(
values = body.to_db()
values['merchant_id'] = request.context['merchant_id']
row = collector_api.create_pg_config(
request.ctxt,
request.context['merchant_id'],
body.to_db())
values)
return models.PGConfig.from_db(row)
@ -79,7 +81,7 @@ class PGConfigsController(RestController):
criterion = _query_to_criterion(
q, merchant_id=request.context['merchant_id'])
rows = central_api.list_pg_configs(
rows = collector_api.list_pg_configs(
request.ctxt, criterion=criterion)
return map(models.PGConfig.from_db, rows)
@ -92,14 +94,14 @@ class PaymentMethodController(RestController):
@wsme_pecan.wsexpose(models.PaymentMethod)
def get_all(self):
row = central_api.get_payment_method(request.ctxt, self.id_)
row = collector_api.get_payment_method(request.ctxt, self.id_)
return models.PaymentMethod.from_db(row)
@wsme.validate(models.PaymentMethod)
@wsme_pecan.wsexpose(models.PaymentMethod, body=models.PaymentMethod)
def patch(self, body):
row = central_api.update_payment_method(
row = collector_api.update_payment_method(
request.ctxt,
self.id_,
body.to_db())
@ -108,7 +110,7 @@ class PaymentMethodController(RestController):
@wsme_pecan.wsexpose(None, status_code=204)
def delete(self):
central_api.delete_payment_method(request.ctxt, self.id_)
collector_api.delete_payment_method(request.ctxt, self.id_)
class PaymentMethodsController(RestController):
@ -120,10 +122,10 @@ class PaymentMethodsController(RestController):
@wsme_pecan.wsexpose(models.PaymentMethod, body=models.PaymentMethod,
status_code=202)
def post(self, body):
row = central_api.create_payment_method(
request.ctxt,
request.context['customer_id'],
body.to_db())
values = body.to_db()
values['customer_id'] = request.context['customer_id']
row = collector_api.create_payment_method(request.ctxt, values)
return models.PaymentMethod.from_db(row)
@ -133,7 +135,7 @@ class PaymentMethodsController(RestController):
q, merchant_id=request.context['merchant_id'],
customer_id=request.context['customer_id'])
rows = central_api.list_payment_methods(
rows = collector_api.list_payment_methods(
request.ctxt, criterion=criterion)
return map(models.PaymentMethod.from_db, rows)

View File

@ -155,8 +155,11 @@ class Usage(Base):
class PGConfig(Base):
name = text
title = text
merchant_id = text
provider_id = text
is_default = bool
properties = DictType(key_type=text, value_type=property_type)
@ -165,6 +168,7 @@ class PaymentMethod(Base):
identifier = text
expires = text
merchant_id = text
customer_id = text
provider_config_id = text
@ -186,15 +190,11 @@ class Merchant(Account):
def to_db(self):
values = self.as_dict()
change_suffixes(values, self._keys, shorten=False)
if 'default_gateway' in values:
values['default_gateway_id'] = values.pop('default_gateway')
return values
@classmethod
def from_db(cls, values):
change_suffixes(values, cls._keys)
if 'default_gateway_id' in values:
values['default_gateway'] = values.pop('default_gateway_id')
return cls(**values)

View File

@ -86,63 +86,6 @@ class CentralAPI(proxy.RpcProxy):
def delete_contact_info(self, ctxt, id_):
return self.call(ctxt, self.make_msg('delete_contact_info', id_=id_))
# PGP
def list_pg_providers(self, ctxt, criterion=None):
return self.call(ctxt, self.make_msg('list_pg_providers',
criterion=criterion))
def get_pg_provider(self, ctxt, id_):
return self.call(ctxt, self.make_msg('get_pg_provider', id_=id_))
# PGM
def list_pg_methods(self, ctxt, criterion=None):
return self.call(ctxt, self.make_msg('list_pg_methods',
criterion=criterion))
def get_pg_method(self, ctxt, id_):
return self.call(ctxt, self.make_msg('get_pg_method', id_=id_))
def delete_pg_method(self, ctxt, id_):
return self.call(ctxt, self.make_msg('delete_pg_method', id_=id_))
# PGC
def create_pg_config(self, ctxt, merchant_id, values):
return self.call(ctxt, self.make_msg('create_pg_config',
merchant_id=merchant_id, values=values))
def list_pg_configs(self, ctxt, criterion=None):
return self.call(ctxt, self.make_msg('list_pg_configs',
criterion=criterion))
def get_pg_config(self, ctxt, id_):
return self.call(ctxt, self.make_msg('get_pg_config', id_=id_))
def update_pg_config(self, ctxt, id_, values):
return self.call(ctxt, self.make_msg('update_pg_config', id_=id_,
values=values))
def delete_pg_config(self, ctxt, id_):
return self.call(ctxt, self.make_msg('delete_pg_config', id_=id_))
# PaymentMethod
def create_payment_method(self, ctxt, customer_id, values):
return self.call(ctxt, self.make_msg('create_payment_method',
customer_id=customer_id, values=values))
def list_payment_methods(self, ctxt, criterion=None):
return self.call(ctxt, self.make_msg('list_payment_methods',
criterion=criterion))
def get_payment_method(self, ctxt, id_):
return self.call(ctxt, self.make_msg('get_payment_method', id_=id_))
def update_payment_method(self, ctxt, id_, values):
return self.call(ctxt, self.make_msg('update_payment_method', id_=id_,
values=values))
def delete_payment_method(self, ctxt, id_):
return self.call(ctxt, self.make_msg('delete_payment_method', id_=id_))
# Merchant
def create_merchant(self, ctxt, values):
return self.call(ctxt, self.make_msg('create_merchant', values=values))

View File

@ -100,39 +100,6 @@ class Service(rpc_service.Service):
def get_pg_provider(self, ctxt, pgp_id):
return self.storage_conn.get_pg_provider(ctxt, pgp_id)
# PGC
def create_pg_config(self, ctxt, merchant_id, values):
return self.storage_conn.create_pg_config(ctxt, merchant_id, values)
def list_pg_configs(self, ctxt, **kw):
return self.storage_conn.list_pg_configs(ctxt, **kw)
def get_pg_config(self, ctxt, id_):
return self.storage_conn.get_pg_config(ctxt, id_)
def update_pg_config(self, ctxt, id_, values):
return self.storage_conn.update_pg_config(ctxt, id_, values)
def delete_pg_config(self, ctxt, id_):
return self.storage_conn.delete_pg_config(ctxt, id_)
# PM
def create_payment_method(self, ctxt, customer_id, values):
return self.storage_conn.create_payment_method(
ctxt, customer_id, values)
def list_payment_methods(self, ctxt, **kw):
return self.storage_conn.list_payment_methods(ctxt, **kw)
def get_payment_method(self, ctxt, id_, **kw):
return self.storage_conn.get_payment_method(ctxt, id_)
def update_payment_method(self, ctxt, id_, values):
return self.storage_conn.update_payment_method(ctxt, id_, values)
def delete_payment_method(self, ctxt, id_):
return self.storage_conn.delete_payment_method(ctxt, id_)
# Merchant
def create_merchant(self, ctxt, values):
return self.storage_conn.create_merchant(ctxt, values)

View File

@ -33,16 +33,6 @@ cfg.CONF.register_group(cfg.OptGroup(
cfg.CONF.register_opts(SQLOPTS, group='central:sqlalchemy')
def filter_merchant_by_join(query, cls, criterion):
if criterion and 'merchant_id' in criterion:
merchant_id = criterion.pop('merchant_id')
if not hasattr(cls, 'merchant_id'):
raise RuntimeError('No merchant_id attribute on %s' % cls)
query = query.join(cls).filter(cls.merchant_id == merchant_id)
return query
class SQLAlchemyEngine(StorageEngine):
__plugin_name__ = 'sqlalchemy'
@ -176,132 +166,6 @@ class Connection(Connection, api.HelpersMixin):
def delete_contact_info(self, ctxt, id_):
self._delete(models.ContactInfo, id_)
# Payment Gateway Providers
def pg_provider_register(self, ctxt, values):
"""
Register a Provider and it's Methods
"""
values = values.copy()
methods = values.pop('methods', [])
query = self.session.query(models.PGProvider)\
.filter_by(name=values['name'])
try:
provider = query.one()
except exc.NoResultFound:
provider = models.PGProvider()
provider.update(values)
self._set_provider_methods(ctxt, provider, methods)
self._save(provider)
return self._dict(provider, extra=['methods'])
def list_pg_providers(self, ctxt, **kw):
"""
List available PG Providers
"""
rows = self._list(models.PGProvider, **kw)
return [self._dict(r, extra=['methods']) for r in rows]
def get_pg_provider(self, ctxt, pgp_id):
row = self._get(models.PGProvider, pgp_id)
return self._dict(row, extra=['methods'])
def pg_provider_deregister(self, ctxt, id_):
self._delete(models.PGProvider, id_)
def _get_provider_methods(self, provider):
"""
Used internally to form a "Map" of the Providers methods
"""
methods = {}
for m in provider.methods:
methods[m.key()] = m
return methods
def _set_provider_methods(self, ctxt, provider, config_methods):
"""Helper method for setting the Methods for a Provider"""
existing = self._get_provider_methods(provider)
for method in config_methods:
self._set_method(provider, method, existing)
def _set_method(self, provider, method, existing):
key = models.PGMethod.make_key(method)
if key in existing:
existing[key].update(method)
else:
row = models.PGMethod(**method)
provider.methods.append(row)
# Payment Gateway Configuration
def create_pg_config(self, ctxt, merchant_id, values):
merchant = self._get(models.Merchant, merchant_id)
row = models.PGConfig(**values)
row.merchant = merchant
self._save(row)
return dict(row)
def list_pg_configs(self, ctxt, **kw):
rows = self._list(models.PGConfig, **kw)
return map(dict, rows)
def get_pg_config(self, ctxt, id_):
row = self._get(models.PGConfig, id_)
return dict(row)
def update_pg_config(self, ctxt, id_, values):
row = self._update(models.PGConfig, id_, values)
return dict(row)
def delete_pg_config(self, ctxt, id_):
self._delete(models.PGConfig, id_)
# PaymentMethod
def create_payment_method(self, ctxt, customer_id, values):
"""
Configure a PaymentMethod like a CreditCard
"""
customer = self._get_id_or_name(models.Customer, customer_id)
# NOTE: Attempt to see if there's a default gateway if none is
# specified
if not values.get('provider_config_id') and \
customer.merchant.default_gateway:
values['provider_config_id'] = customer.merchant.default_gateway_id
row = models.PaymentMethod(**values)
row.customer = customer
self._save(row)
return self._dict(row)
def list_payment_methods(self, ctxt, criterion=None, **kw):
query = self.session.query(models.PaymentMethod)
query = filter_merchant_by_join(query, models.Customer, criterion)
rows = self._list(query=query, cls=models.PaymentMethod,
criterion=criterion, **kw)
return [self._dict(row) for row in rows]
def get_payment_method(self, ctxt, id_, **kw):
row = self._get_id_or_name(models.PaymentMethod, id_)
return self._dict(row)
def update_payment_method(self, ctxt, id_, values):
row = self._update(models.PaymentMethod, id_, values)
return self._dict(row)
def delete_payment_method(self, ctxt, id_):
self._delete(models.PaymentMethod, id_)
# Merchant
def create_merchant(self, ctxt, values):
row = models.Merchant(**values)
@ -595,10 +459,15 @@ class Connection(Connection, api.HelpersMixin):
"""
query = self.session.query(models.Subscription)
query = filter_merchant_by_join(query, models.Customer, criterion)
# NOTE: Filter needs to be joined for merchant_id
query = db_utils.filter_merchant_by_join(
query, models.Customer, criterion)
rows = self._list(query=query, cls=models.Subscription,
criterion=criterion, **kw)
rows = self._list(
query=query,
cls=models.Subscription,
criterion=criterion,
**kw)
return map(self._subscription, rows)

View File

@ -46,62 +46,6 @@ class Language(BASE):
title = Column(Unicode(100), nullable=False)
class PGProvider(BASE, BaseMixin):
"""
A Payment Gateway - The thing that processes a Payment Method
This is registered either by the Admin or by the PaymentGateway plugin
"""
__tablename__ = 'pg_provider'
name = Column(Unicode(60), nullable=False)
title = Column(Unicode(100))
description = Column(Unicode(255))
properties = Column(JSON)
methods = relationship(
'PGMethod',
backref='provider',
lazy='joined')
def method_map(self):
return self.attrs_map(['provider_methods'])
class PGMethod(BASE, BaseMixin):
"""
This represents a PaymentGatewayProviders method with some information
like name, type etc to describe what is in other settings known as a
"CreditCard"
Example:
A Visa card: {"type": "creditcard", "visa"}
"""
__tablename__ = 'pg_method'
name = Column(Unicode(100), nullable=False)
title = Column(Unicode(100))
description = Column(Unicode(255))
type = Column(Unicode(100), nullable=False)
properties = Column(JSON)
# NOTE: This is so a PGMethod can be "owned" by a Provider, meaning that
# other Providers should not be able to use it.
provider_id = Column(UUID, ForeignKey(
'pg_provider.id',
ondelete='CASCADE',
onupdate='CASCADE'))
@staticmethod
def make_key(data):
return '%(type)s:%(name)s' % data
def key(self):
return self.make_key(self)
class ContactInfo(BASE, BaseMixin):
"""
Contact Information about an entity like a User, Customer etc...
@ -146,20 +90,10 @@ class Merchant(BASE, BaseMixin):
title = Column(Unicode(60))
customers = relationship('Customer', backref='merchant')
payment_gateways = relationship(
'PGConfig', backref='merchant',
primaryjoin='merchant.c.id==pg_config.c.merchant_id')
plans = relationship('Plan', backref='merchant')
products = relationship('Product', backref='merchant')
default_gateway = relationship(
'PGConfig', uselist=False,
primaryjoin='merchant.c.id==pg_config.c.merchant_id')
default_gateway_id = Column(UUID, ForeignKey('pg_config.id',
use_alter=True, name='default_gateway'),
nullable=True)
currency = relationship('Currency', uselist=False, backref='merchants')
currency_name = Column(Unicode(10), ForeignKey('currency.name'),
nullable=False)
@ -169,27 +103,6 @@ class Merchant(BASE, BaseMixin):
nullable=False)
class PGConfig(BASE, BaseMixin):
"""
A Merchant's configuration of a PaymentGateway like api keys, url and more
"""
__tablename__ = 'pg_config'
name = Column(Unicode(100), nullable=False)
title = Column(Unicode(100))
properties = Column(JSON)
# Link to the Merchant
merchant_id = Column(UUID, ForeignKey('merchant.id'), nullable=False)
provider = relationship('PGProvider',
backref='merchant_configurations')
provider_id = Column(UUID, ForeignKey('pg_provider.id',
onupdate='CASCADE'),
nullable=False)
class Customer(BASE, BaseMixin):
"""
A Customer is linked to a Merchant and can have Users related to it
@ -200,8 +113,6 @@ class Customer(BASE, BaseMixin):
merchant_id = Column(UUID, ForeignKey('merchant.id', ondelete='CASCADE'),
nullable=False)
payment_methods = relationship('PaymentMethod', backref='customer')
contact_info = relationship(
'CustomerInfo',
backref='customer',
@ -225,22 +136,6 @@ class Customer(BASE, BaseMixin):
language_name = Column(Unicode(10), ForeignKey('language.name'))
class PaymentMethod(BASE, BaseMixin):
name = Column(Unicode(255), nullable=False)
identifier = Column(Unicode(255), nullable=False)
expires = Column(Unicode(255))
properties = Column(JSON)
customer_id = Column(UUID, ForeignKey('customer.id', onupdate='CASCADE'),
nullable=False)
provider_config = relationship('PGConfig', backref='payment_methods')
provider_config_id = Column(UUID, ForeignKey('pg_config.id',
onupdate='CASCADE'), nullable=False)
class Plan(BASE, BaseMixin):
"""
A Product collection like a "Virtual Web Cluster" with 10 servers
@ -330,6 +225,4 @@ class Subscription(BASE, BaseMixin):
customer_id = Column(UUID, ForeignKey('customer.id', ondelete='CASCADE'),
nullable=False)
payment_method = relationship('PaymentMethod', backref='subscriptions')
payment_method_id = Column(UUID, ForeignKey('payment_method.id',
ondelete='CASCADE', onupdate='CASCADE'))
payment_method_id = Column(UUID)

View File

@ -33,5 +33,62 @@ class CollectorAPI(proxy.RpcProxy):
topic=cfg.CONF.collector_topic,
default_version=self.BASE_RPC_VERSION)
# PGP
def list_pg_providers(self, ctxt, criterion=None):
return self.call(ctxt, self.make_msg('list_pg_providers',
criterion=criterion))
def get_pg_provider(self, ctxt, id_):
return self.call(ctxt, self.make_msg('get_pg_provider', id_=id_))
# PGM
def list_pg_methods(self, ctxt, criterion=None):
return self.call(ctxt, self.make_msg('list_pg_methods',
criterion=criterion))
def get_pg_method(self, ctxt, id_):
return self.call(ctxt, self.make_msg('get_pg_method', id_=id_))
def delete_pg_method(self, ctxt, id_):
return self.call(ctxt, self.make_msg('delete_pg_method', id_=id_))
# PGC
def create_pg_config(self, ctxt, values):
return self.call(ctxt, self.make_msg('create_pg_config',
values=values))
def list_pg_configs(self, ctxt, criterion=None):
return self.call(ctxt, self.make_msg('list_pg_configs',
criterion=criterion))
def get_pg_config(self, ctxt, id_):
return self.call(ctxt, self.make_msg('get_pg_config', id_=id_))
def update_pg_config(self, ctxt, id_, values):
return self.call(ctxt, self.make_msg('update_pg_config', id_=id_,
values=values))
def delete_pg_config(self, ctxt, id_):
return self.call(ctxt, self.make_msg('delete_pg_config', id_=id_))
# PaymentMethod
def create_payment_method(self, ctxt, values):
return self.call(ctxt, self.make_msg('create_payment_method',
values=values))
def list_payment_methods(self, ctxt, criterion=None):
return self.call(ctxt, self.make_msg('list_payment_methods',
criterion=criterion))
def get_payment_method(self, ctxt, id_):
return self.call(ctxt, self.make_msg('get_payment_method', id_=id_))
def update_payment_method(self, ctxt, id_, values):
return self.call(ctxt, self.make_msg('update_payment_method', id_=id_,
values=values))
def delete_payment_method(self, ctxt, id_):
return self.call(ctxt, self.make_msg('delete_payment_method', id_=id_))
collector_api = CollectorAPI()

View File

@ -23,6 +23,7 @@ from oslo.config import cfg
from billingstack.openstack.common import log as logging
from billingstack.openstack.common.rpc import service as rpc_service
from billingstack.openstack.common import service as os_service
from billingstack.storage.utils import get_connection
from billingstack.central.rpcapi import CentralAPI
from billingstack import service as bs_service
@ -47,26 +48,49 @@ class Service(rpc_service.Service):
# Get a storage connection
self.central_api = CentralAPI()
def start(self):
self.storage_conn = get_connection('collector')
super(Service, self).start()
def wait(self):
super(Service, self).wait()
self.conn.consumer_thread.wait()
def get_pg_provider(self, ctxt, pg_info):
"""
Work out a PGC config either from pg_info or via ctxt fetching it
from central.
Return the appropriate PGP for this info.
# PGP
def list_pg_providers(self, ctxt, **kw):
return self.storage_conn.list_pg_providers(ctxt, **kw)
:param ctxt: Request context
:param pg_info: Payment Gateway Config...
"""
# PGC
def create_pg_config(self, ctxt, values):
return self.storage_conn.create_pg_config(ctxt, values)
def create_account(self, ctxt, values, pg_config=None):
"""
Create an Account on the underlying provider
def list_pg_configs(self, ctxt, **kw):
return self.storage_conn.list_pg_configs(ctxt, **kw)
:param values: The account values
"""
def get_pg_config(self, ctxt, id_):
return self.storage_conn.get_pg_config(ctxt, id_)
def update_pg_config(self, ctxt, id_, values):
return self.storage_conn.update_pg_config(ctxt, id_, values)
def delete_pg_config(self, ctxt, id_):
return self.storage_conn.delete_pg_config(ctxt, id_)
# PM
def create_payment_method(self, ctxt, values):
return self.storage_conn.create_payment_method(ctxt, values)
def list_payment_methods(self, ctxt, **kw):
return self.storage_conn.list_payment_methods(ctxt, **kw)
def get_payment_method(self, ctxt, id_, **kw):
return self.storage_conn.get_payment_method(ctxt, id_)
def update_payment_method(self, ctxt, id_, values):
return self.storage_conn.update_payment_method(ctxt, id_, values)
def delete_payment_method(self, ctxt, id_):
return self.storage_conn.delete_payment_method(ctxt, id_)
def launch():

View File

@ -0,0 +1,108 @@
# -*- encoding: utf-8 -*-
#
# Author: Endre Karlson <endre.karlson@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from billingstack.storage import base
class StorageEngine(base.StorageEngine):
"""Base class for the collector storage"""
__plugin_ns__ = 'billingstack.collector.storage'
class Connection(base.Connection):
"""Define the base API for collector storage"""
def pg_provider_register(self):
"""
Register a Provider and it's Methods
"""
raise NotImplementedError
def list_pg_providers(self, ctxt, **kw):
"""
List available PG Providers
"""
raise NotImplementedError
def get_pg_provider(self, ctxt, id_):
"""
Get a PaymentGateway Provider
"""
raise NotImplementedError
def pg_provider_deregister(self, ctxt, id_):
"""
De-register a PaymentGateway Provider (Plugin) and all it's methods
"""
raise NotImplementedError
def create_pg_config(self, ctxt, values):
"""
Create a PaymentGateway Configuration
"""
raise NotImplementedError
def list_pg_configs(self, ctxt, **kw):
"""
List PaymentGateway Configurations
"""
raise NotImplementedError
def get_pg_config(self, ctxt, id_):
"""
Get a PaymentGateway Configuration
"""
raise NotImplementedError
def update_pg_config(self, ctxt, id_, values):
"""
Update a PaymentGateway Configuration
"""
raise NotImplementedError
def delete_pg_config(self, ctxt, id_):
"""
Delete a PaymentGateway Configuration
"""
raise NotImplementedError
def create_payment_method(self, ctxt, values):
"""
Configure a PaymentMethod like a CreditCard
"""
raise NotImplementedError
def list_payment_methods(self, ctxt, criterion=None, **kw):
"""
List a Customer's PaymentMethods
"""
raise NotImplementedError
def get_payment_method(self, ctxt, id_, **kw):
"""
Get a Customer's PaymentMethod
"""
raise NotImplementedError
def update_payment_method(self, ctxt, id_, values):
"""
Update a Customer's PaymentMethod
"""
raise NotImplementedError
def delete_payment_method(self, ctxt, id_):
"""
Delete a Customer's PaymentMethod
"""
raise NotImplementedError

View File

@ -0,0 +1,258 @@
# -*- encoding: utf-8 -*-
#
# Author: Endre Karlson <endre.karlson@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo.config import cfg
from sqlalchemy import Column, ForeignKey
from sqlalchemy import Unicode
from sqlalchemy.orm import exc, relationship
from sqlalchemy.ext.declarative import declarative_base
from billingstack.collector.storage import Connection, StorageEngine
from billingstack.openstack.common import log as logging
from billingstack.sqlalchemy.types import JSON, UUID
from billingstack.sqlalchemy import api, model_base, session, utils
LOG = logging.getLogger(__name__)
BASE = declarative_base(cls=model_base.ModelBase)
cfg.CONF.register_group(cfg.OptGroup(
name='collector:sqlalchemy',
title='Config for collector sqlalchemy plugin'))
cfg.CONF.register_opts(session.SQLOPTS, group='collector:sqlalchemy')
class PGProvider(BASE, model_base.BaseMixin):
"""
A Payment Gateway - The thing that processes a Payment Method
This is registered either by the Admin or by the PaymentGateway plugin
"""
__tablename__ = 'pg_provider'
name = Column(Unicode(60), nullable=False)
title = Column(Unicode(100))
description = Column(Unicode(255))
properties = Column(JSON)
methods = relationship(
'PGMethod',
backref='provider',
lazy='joined')
def method_map(self):
return self.attrs_map(['provider_methods'])
class PGMethod(BASE, model_base.BaseMixin):
"""
This represents a PaymentGatewayProviders method with some information
like name, type etc to describe what is in other settings known as a
"CreditCard"
Example:
A Visa card: {"type": "creditcard", "visa"}
"""
__tablename__ = 'pg_method'
name = Column(Unicode(100), nullable=False)
title = Column(Unicode(100))
description = Column(Unicode(255))
type = Column(Unicode(100), nullable=False)
properties = Column(JSON)
# NOTE: This is so a PGMethod can be "owned" by a Provider, meaning that
# other Providers should not be able to use it.
provider_id = Column(UUID, ForeignKey(
'pg_provider.id',
ondelete='CASCADE',
onupdate='CASCADE'))
@staticmethod
def make_key(data):
return '%(type)s:%(name)s' % data
def key(self):
return self.make_key(self)
class PGConfig(BASE, model_base.BaseMixin):
"""
A Merchant's configuration of a PaymentGateway like api keys, url and more
"""
__tablename__ = 'pg_config'
name = Column(Unicode(100), nullable=False)
title = Column(Unicode(100))
properties = Column(JSON)
# Link to the Merchant
merchant_id = Column(UUID, nullable=False)
provider = relationship('PGProvider',
backref='merchant_configurations')
provider_id = Column(UUID, ForeignKey('pg_provider.id',
onupdate='CASCADE'),
nullable=False)
class PaymentMethod(BASE, model_base.BaseMixin):
name = Column(Unicode(255), nullable=False)
identifier = Column(Unicode(255), nullable=False)
expires = Column(Unicode(255))
properties = Column(JSON)
customer_id = Column(UUID, nullable=False)
provider_config = relationship('PGConfig', backref='payment_methods',
lazy='joined')
provider_config_id = Column(UUID, ForeignKey('pg_config.id',
onupdate='CASCADE'), nullable=False)
class SQLAlchemyEngine(StorageEngine):
__plugin_name__ = 'sqlalchemy'
def get_connection(self):
return Connection()
class Connection(Connection, api.HelpersMixin):
def __init__(self):
self.setup('collector:sqlalchemy')
def base(self):
return BASE
# Payment Gateway Providers
def pg_provider_register(self, ctxt, values):
values = values.copy()
methods = values.pop('methods', [])
query = self.session.query(PGProvider)\
.filter_by(name=values['name'])
try:
provider = query.one()
except exc.NoResultFound:
provider = PGProvider()
provider.update(values)
self._set_provider_methods(ctxt, provider, methods)
self._save(provider)
return self._dict(provider, extra=['methods'])
def list_pg_providers(self, ctxt, **kw):
rows = self._list(PGProvider, **kw)
return [self._dict(r, extra=['methods']) for r in rows]
def get_pg_provider(self, ctxt, id_, **kw):
row = self._get(PGProvider, id_)
return self._dict(row, extra=['methods'])
def pg_provider_deregister(self, ctxt, id_):
self._delete(PGProvider, id_)
def _get_provider_methods(self, provider):
"""
Used internally to form a "Map" of the Providers methods
"""
methods = {}
for m in provider.methods:
methods[m.key()] = m
return methods
def _set_provider_methods(self, ctxt, provider, config_methods):
"""Helper method for setting the Methods for a Provider"""
existing = self._get_provider_methods(provider)
for method in config_methods:
self._set_method(provider, method, existing)
def _set_method(self, provider, method, existing):
key = PGMethod.make_key(method)
if key in existing:
existing[key].update(method)
else:
row = PGMethod(**method)
provider.methods.append(row)
# Payment Gateway Configuration
def create_pg_config(self, ctxt, values):
row = PGConfig(**values)
self._save(row)
return dict(row)
def list_pg_configs(self, ctxt, **kw):
rows = self._list(PGConfig, **kw)
return map(dict, rows)
def get_pg_config(self, ctxt, id_, **kw):
row = self._get(PGConfig, id_, **kw)
return dict(row)
def update_pg_config(self, ctxt, id_, values):
row = self._update(PGConfig, id_, values)
return dict(row)
def delete_pg_config(self, ctxt, id_):
self._delete(PGConfig, id_)
# PaymentMethod
def create_payment_method(self, ctxt, values):
row = PaymentMethod(**values)
self._save(row)
return self._dict(row)
def list_payment_methods(self, ctxt, criterion=None, **kw):
query = self.session.query(PaymentMethod)
# NOTE: Filter needs to be joined for merchant_id
query = utils.filter_merchant_by_join(
query, PGConfig, criterion)
rows = self._list(
cls=PaymentMethod,
query=query,
criterion=criterion,
**kw)
return [self._dict(row) for row in rows]
def get_payment_method(self, ctxt, id_, **kw):
row = self._get_id_or_name(PaymentMethod, id_)
return self._dict(row)
def update_payment_method(self, ctxt, id_, values):
row = self._update(PaymentMethod, id_, values)
return self._dict(row)
def delete_payment_method(self, ctxt, id_):
self._delete(PaymentMethod, id_)

View File

@ -31,7 +31,7 @@ class ProvidersRegister(DatabaseCommand):
class ProvidersList(DatabaseCommand, ListCommand):
def execute(self, parsed_args):
context = get_admin_context()
conn = self.get_connection('central')
conn = self.get_connection('collector')
data = conn.list_pg_providers(context)

View File

@ -47,7 +47,7 @@ def _register(ep, context, conn):
def register_providers(context):
conn = get_connection('central')
conn = get_connection('collector')
em = ExtensionManager(Provider.__plugin_ns__)
em.map(_register, context, conn)

View File

@ -13,7 +13,6 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo.config import cfg
from billingstack.storage import base
@ -38,9 +37,3 @@ class Connection(base.Connection):
def delete_usage(self, ctxt, id_):
raise NotImplementedError
def get_connection():
name = cfg.CONF['service:rater'].storage_driver
plugin = StorageEngine.get_plugin(name, invoke_on_load=True)
return plugin.get_connection()

View File

@ -42,3 +42,17 @@ def is_valid_id(id_):
return True
else:
return False
def filter_merchant_by_join(query, cls, criterion, pop=True):
if criterion and 'merchant_id' in criterion:
if not hasattr(cls, 'merchant_id'):
raise RuntimeError('No merchant_id attribute on %s' % cls)
query = query.join(cls).filter(
cls.merchant_id == criterion['merchant_id'])
if pop:
criterion.pop('merchant_id')
return query

View File

@ -162,6 +162,9 @@ class FunctionalTest(ServiceTestCase, APITestMixin):
# NOTE: Needs to be started after the db schema is created
self.start_storage('central')
self.start_service('central')
self.start_storage('collector')
self.start_service('collector')
self.setSamples()
self.app = self.make_app()

View File

@ -30,11 +30,16 @@ class TestPaymentMethod(V2Test):
def setUp(self):
super(TestPaymentMethod, self).setUp()
self.start_storage('collector')
self.start_service('collector')
_, self.provider = self.pg_provider_register()
_, self.customer = self.create_customer(self.merchant['id'])
_, self.pg_config = self.create_pg_config(
self.merchant['id'], values={'provider_id': self.provider['id']})
values = {
'provider_id': self.provider['id'],
'merchant_id': self.merchant['id']}
_, self.pg_config = self.create_pg_config(values=values)
def test_create_payment_method(self):
fixture = self.get_fixture('payment_method')
@ -48,9 +53,10 @@ class TestPaymentMethod(V2Test):
def test_list_payment_methods(self):
values = {
'provider_config_id': self.pg_config['id']
'provider_config_id': self.pg_config['id'],
'customer_id': self.customer['id']
}
self.create_payment_method(self.customer['id'], values=values)
self.create_payment_method(values=values)
url = self.path % (self.merchant['id'], self.customer['id'])
resp = self.get(url)
@ -59,10 +65,10 @@ class TestPaymentMethod(V2Test):
def test_get_payment_method(self):
values = {
'provider_config_id': self.pg_config['id']
'provider_config_id': self.pg_config['id'],
'customer_id': self.customer['id']
}
_, method = self.create_payment_method(
self.customer['id'], values=values)
_, method = self.create_payment_method(values=values)
url = self.item_path(self.merchant['id'],
self.customer['id'], method['id'])
@ -73,10 +79,10 @@ class TestPaymentMethod(V2Test):
def test_update_payment_method(self):
values = {
'provider_config_id': self.pg_config['id']
'provider_config_id': self.pg_config['id'],
'customer_id': self.customer['id']
}
fixture, method = self.create_payment_method(
self.customer['id'], values=values)
fixture, method = self.create_payment_method(values=values)
url = self.item_path(self.merchant['id'],
self.customer['id'], method['id'])
@ -87,10 +93,10 @@ class TestPaymentMethod(V2Test):
def test_delete_payment_method(self):
values = {
'provider_config_id': self.pg_config['id']
'provider_config_id': self.pg_config['id'],
'customer_id': self.customer['id']
}
_, method = self.create_payment_method(
self.customer['id'], values=values)
_, method = self.create_payment_method(values=values)
url = self.item_path(self.merchant['id'],
self.customer['id'], method['id'])

View File

@ -117,8 +117,12 @@ class SQLAlchemyHelper(FixtureHelper):
def post_init(self):
if self.fixture.database_connection == "sqlite://":
conn = self.fixture.connection.engine.connect()
self._as_string = "".join(
l for l in conn.connection.iterdump())
try:
self._as_string = "".join(
l for l in conn.connection.iterdump())
except Exception:
print "".join(l for l in conn.connection.iterdump())
raise
self.fixture.connection.engine.dispose()
else:
cleandb = paths.state_path_rel(self.sqlite_clean_db)
@ -338,8 +342,8 @@ class TestCase(BaseTestCase):
# NOTE: No services up by default
self.services = Services()
def get_admin_context(self):
return get_admin_context()
def get_admin_context(self, **kw):
return get_admin_context(**kw)
def get_context(self, **kw):
return RequestContext(**kw)
@ -431,7 +435,7 @@ class ServiceTestCase(TestCase):
fixture['methods'] = [self.get_fixture('pg_method')]
ctxt = kw.pop('context', self.admin_ctxt)
data = self.services.central.storage_conn.pg_provider_register(
data = self.services.collector.storage_conn.pg_provider_register(
ctxt, fixture, **kw)
return fixture, data
@ -445,12 +449,12 @@ class ServiceTestCase(TestCase):
return fixture, self.services.central.create_merchant(
ctxt, fixture, **kw)
def create_pg_config(self, merchant_id, fixture=0, values={},
def create_pg_config(self, fixture=0, values={},
**kw):
fixture = self.get_fixture('pg_config', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
return fixture, self.services.central.create_pg_config(
ctxt, merchant_id, fixture, **kw)
return fixture, self.services.collector.create_pg_config(
ctxt, fixture, **kw)
def create_customer(self, merchant_id, fixture=0, values={}, **kw):
fixture = self.get_fixture('customer', fixture, values)
@ -459,11 +463,11 @@ class ServiceTestCase(TestCase):
return fixture, self.services.central.create_customer(
ctxt, merchant_id, fixture, **kw)
def create_payment_method(self, customer_id, fixture=0, values={}, **kw):
def create_payment_method(self, fixture=0, values={}, **kw):
fixture = self.get_fixture('payment_method', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
return fixture, self.services.central.create_payment_method(
ctxt, customer_id, fixture, **kw)
return fixture, self.services.collector.create_payment_method(
ctxt, fixture, **kw)
def user_add(self, merchant_id, fixture=0, values={}, **kw):
fixture = self.get_fixture('user', fixture, values)

View File

@ -0,0 +1,249 @@
# -*- encoding: utf-8 -*-
#
# Author: Endre Karlson <endre.karlson@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from billingstack.openstack.common import log as logging
from billingstack.central.storage.impl_sqlalchemy import models
LOG = logging.getLogger(__name__)
UUID = 'caf771fc-6b05-4891-bee1-c2a48621f57b'
class DriverMixin(object):
def create_language(self, fixture=0, values={}, **kw):
fixture = self.get_fixture('language', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
return fixture, self.storage_conn.create_language(ctxt, fixture, **kw)
def create_currency(self, fixture=0, values={}, **kw):
fixture = self.get_fixture('currency', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
return fixture, self.storage_conn.create_currency(ctxt, fixture, **kw)
def create_merchant(self, fixture=0, values={}, **kw):
fixture = self.get_fixture('merchant', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
self._account_defaults(fixture)
return fixture, self.storage_conn.create_merchant(ctxt, fixture, **kw)
def create_customer(self, merchant_id, fixture=0, values={}, **kw):
fixture = self.get_fixture('customer', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
self._account_defaults(fixture)
return fixture, self.storage_conn.create_customer(
ctxt, merchant_id, fixture, **kw)
def create_product(self, merchant_id, fixture=0, values={}, **kw):
fixture = self.get_fixture('product', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
return fixture, self.storage_conn.create_product(
ctxt, merchant_id, fixture, **kw)
def create_plan(self, merchant_id, fixture=0, values={}, **kw):
fixture = self.get_fixture('plan', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
return fixture, self.storage_conn.create_plan(
ctxt, merchant_id, fixture, **kw)
# Currencies
def test_create_currency(self):
self.assertDuplicate(self.create_currency)
# Languages
def test_create_language(self):
self.assertDuplicate(self.create_language)
def test_set_properties(self):
fixture, data = self.create_product(self.merchant['id'])
metadata = {"random": True}
self.storage_conn.set_properties(data['id'], metadata,
cls=models.Product)
metadata.update({'foo': 1, 'bar': 2})
self.storage_conn.set_properties(data['id'], metadata,
cls=models.Product)
actual = self.storage_conn.get_product(self.admin_ctxt, data['id'])
self.assertLen(6, actual['properties'])
# Merchant
def test_create_merchant(self):
fixture, data = self.create_merchant()
self.assertData(fixture, data)
def test_get_merchant(self):
_, expected = self.create_merchant()
actual = self.storage_conn.get_merchant(
self.admin_ctxt, expected['id'])
self.assertData(expected, actual)
def test_get_merchant_missing(self):
self.assertMissing(self.storage_conn.get_merchant,
self.admin_ctxt, UUID)
def test_update_merchant(self):
fixture, data = self.create_merchant()
fixture['name'] = 'test'
updated = self.storage_conn.update_merchant(
self.admin_ctxt, data['id'], fixture)
self.assertData(fixture, updated)
def test_update_merchant_missing(self):
self.assertMissing(self.storage_conn.update_merchant,
self.admin_ctxt, UUID, {})
def test_delete_merchant(self):
self.storage_conn.delete_merchant(self.admin_ctxt, self.merchant['id'])
self.assertMissing(self.storage_conn.get_merchant,
self.admin_ctxt, self.merchant['id'])
def test_delete_merchant_missing(self):
self.assertMissing(self.storage_conn.delete_merchant,
self.admin_ctxt, UUID)
# Customer
def test_create_customer(self):
fixture, data = self.create_customer(self.merchant['id'])
assert data['default_info'] == {}
assert data['contact_info'] == []
self.assertData(fixture, data)
def test_create_customer_with_contact_info(self):
contact_fixture = self.get_fixture('contact_info')
customer_fixture, data = self.create_customer(
self.merchant['id'],
values={'contact_info': contact_fixture})
self.assertData(customer_fixture, data)
self.assertData(contact_fixture, data['default_info'])
self.assertData(contact_fixture, data['contact_info'][0])
def test_get_customer(self):
_, expected = self.create_customer(self.merchant['id'])
actual = self.storage_conn.get_customer(
self.admin_ctxt, expected['id'])
self.assertData(expected, actual)
def test_get_customer_missing(self):
self.assertMissing(self.storage_conn.get_customer,
self.admin_ctxt, UUID)
def test_update_customer(self):
fixture, data = self.create_customer(self.merchant['id'])
fixture['name'] = 'test'
updated = self.storage_conn.update_customer(
self.admin_ctxt, data['id'], fixture)
self.assertData(fixture, updated)
def test_update_customer_missing(self):
self.assertMissing(self.storage_conn.update_customer,
self.admin_ctxt, UUID, {})
def test_delete_customer(self):
_, data = self.create_customer(self.merchant['id'])
self.storage_conn.delete_customer(self.admin_ctxt, data['id'])
self.assertMissing(self.storage_conn.get_customer,
self.admin_ctxt, data['id'])
def test_delete_customer_missing(self):
self.assertMissing(self.storage_conn.delete_customer,
self.admin_ctxt, UUID)
# Products
def test_create_product(self):
f, data = self.create_product(self.merchant['id'])
self.assertData(f, data)
def test_get_product(self):
f, expected = self.create_product(self.merchant['id'])
actual = self.storage_conn.get_product(self.admin_ctxt, expected['id'])
self.assertData(expected, actual)
def test_get_product_missing(self):
self.assertMissing(self.storage_conn.get_product,
self.admin_ctxt, UUID)
def test_update_product(self):
fixture, data = self.create_product(self.merchant['id'])
fixture['name'] = 'test'
updated = self.storage_conn.update_product(
self.admin_ctxt, data['id'], fixture)
self.assertData(fixture, updated)
def test_update_product_missing(self):
self.assertMissing(self.storage_conn.update_product,
self.admin_ctxt, UUID, {})
def test_delete_product(self):
fixture, data = self.create_product(self.merchant['id'])
self.storage_conn.delete_product(self.admin_ctxt, data['id'])
self.assertMissing(self.storage_conn.get_product,
self.admin_ctxt, data['id'])
def test_delete_product_missing(self):
self.assertMissing(self.storage_conn.delete_product,
self.admin_ctxt, UUID)
# Plan
def test_create_plan(self):
fixture, data = self.create_plan(self.merchant['id'])
self.assertData(fixture, data)
def test_get_plan(self):
fixture, data = self.create_plan(self.merchant['id'])
actual = self.storage_conn.get_plan(self.admin_ctxt, data['id'])
# FIXME(ekarlso): This should test the actual items also? But atm
# there's am error that if the value is int when getting added it's
# string when returned...
self.assertEqual(data['name'], actual['name'])
self.assertEqual(data['title'], actual['title'])
self.assertEqual(data['description'], actual['description'])
def test_get_plan_missing(self):
self.assertMissing(self.storage_conn.get_plan, self.admin_ctxt, UUID)
def test_update_plan(self):
fixture, data = self.create_plan(self.merchant['id'])
fixture['name'] = 'test'
updated = self.storage_conn.update_plan(
self.admin_ctxt, data['id'], fixture)
self.assertData(fixture, updated)
def test_update_plan_missing(self):
self.assertMissing(self.storage_conn.update_plan,
self.admin_ctxt, UUID, {})
def test_delete_plan(self):
fixture, data = self.create_plan(self.merchant['id'])
self.storage_conn.delete_plan(self.admin_ctxt, data['id'])
self.assertMissing(self.storage_conn.get_plan,
self.admin_ctxt, data['id'])
def test_delete_plan_missing(self):
self.assertMissing(self.storage_conn.delete_plan,
self.admin_ctxt, UUID)

View File

@ -1,493 +0,0 @@
# -*- encoding: utf-8 -*-
#
# Author: Endre Karlson <endre.karlson@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from billingstack.openstack.common import log as logging
from billingstack.central.storage.impl_sqlalchemy import models
LOG = logging.getLogger(__name__)
UUID = 'caf771fc-6b05-4891-bee1-c2a48621f57b'
class DriverMixin(object):
def create_language(self, fixture=0, values={}, **kw):
fixture = self.get_fixture('language', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
return fixture, self.storage_conn.create_language(ctxt, fixture, **kw)
def create_currency(self, fixture=0, values={}, **kw):
fixture = self.get_fixture('currency', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
return fixture, self.storage_conn.create_currency(ctxt, fixture, **kw)
def pg_provider_register(self, fixture=0, values={}, methods=[], **kw):
methods = [self.get_fixture('pg_method')] or methods
if not 'methods' in values:
values['methods'] = methods
fixture = self.get_fixture('pg_provider', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
data = self.storage_conn.pg_provider_register(
ctxt, fixture.copy(), **kw)
return fixture, data
def create_merchant(self, fixture=0, values={}, **kw):
fixture = self.get_fixture('merchant', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
self._account_defaults(fixture)
return fixture, self.storage_conn.create_merchant(ctxt, fixture, **kw)
def create_pg_config(self, merchant_id, fixture=0, values={},
**kw):
fixture = self.get_fixture('pg_config', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
return fixture, self.storage_conn.create_pg_config(
ctxt, merchant_id, fixture, **kw)
def create_customer(self, merchant_id, fixture=0, values={}, **kw):
fixture = self.get_fixture('customer', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
self._account_defaults(fixture)
return fixture, self.storage_conn.create_customer(
ctxt, merchant_id, fixture, **kw)
def create_payment_method(self, customer_id, fixture=0,
values={}, **kw):
fixture = self.get_fixture('payment_method', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
return fixture, self.storage_conn.create_payment_method(
ctxt, customer_id, fixture, **kw)
def create_product(self, merchant_id, fixture=0, values={}, **kw):
fixture = self.get_fixture('product', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
return fixture, self.storage_conn.create_product(
ctxt, merchant_id, fixture, **kw)
def create_plan(self, merchant_id, fixture=0, values={}, **kw):
fixture = self.get_fixture('plan', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
return fixture, self.storage_conn.create_plan(
ctxt, merchant_id, fixture, **kw)
# Currencies
def test_create_currency(self):
self.assertDuplicate(self.create_currency)
# Languages
def test_create_language(self):
self.assertDuplicate(self.create_language)
def test_set_properties(self):
fixture, data = self.create_product(self.merchant['id'])
metadata = {"random": True}
self.storage_conn.set_properties(data['id'], metadata,
cls=models.Product)
metadata.update({'foo': 1, 'bar': 2})
self.storage_conn.set_properties(data['id'], metadata,
cls=models.Product)
actual = self.storage_conn.get_product(self.admin_ctxt, data['id'])
self.assertLen(6, actual['properties'])
# Payment Gateways
def test_pg_provider_register(self):
fixture, actual = self.pg_provider_register()
self.assertEqual(fixture['name'], actual['name'])
self.assertEqual(fixture['title'], actual['title'])
self.assertEqual(fixture['description'], actual['description'])
self.assertData(fixture['methods'][0], actual['methods'][0])
def test_pg_provider_register_different_methods(self):
# Add a Global method
method1 = {'type': 'creditcard', 'name': 'mastercard'}
method2 = {'type': 'creditcard', 'name': 'amex'}
method3 = {'type': 'creditcard', 'name': 'visa'}
provider = {'name': 'noop', 'methods': [method1, method2, method3]}
provider = self.storage_conn.pg_provider_register(
self.admin_ctxt, provider)
# TODO(ekarls): Make this more extensive?
self.assertLen(3, provider['methods'])
def test_get_pg_provider(self):
_, expected = self.pg_provider_register()
actual = self.storage_conn.get_pg_provider(self.admin_ctxt,
expected['id'])
self.assertData(expected, actual)
def test_get_pg_provider_missing(self):
self.assertMissing(self.storage_conn.get_pg_provider,
self.admin_ctxt, UUID)
def test_pg_provider_deregister(self):
_, data = self.pg_provider_register()
self.storage_conn.pg_provider_deregister(self.admin_ctxt, data['id'])
self.assertMissing(self.storage_conn.pg_provider_deregister,
self.admin_ctxt, data['id'])
def test_pg_provider_deregister_missing(self):
self.assertMissing(self.storage_conn.pg_provider_deregister,
self.admin_ctxt, UUID)
# Payment Gateway Configuration
def test_create_pg_config(self):
_, provider = self.pg_provider_register()
values = {'provider_id': provider['id']}
fixture, data = self.create_pg_config(
self.merchant['id'], values=values)
self.assertData(fixture, data)
def test_get_pg_config(self):
_, provider = self.pg_provider_register()
values = {'provider_id': provider['id']}
fixture, data = self.create_pg_config(
self.merchant['id'], values=values)
def test_get_pg_config_missing(self):
self.assertMissing(self.storage_conn.get_pg_config,
self.admin_ctxt, UUID)
def test_update_pg_config(self):
_, provider = self.pg_provider_register()
values = {'provider_id': provider['id']}
fixture, data = self.create_pg_config(
self.merchant['id'], values=values)
fixture['properties'] = {"api": 1}
updated = self.storage_conn.update_pg_config(
self.admin_ctxt, data['id'], fixture)
self.assertData(fixture, updated)
def test_update_pg_config_missing(self):
_, provider = self.pg_provider_register()
values = {'provider_id': provider['id']}
fixture, data = self.create_pg_config(
self.merchant['id'], values=values)
self.assertMissing(self.storage_conn.update_pg_config,
self.admin_ctxt, UUID, {})
def test_delete_pg_config(self):
_, provider = self.pg_provider_register()
values = {'provider_id': provider['id']}
fixture, data = self.create_pg_config(
self.merchant['id'], values=values)
self.storage_conn.delete_pg_config(self.admin_ctxt, data['id'])
self.assertMissing(self.storage_conn.get_pg_config,
self.admin_ctxt, data['id'])
def test_delete_pg_config_missing(self):
self.assertMissing(self.storage_conn.delete_pg_config,
self.admin_ctxt, UUID)
# PaymentMethod
def test_create_payment_method(self):
# Setup pgp / pgm / pgc
_, provider = self.pg_provider_register()
_, config = self.create_pg_config(
self.merchant['id'], values={'provider_id': provider['id']})
_, customer = self.create_customer(self.merchant['id'])
# Setup PaymentMethod
values = {
'provider_config_id': config['id']}
fixture, data = self.create_payment_method(
customer['id'], values=values)
self.assertData(fixture, data)
def test_get_payment_method(self):
# Setup pgp / pgm / pgc
_, provider = self.pg_provider_register()
_, config = self.create_pg_config(
self.merchant['id'], values={'provider_id': provider['id']})
_, customer = self.create_customer(self.merchant['id'])
# Setup PaymentMethod
values = {
'provider_config_id': config['id']}
_, expected = self.create_payment_method(
customer['id'], values=values)
actual = self.storage_conn.get_payment_method(self.admin_ctxt,
expected['id'])
self.assertData(expected, actual)
# TODO(ekarlso): Make this test more extensive?
def test_list_payment_methods(self):
# Setup pgp / pgm / pgc
_, provider = self.pg_provider_register()
_, config = self.create_pg_config(
self.merchant['id'], values={'provider_id': provider['id']})
values = {
'provider_config_id': config['id']}
# Add two Customers with some methods
_, customer1 = self.create_customer(self.merchant['id'])
self.create_payment_method(
customer1['id'], values=values)
rows = self.storage_conn.list_payment_methods(
self.admin_ctxt,
criterion={'customer_id': customer1['id']})
self.assertLen(1, rows)
_, customer2 = self.create_customer(self.merchant['id'])
self.create_payment_method(
customer2['id'], values=values)
self.create_payment_method(
customer2['id'], values=values)
rows = self.storage_conn.list_payment_methods(
self.admin_ctxt,
criterion={'customer_id': customer2['id']})
self.assertLen(2, rows)
def test_get_payment_method_missing(self):
self.assertMissing(self.storage_conn.get_payment_method,
self.admin_ctxt, UUID)
def test_update_payment_method(self):
# Setup pgp / pgm / pgc
_, provider = self.pg_provider_register()
_, config = self.create_pg_config(
self.merchant['id'], values={'provider_id': provider['id']})
_, customer = self.create_customer(self.merchant['id'])
# Setup PaymentMethod
values = {
'provider_config_id': config['id']}
fixture, data = self.create_payment_method(
customer['id'], values=values)
fixture['identifier'] = 1
updated = self.storage_conn.update_payment_method(self.admin_ctxt,
data['id'], fixture)
self.assertData(fixture, updated)
def test_update_payment_method_missing(self):
self.assertMissing(self.storage_conn.update_payment_method,
self.admin_ctxt, UUID, {})
def test_delete_payment_method(self):
# Setup pgp / pgm / pgc
_, provider = self.pg_provider_register()
_, config = self.create_pg_config(
self.merchant['id'], values={'provider_id': provider['id']})
_, customer = self.create_customer(self.merchant['id'])
# Setup PaymentMethod
values = {
'provider_config_id': config['id']}
fixture, data = self.create_payment_method(
customer['id'], values=values)
self.storage_conn.delete_payment_method(self.admin_ctxt, data['id'])
self.assertMissing(self.storage_conn.get_payment_method,
self.admin_ctxt, data['id'])
def test_delete_payment_method_missing(self):
self.assertMissing(self.storage_conn.delete_payment_method,
self.admin_ctxt, UUID)
# Merchant
def test_create_merchant(self):
fixture, data = self.create_merchant()
self.assertData(fixture, data)
def test_get_merchant(self):
_, expected = self.create_merchant()
actual = self.storage_conn.get_merchant(
self.admin_ctxt, expected['id'])
self.assertData(expected, actual)
def test_get_merchant_missing(self):
self.assertMissing(self.storage_conn.get_merchant,
self.admin_ctxt, UUID)
def test_update_merchant(self):
fixture, data = self.create_merchant()
fixture['name'] = 'test'
updated = self.storage_conn.update_merchant(
self.admin_ctxt, data['id'], fixture)
self.assertData(fixture, updated)
def test_update_merchant_missing(self):
self.assertMissing(self.storage_conn.update_merchant,
self.admin_ctxt, UUID, {})
def test_delete_merchant(self):
self.storage_conn.delete_merchant(self.admin_ctxt, self.merchant['id'])
self.assertMissing(self.storage_conn.get_merchant,
self.admin_ctxt, self.merchant['id'])
def test_delete_merchant_missing(self):
self.assertMissing(self.storage_conn.delete_merchant,
self.admin_ctxt, UUID)
# Customer
def test_create_customer(self):
fixture, data = self.create_customer(self.merchant['id'])
assert data['default_info'] == {}
assert data['contact_info'] == []
self.assertData(fixture, data)
def test_create_customer_with_contact_info(self):
contact_fixture = self.get_fixture('contact_info')
customer_fixture, data = self.create_customer(
self.merchant['id'],
values={'contact_info': contact_fixture})
self.assertData(customer_fixture, data)
self.assertData(contact_fixture, data['default_info'])
self.assertData(contact_fixture, data['contact_info'][0])
def test_get_customer(self):
_, expected = self.create_customer(self.merchant['id'])
actual = self.storage_conn.get_customer(
self.admin_ctxt, expected['id'])
self.assertData(expected, actual)
def test_get_customer_missing(self):
self.assertMissing(self.storage_conn.get_customer,
self.admin_ctxt, UUID)
def test_update_customer(self):
fixture, data = self.create_customer(self.merchant['id'])
fixture['name'] = 'test'
updated = self.storage_conn.update_customer(
self.admin_ctxt, data['id'], fixture)
self.assertData(fixture, updated)
def test_update_customer_missing(self):
self.assertMissing(self.storage_conn.update_customer,
self.admin_ctxt, UUID, {})
def test_delete_customer(self):
_, data = self.create_customer(self.merchant['id'])
self.storage_conn.delete_customer(self.admin_ctxt, data['id'])
self.assertMissing(self.storage_conn.get_customer,
self.admin_ctxt, data['id'])
def test_delete_customer_missing(self):
self.assertMissing(self.storage_conn.delete_customer,
self.admin_ctxt, UUID)
# Products
def test_create_product(self):
f, data = self.create_product(self.merchant['id'])
self.assertData(f, data)
def test_get_product(self):
f, expected = self.create_product(self.merchant['id'])
actual = self.storage_conn.get_product(self.admin_ctxt, expected['id'])
self.assertData(expected, actual)
def test_get_product_missing(self):
self.assertMissing(self.storage_conn.get_product,
self.admin_ctxt, UUID)
def test_update_product(self):
fixture, data = self.create_product(self.merchant['id'])
fixture['name'] = 'test'
updated = self.storage_conn.update_product(
self.admin_ctxt, data['id'], fixture)
self.assertData(fixture, updated)
def test_update_product_missing(self):
self.assertMissing(self.storage_conn.update_product,
self.admin_ctxt, UUID, {})
def test_delete_product(self):
fixture, data = self.create_product(self.merchant['id'])
self.storage_conn.delete_product(self.admin_ctxt, data['id'])
self.assertMissing(self.storage_conn.get_product,
self.admin_ctxt, data['id'])
def test_delete_product_missing(self):
self.assertMissing(self.storage_conn.delete_product,
self.admin_ctxt, UUID)
# Plan
def test_create_plan(self):
fixture, data = self.create_plan(self.merchant['id'])
self.assertData(fixture, data)
def test_get_plan(self):
fixture, data = self.create_plan(self.merchant['id'])
actual = self.storage_conn.get_plan(self.admin_ctxt, data['id'])
# FIXME(ekarlso): This should test the actual items also? But atm
# there's am error that if the value is int when getting added it's
# string when returned...
self.assertEqual(data['name'], actual['name'])
self.assertEqual(data['title'], actual['title'])
self.assertEqual(data['description'], actual['description'])
def test_get_plan_missing(self):
self.assertMissing(self.storage_conn.get_plan, self.admin_ctxt, UUID)
def test_update_plan(self):
fixture, data = self.create_plan(self.merchant['id'])
fixture['name'] = 'test'
updated = self.storage_conn.update_plan(
self.admin_ctxt, data['id'], fixture)
self.assertData(fixture, updated)
def test_update_plan_missing(self):
self.assertMissing(self.storage_conn.update_plan,
self.admin_ctxt, UUID, {})
def test_delete_plan(self):
fixture, data = self.create_plan(self.merchant['id'])
self.storage_conn.delete_plan(self.admin_ctxt, data['id'])
self.assertMissing(self.storage_conn.get_plan,
self.admin_ctxt, data['id'])
def test_delete_plan_missing(self):
self.assertMissing(self.storage_conn.delete_plan,
self.admin_ctxt, UUID)

View File

@ -17,7 +17,7 @@
# Copied: billingstack
from billingstack.openstack.common import log as logging
from billingstack.tests.base import TestCase
from billingstack.tests.central.storage.base import DriverMixin
from billingstack.tests.central.storage import DriverMixin
LOG = logging.getLogger(__name__)

View File

@ -0,0 +1,293 @@
# -*- encoding: utf-8 -*-
#
# Author: Endre Karlson <endre.karlson@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from billingstack.openstack.common import log as logging
from billingstack.openstack.common.uuidutils import generate_uuid
LOG = logging.getLogger(__name__)
UUID = generate_uuid()
MERCHANT_UUID = generate_uuid()
CUSTOMER_UUID = generate_uuid()
class DriverMixin(object):
def pg_provider_register(self, fixture=0, values={}, methods=[], **kw):
methods = [self.get_fixture('pg_method')] or methods
if not 'methods' in values:
values['methods'] = methods
fixture = self.get_fixture('pg_provider', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
data = self.storage_conn.pg_provider_register(
ctxt, fixture.copy(), **kw)
return fixture, data
def create_pg_config(self, fixture=0, values={},
**kw):
fixture = self.get_fixture('pg_config', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
return fixture, self.storage_conn.create_pg_config(
ctxt, fixture, **kw)
def create_payment_method(self, fixture=0,
values={}, **kw):
fixture = self.get_fixture('payment_method', fixture, values)
ctxt = kw.pop('context', self.admin_ctxt)
return fixture, self.storage_conn.create_payment_method(
ctxt, fixture, **kw)
# Payment Gateways
def test_pg_provider_register(self):
fixture, actual = self.pg_provider_register()
self.assertEqual(fixture['name'], actual['name'])
self.assertEqual(fixture['title'], actual['title'])
self.assertEqual(fixture['description'], actual['description'])
self.assertData(fixture['methods'][0], actual['methods'][0])
def test_pg_provider_register_different_methods(self):
# Add a Global method
method1 = {'type': 'creditcard', 'name': 'mastercard'}
method2 = {'type': 'creditcard', 'name': 'amex'}
method3 = {'type': 'creditcard', 'name': 'visa'}
provider = {'name': 'noop', 'methods': [method1, method2, method3]}
provider = self.storage_conn.pg_provider_register(
self.admin_ctxt, provider)
# TODO(ekarls): Make this more extensive?
self.assertLen(3, provider['methods'])
def test_get_pg_provider(self):
_, expected = self.pg_provider_register()
actual = self.storage_conn.get_pg_provider(self.admin_ctxt,
expected['id'])
self.assertData(expected, actual)
def test_get_pg_provider_missing(self):
self.assertMissing(self.storage_conn.get_pg_provider,
self.admin_ctxt, UUID)
def test_pg_provider_deregister(self):
_, data = self.pg_provider_register()
self.storage_conn.pg_provider_deregister(self.admin_ctxt, data['id'])
self.assertMissing(self.storage_conn.pg_provider_deregister,
self.admin_ctxt, data['id'])
def test_pg_provider_deregister_missing(self):
self.assertMissing(self.storage_conn.pg_provider_deregister,
self.admin_ctxt, UUID)
# Payment Gateway Configuration
def test_create_pg_config(self):
_, provider = self.pg_provider_register()
values = {
'merchant_id': MERCHANT_UUID,
'provider_id': provider['id']}
fixture, data = self.create_pg_config(values=values)
self.assertData(fixture, data)
def test_get_pg_config(self):
_, provider = self.pg_provider_register()
values = {
'merchant_id': MERCHANT_UUID,
'provider_id': provider['id']}
fixture, data = self.create_pg_config(values=values)
def test_get_pg_config_missing(self):
self.assertMissing(self.storage_conn.get_pg_config,
self.admin_ctxt, UUID)
def test_update_pg_config(self):
_, provider = self.pg_provider_register()
values = {
'merchant_id': MERCHANT_UUID,
'provider_id': provider['id']}
fixture, data = self.create_pg_config(values=values)
fixture['properties'] = {"api": 1}
updated = self.storage_conn.update_pg_config(
self.admin_ctxt, data['id'], fixture)
self.assertData(fixture, updated)
def test_update_pg_config_missing(self):
_, provider = self.pg_provider_register()
values = {
'merchant_id': MERCHANT_UUID,
'provider_id': provider['id']}
fixture, data = self.create_pg_config(values=values)
self.assertMissing(self.storage_conn.update_pg_config,
self.admin_ctxt, UUID, {})
def test_delete_pg_config(self):
_, provider = self.pg_provider_register()
values = {
'merchant_id': MERCHANT_UUID,
'provider_id': provider['id']}
fixture, data = self.create_pg_config(values=values)
self.storage_conn.delete_pg_config(self.admin_ctxt, data['id'])
self.assertMissing(self.storage_conn.get_pg_config,
self.admin_ctxt, data['id'])
def test_delete_pg_config_missing(self):
self.assertMissing(self.storage_conn.delete_pg_config,
self.admin_ctxt, UUID)
# PaymentMethod
def test_create_payment_method(self):
# Setup pgp / pgm / pgc
_, provider = self.pg_provider_register()
values = {
'merchant_id': MERCHANT_UUID,
'provider_id': provider['id']
}
_, config = self.create_pg_config(values=values)
# Setup PaymentMethod
values = {
'customer_id': CUSTOMER_UUID,
'provider_config_id': config['id']}
fixture, data = self.create_payment_method(values=values)
self.assertData(fixture, data)
def test_get_payment_method(self):
# Setup pgp / pgm / pgc
_, provider = self.pg_provider_register()
values = {
'merchant_id': MERCHANT_UUID,
'provider_id': provider['id']
}
_, config = self.create_pg_config(values=values)
# Setup PaymentMethod
values = {
'customer_id': CUSTOMER_UUID,
'provider_config_id': config['id']}
_, expected = self.create_payment_method(values=values)
actual = self.storage_conn.get_payment_method(self.admin_ctxt,
expected['id'])
self.assertData(expected, actual)
# TODO(ekarlso): Make this test more extensive?
def test_list_payment_methods(self):
# Setup pgp / pgm / pgc
_, provider = self.pg_provider_register()
values = {
'merchant_id': MERCHANT_UUID,
'provider_id': provider['id']
}
_, config = self.create_pg_config(values=values)
# Add two Customers with some methods
customer1_id = generate_uuid()
values = {
'customer_id': customer1_id,
'provider_config_id': config['id']}
self.create_payment_method(values=values)
rows = self.storage_conn.list_payment_methods(
self.admin_ctxt,
criterion={'customer_id': customer1_id})
self.assertLen(1, rows)
customer2_id = generate_uuid()
values = {
'customer_id': customer2_id,
'provider_config_id': config['id']}
self.create_payment_method(values=values)
self.create_payment_method(values=values)
rows = self.storage_conn.list_payment_methods(
self.admin_ctxt,
criterion={'customer_id': customer2_id})
self.assertLen(2, rows)
def test_get_payment_method_missing(self):
self.assertMissing(self.storage_conn.get_payment_method,
self.admin_ctxt, UUID)
def test_update_payment_method(self):
# Setup pgp / pgm / pgc
_, provider = self.pg_provider_register()
values = {
'merchant_id': MERCHANT_UUID,
'provider_id': provider['id']
}
_, config = self.create_pg_config(values=values)
# Setup PaymentMethod
values = {
'customer_id': CUSTOMER_UUID,
'provider_config_id': config['id']}
fixture, data = self.create_payment_method(values=values)
fixture['identifier'] = 1
updated = self.storage_conn.update_payment_method(
self.admin_ctxt,
data['id'],
fixture)
self.assertData(fixture, updated)
def test_update_payment_method_missing(self):
self.assertMissing(self.storage_conn.update_payment_method,
self.admin_ctxt, UUID, {})
def test_delete_payment_method(self):
# Setup pgp / pgm / pgc
_, provider = self.pg_provider_register()
values = {
'merchant_id': MERCHANT_UUID,
'provider_id': provider['id']
}
_, config = self.create_pg_config(values=values)
# Setup PaymentMethod
values = {
'customer_id': CUSTOMER_UUID,
'provider_config_id': config['id']}
fixture, data = self.create_payment_method(values=values)
self.storage_conn.delete_payment_method(self.admin_ctxt, data['id'])
self.assertMissing(self.storage_conn.get_payment_method,
self.admin_ctxt, data['id'])
def test_delete_payment_method_missing(self):
self.assertMissing(self.storage_conn.delete_payment_method,
self.admin_ctxt, UUID)

View File

@ -0,0 +1,29 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# Copied: billingstack
from billingstack.openstack.common import log as logging
from billingstack.tests.base import TestCase
from billingstack.tests.collector.storage import DriverMixin
LOG = logging.getLogger(__name__)
class SqlalchemyStorageTest(DriverMixin, TestCase):
def setUp(self):
super(SqlalchemyStorageTest, self).setUp()
fixture = self.start_storage('collector')
self.storage_conn = fixture.connection

View File

@ -39,6 +39,10 @@ console_scripts =
billingstack.central.storage =
sqlalchemy = billingstack.central.storage.impl_sqlalchemy:SQLAlchemyEngine
billingstack.collector.storage =
sqlalchemy = billingstack.collector.storage.impl_sqlalchemy:SQLAlchemyEngine
billingstack.biller.storage =
sqlalchemy = billingstack.biller.storage.impl_sqlalchemy:SQLAlchemyEngine

View File

@ -9,7 +9,7 @@ from billingstack import service
from billingstack.storage.utils import get_connection
# NOTE: make this based on entrypoints ?
SERVICES = ['biller', 'central', 'rater']
SERVICES = ['biller', 'central', 'collector', 'rater']
LOG = logging.getLogger(__name__)