Make Odoo as a driver of ERP
This feature is trying to make the back end ERP driver plugable. As a result, the current interaction with Odoo will be capsulated as an ERP driver. Implement blueprint: erp-driver Change-Id: I58e1a3d1f47806b8ffeeb7244fe8806683efaced
This commit is contained in:
parent
6d1dec6cd9
commit
5077347972
|
@ -38,6 +38,10 @@ DEFAULT_OPTIONS = (
|
|||
default=[],
|
||||
help=('The tenant name list which will be ignored when '
|
||||
'collecting metrics from Ceilometer.')),
|
||||
cfg.StrOpt('erp_driver',
|
||||
default='odoo',
|
||||
help='The ERP driver used for Distil',
|
||||
),
|
||||
)
|
||||
|
||||
COLLECTOR_OPTS = [
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
# Copyright 2017 Catalyst IT Ltd
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
class BaseDriver(object):
|
||||
"""Base class for ERP drivers.
|
||||
"""
|
||||
conf = None
|
||||
|
||||
def __init__(self, conf):
|
||||
self.conf = conf
|
||||
|
||||
def get_salesOrders(self, project, start_at, end_at):
|
||||
"""List sales orders based on the given project and time range
|
||||
|
||||
:param project: project id
|
||||
:param start_at: start time
|
||||
:param end_at: end time
|
||||
:returns List of sales order, if the time range only cover one month,
|
||||
then, the list will only contain 1 sale orders. Otherwise,
|
||||
the length of the list depends on the months number of the
|
||||
time range.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_products(self, regions=None):
|
||||
"""List products based o given regions
|
||||
|
||||
:param regions: List of regions to get projects
|
||||
:returns Dict of products based on the given regions
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_product(self, product):
|
||||
"""Create product in Odoo.
|
||||
|
||||
:param product: info used to create product
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_credits(self, project):
|
||||
"""Get project credits
|
||||
|
||||
:param instance: nova.objects.instance.Instance
|
||||
:returns list of credits current project can get
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_credit(self, project, credit):
|
||||
"""Create credit for a given project
|
||||
|
||||
:param project: project
|
||||
"""
|
||||
raise NotImplementedError()
|
|
@ -0,0 +1,97 @@
|
|||
# Copyright (c) 2016 Catalyst IT Ltd.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import odoorpc
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from distil.erp import driver
|
||||
from distil.common import openstack
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
PRODUCT_CATEGORY = ('Compute', 'Network', 'Block Storage', 'Object Storage')
|
||||
|
||||
|
||||
class OdooDriver(driver.BaseDriver):
|
||||
|
||||
def __init__(self, conf):
|
||||
self.odoo = odoorpc.ODOO(conf.odoo.hostname,
|
||||
protocol=conf.odoo.protocol,
|
||||
port=conf.odoo.port,
|
||||
version=conf.odoo.version)
|
||||
self.odoo.login(conf.odoo.database, conf.odoo.user, conf.odoo.password)
|
||||
|
||||
# NOTE(flwang): This is not necessary for most of cases, but just in
|
||||
# case some cloud providers are using different region name formats in
|
||||
# Keystone and Odoo.
|
||||
if conf.odoo.region_mapping:
|
||||
regions = conf.odoo.region_mapping.split(',')
|
||||
self.region_mapping = dict([(r.split(":")[0].strip(),
|
||||
r.split(":")[1].strip())
|
||||
for r in regions])
|
||||
|
||||
self.order = self.odoo.env['sale.order']
|
||||
self.orderline = self.odoo.env['sale.order.line']
|
||||
self.tenant = self.odoo.env['cloud.tenant']
|
||||
self.partner = self.odoo.env['res.partner']
|
||||
self.pricelist = self.odoo.env['product.pricelist']
|
||||
self.product = self.odoo.env['product.product']
|
||||
self.category = self.odoo.env['product.category']
|
||||
|
||||
def get_products(self, regions=None):
|
||||
if not regions:
|
||||
regions = [r.id for r in openstack.get_regions()]
|
||||
if hasattr(self, 'region_mapping'):
|
||||
regions = self.region_mapping.values()
|
||||
|
||||
else:
|
||||
if hasattr(self, 'region_mapping'):
|
||||
regions = [self.region_mapping.get(r) for r in regions]
|
||||
|
||||
prices = {}
|
||||
try:
|
||||
for r in regions:
|
||||
if not r:
|
||||
continue
|
||||
prices[r] = {}
|
||||
for cat in PRODUCT_CATEGORY:
|
||||
prices[r][cat.lower()] = []
|
||||
c = self.category.search([('name', '=', cat),
|
||||
('display_name', 'ilike', r)])
|
||||
product_ids = self.product.search([('categ_id', '=', c[0]),
|
||||
('sale_ok', '=', True),
|
||||
('active', '=', True)])
|
||||
products = self.odoo.execute('product.product',
|
||||
'read',
|
||||
product_ids)
|
||||
for p in products:
|
||||
name = p['name_template'][len(r) + 1:]
|
||||
if 'pre-prod' in name:
|
||||
continue
|
||||
price = round(p['lst_price'], 5)
|
||||
# NOTE(flwang): default_code is Internal Reference on
|
||||
# Odoo GUI
|
||||
unit = p['default_code']
|
||||
desc = p['description']
|
||||
prices[r][cat.lower()].append({'resource': name,
|
||||
'price': price,
|
||||
'unit': unit,
|
||||
'description': desc})
|
||||
except odoorpc.error.Error as e:
|
||||
LOG.exception(e)
|
||||
return {}
|
||||
|
||||
return prices
|
|
@ -0,0 +1,47 @@
|
|||
# Copyright (c) 2016 Catalyst IT Ltd.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
import six
|
||||
|
||||
|
||||
from oslo_log import log
|
||||
from stevedore import driver
|
||||
|
||||
from distil import exceptions
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def load_erp_driver(conf):
|
||||
"""Loads a erp driver and returns it.
|
||||
|
||||
:param conf: Configuration instance to use for loading the
|
||||
driver. Must include a 'drivers' group.
|
||||
"""
|
||||
|
||||
_invoke_args = [conf]
|
||||
|
||||
try:
|
||||
mgr = driver.DriverManager('distil.erp',
|
||||
conf.erp_driver,
|
||||
invoke_on_load=True,
|
||||
invoke_args=_invoke_args)
|
||||
|
||||
return mgr.driver
|
||||
|
||||
except Exception as exc:
|
||||
LOG.exception(exc)
|
||||
raise exceptions.InvalidDriver('Failed to load ERP driver'
|
||||
' for {0}'.format(conf.erp_driver))
|
|
@ -75,3 +75,8 @@ class DateTimeException(DistilException):
|
|||
class Forbidden(DistilException):
|
||||
code = 403
|
||||
message = _("You are not authorized to complete this action")
|
||||
|
||||
|
||||
class InvalidDriver(DistilException):
|
||||
"""A driver was not found or loaded."""
|
||||
message = _("Failed to load driver")
|
||||
|
|
|
@ -15,13 +15,13 @@
|
|||
|
||||
from distil import rater
|
||||
from distil.rater import rate_file
|
||||
from distil.common import odoo
|
||||
from distil.service.api.v2 import products
|
||||
|
||||
|
||||
class OdooRater(rater.BaseRater):
|
||||
|
||||
def __init__(self):
|
||||
self.prices = odoo.Odoo().get_prices()
|
||||
self.prices = products.get_products()
|
||||
|
||||
def rate(self, name, region=None):
|
||||
if not self.prices:
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from distil.common import odoo
|
||||
|
||||
from distil.common import cache
|
||||
from distil.erp import utils as erp_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
@ -25,4 +25,6 @@ CONF = cfg.CONF
|
|||
|
||||
@cache.memoize
|
||||
def get_products(regions):
|
||||
return odoo.Odoo().get_products(regions)
|
||||
erp_driver = erp_utils.load_erp_driver(CONF)
|
||||
products = erp_driver.get_products(regions)
|
||||
return products
|
||||
|
|
|
@ -7,13 +7,13 @@ port = 9999
|
|||
|
||||
[odoo]
|
||||
version = 8.0
|
||||
hostname =
|
||||
hostname = localhost
|
||||
port = 443
|
||||
protocol = jsonrpc+ssl
|
||||
database =
|
||||
user =
|
||||
password =
|
||||
region_mapping =
|
||||
database = prod
|
||||
user = tester
|
||||
password = passw0rd
|
||||
region_mapping = nz_1:nz-1,nz_2:nz-2
|
||||
|
||||
[collector]
|
||||
source = ceilometer
|
|
@ -37,6 +37,7 @@ class DistilTestCase(base.BaseTestCase):
|
|||
self.conf = cfg.ConfigOpts()
|
||||
|
||||
self.conf.register_opts(config.DEFAULT_OPTIONS)
|
||||
self.conf.register_opts(config.ODOO_OPTS, group=config.ODOO_GROUP)
|
||||
|
||||
def setup_context(self, username="test_user", tenant_id="tenant_1",
|
||||
auth_token="test_auth_token", tenant_name='test_tenant',
|
||||
|
|
|
@ -25,7 +25,7 @@ from distil.tests.unit import base
|
|||
|
||||
class TestCache(base.DistilTestCase):
|
||||
|
||||
config_file = 'distil_cache.conf'
|
||||
config_file = 'distil.conf'
|
||||
|
||||
def setUp(self):
|
||||
super(TestCache, self).setUp()
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
# Copyright (c) 2017 Catalyst IT Ltd.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from distil.erp.drivers import odoo
|
||||
from distil.tests.unit import base
|
||||
|
||||
REGION = namedtuple('Region', ['id'])
|
||||
|
||||
PRODUCTS = {'11': {'name_template': 'nz-1.c1.c1r1',
|
||||
'lst_price': 0.00015,
|
||||
'default_code': 'hour',
|
||||
'description': '1 CPU, 1GB RAM'},
|
||||
'22': {'name_template': 'nz-1.n1.router',
|
||||
'lst_price': 0.00025,
|
||||
'default_code': 'hour',
|
||||
'description': 'Router'},
|
||||
'33': {'name_template': 'nz-1.b1.volume',
|
||||
'lst_price': 0.00035,
|
||||
'default_code': 'hour',
|
||||
'description': 'Block storage'},
|
||||
'44': {'name_template': 'nz-1.o1.object',
|
||||
'lst_price': 0.00045,
|
||||
'default_code': 'hour',
|
||||
'description': 'Object storage'}}
|
||||
|
||||
class TestOdooDriver(base.DistilTestCase):
|
||||
|
||||
config_file = 'distil.conf'
|
||||
|
||||
def setUp(self):
|
||||
super(TestOdooDriver, self).setUp()
|
||||
|
||||
@mock.patch('odoorpc.ODOO')
|
||||
@mock.patch('distil.common.openstack.get_regions')
|
||||
def test_get_products(self, mock_get_regions, mock_odoo):
|
||||
mock_get_regions.return_value = [REGION(id='nz-1'),
|
||||
REGION(id='nz-2')]
|
||||
|
||||
odoodriver = odoo.OdooDriver(self.conf)
|
||||
|
||||
def _category_search(filters):
|
||||
for filter in filters:
|
||||
if filter[0] == 'name' and filter[2] == 'Compute':
|
||||
return ['1']
|
||||
if filter[0] == 'name' and filter[2] == 'Network':
|
||||
return ['2']
|
||||
if filter[0] == 'name' and filter[2] == 'Block Storage':
|
||||
return ['3']
|
||||
if filter[0] == 'name' and filter[2] == 'Object Storage':
|
||||
return ['4']
|
||||
|
||||
def _product_search(filters):
|
||||
for filter in filters:
|
||||
if filter[0] == 'categ_id' and filter[2] == '1':
|
||||
return ['11']
|
||||
if filter[0] == 'categ_id' and filter[2] == '2':
|
||||
return ['22']
|
||||
if filter[0] == 'categ_id' and filter[2] == '3':
|
||||
return ['33']
|
||||
if filter[0] == 'categ_id' and filter[2] == '4':
|
||||
return ['44']
|
||||
|
||||
def _odoo_execute(model, method, *args):
|
||||
products = []
|
||||
for id in args[0]:
|
||||
products.append(PRODUCTS[id])
|
||||
return products
|
||||
|
||||
|
||||
odoodriver.odoo.execute = _odoo_execute
|
||||
odoodriver.category = mock.Mock()
|
||||
odoodriver.category.search = _category_search
|
||||
odoodriver.product = mock.Mock()
|
||||
odoodriver.product.search = _product_search
|
||||
|
||||
products = odoodriver.get_products(regions=['nz_1'])
|
||||
self.assertEqual({'nz-1': {'block storage': [{'description':
|
||||
'Block storage',
|
||||
'price': 0.00035,
|
||||
'resource': 'b1.volume',
|
||||
'unit': 'hour'}],
|
||||
'compute': [{'description':
|
||||
'1 CPU, 1GB RAM',
|
||||
'price': 0.00015,
|
||||
'resource': 'c1.c1r1',
|
||||
'unit': 'hour'}],
|
||||
'network': [{'description': 'Router',
|
||||
'price': 0.00025,
|
||||
'resource': 'n1.router',
|
||||
'unit': 'hour'}],
|
||||
'object storage': [{'description':
|
||||
'Object storage',
|
||||
'price': 0.00045,
|
||||
'resource': 'o1.object',
|
||||
'unit': 'hour'}]}},
|
||||
products)
|
|
@ -49,6 +49,9 @@ distil.transformer =
|
|||
fromimage = distil.transformer.conversion:FromImageTransformer
|
||||
networkservice = distil.transformer.conversion:NetworkServiceTransformer
|
||||
|
||||
distil.erp =
|
||||
odoo = distil.erp.drivers.odoo:OdooDriver
|
||||
|
||||
[build_sphinx]
|
||||
all_files = 1
|
||||
build-dir = doc/build
|
||||
|
|
Loading…
Reference in New Issue