Add command to generate Purchase order for Windows

A new sub command 'windows' is added in this patch. The command
can list the windows hosts which are used for hosting windows
instance for the given time range. And it also supports create
a purchase order in Odoo based on the windows instance usage.

Change-Id: Ia0e0268fd5c70ac8c62ea75cb4d019d974425566
This commit is contained in:
Fei Long Wang 2016-04-15 16:57:14 +12:00
parent 3efcfcdf6a
commit ddf0f5bbf6
2 changed files with 126 additions and 33 deletions

View File

@ -33,31 +33,13 @@ collection:
max_windows_per_cycle: 4
# defines which meter is mapped to which transformer
meter_mappings:
# -
# # meter name as seen in ceilometer
# meter: instance
# # type of resource it maps to (seen on sales order)
# type: Virtual Machine
# # which transformer to use
# transformer: InstanceUptime
# # what unit type is coming in via the meter
# unit: second
# metadata:
# name:
# sources:
# # which keys to search for in the ceilometer entry metadata
# # this can be more than one as metadata is inconsistent between
# # source types
# - display_name
# availability zone:
# sources:
# - OS-EXT-AZ:availability_zone
-
meter: state
# type of resource it maps to (seen on sales order)
# meter name as seen in ceilometer
meter: instance
# type of resource it maps to (seen on sales order)
type: Virtual Machine
# which transformer to use
transformer: Uptime
transformer: InstanceUptime
# what unit type is coming in via the meter
unit: second
metadata:
@ -70,6 +52,9 @@ collection:
availability zone:
sources:
- OS-EXT-AZ:availability_zone
host:
sources:
- instance_host
-
meter: ip.floating
service: n1.ipv4

View File

@ -415,7 +415,6 @@ def get_tenant_usage(shell, tenant, start, end):
for region in REGION_MAPPING.keys():
distil_client = getattr(shell, 'distil' + region.replace('-', '_'))
raw_usage = distil_client.get_usage(tenant, start, end)
if not raw_usage:
return None
@ -428,6 +427,7 @@ def get_tenant_usage(shell, tenant, start, end):
name = res.get('name', res.get('ip address', '')) or res_id
type = res.get('type')
is_windows_instance = res.get('os_distro') == 'windows'
instance_host = res.get('host')
for service_usage in res['services']:
if service_usage['volume'] == 'unknown unit conversion':
@ -442,20 +442,18 @@ def get_tenant_usage(shell, tenant, start, end):
if service_usage['unit'] == 'byte':
v = Decimal(service_usage['volume'])
service_usage['unit'] = 'gigabyte'
service_usage['volume'] = str(v /
Decimal(1024 * 1024 * 1024))
service_usage['volume'] = str(v / Decimal(1024 * 1024 * 1024))
if service_usage['unit'] == 'second':
# convert seconds to hours, rounding up.
v = Decimal(service_usage['volume'])
service_usage['unit'] = 'hour'
service_usage['volume'] = str(math.ceil(v /
Decimal(60 * 60)))
service_usage['volume'] = str(math.ceil(v / Decimal(60 * 60)))
# drop zero usages.
if not Decimal(service_usage['volume']):
print('WARNING: Dropping 0-volume line: %s' %
(service_usage,))
log(shell.debug,'WARNING: Dropping 0-volume line: %s' %
(service_usage,))
continue
if Decimal(service_usage['volume']) <= 0.00001:
@ -474,7 +472,8 @@ def get_tenant_usage(shell, tenant, start, end):
usage.append({'product': service_usage['name'],
'name': name,
'volume': float(service_usage['volume']),
'region': region})
'region': region,
'resource_id': res_id})
# NOTE(flwang): If this usage line is for VM(instance),
# and the instance is windows image, then a new usage line
# is added.
@ -482,12 +481,14 @@ def get_tenant_usage(shell, tenant, start, end):
usage.append({'product': service_usage['name'] + '-windows',
'name': name,
'volume': float(service_usage['volume']),
'region': region})
'region': region,
'resource_id': res_id,
'host': instance_host})
# Aggregate traffic data
for type, volume in traffic.items():
print('Region: %s, traffic type: %s, volume: %s' %
(region, type, str(volume)))
log(shell.debug,'Region: %s, traffic type: %s, volume: %s' %
(region, type, str(volume)))
usage.append({'product': type,
'name': TRAFFIC_MAPPING[type],
'volume': math.floor(volume),
@ -682,6 +683,8 @@ def login_odoo(shell):
shell.Partner = shell.oerp.env['res.partner']
shell.Pricelist = shell.oerp.env['product.pricelist']
shell.Product = shell.oerp.env['product.product']
shell.PurchaseOrder = shell.oerp.env['purchase.order']
shell.PurchaseOrderline = shell.oerp.env['purchase.order.line']
def check_duplicate(order):
@ -749,6 +752,111 @@ def do_update_quote(shell, args):
print('Failed to update order: %s' % id)
@arg('--start', type=str, metavar='START',
dest='START', required=True,
help='Start date for quote.')
@arg('--end', type=str, metavar='END',
dest='END', required=True,
help='End date for quote.')
@arg('--create-purchase-order', type=bool, metavar='CREATE_PURCHASE_ORDER',
dest='CREATE_PURCHASE_ORDER', required=False, default=False,
help='If or not create a purchase order based on usage for given time'
' range, the default value is False.')
def do_windows(shell, args):
"""
By default, this sub command will print the windows hosts list based on
the usage for given time range. If the 'create-purchase-order' is set to
'True' then a purchase order based on the monthly Windows instances usage
will be created on Odoo.
"""
user_roles = shell.keystone.session.auth.auth_ref['user']['roles']
if {u'name': u'admin'} not in user_roles:
print('Admin permission is required.')
return
login_odoo(shell)
end_timestamp = datetime.datetime.strptime(
args.END,
'%Y-%m-%dT%H:%M:%S'
)
billing_date = str((end_timestamp - datetime.timedelta(days=1)).date())
windows_usage = []
windows_hosts = set()
tenants = shell.keystone.tenants.list()
pricelist = None
for t in tenants:
if t.name == 'openstack':
# NOTE(flwang): Using a default tenant to find parter and
# pricelist of root parter
partner = find_oerp_partner_for_tenant(shell, t)
root_partner = find_root_partner(shell, partner)
pricelist, _ = root_partner['property_product_pricelist']
usages = get_tenant_usage(shell, t.id, args.START, args.END)
for u in usages:
# TODO(flwang): Using regex to match 'c1.c%dr%d-windows'
if u['product'].endswith('-windows'):
windows_usage.append(u)
if u['host'] not in windows_hosts:
windows_hosts.add(u['host'])
print(u['host'])
if args.CREATE_PURCHASE_ORDER:
generate_purchase_order(shell, args, windows_usage,
billing_date, pricelist)
def generate_purchase_order(shell, args, usage, billing_date, pricelist):
"""
Call odoo API to create a new purchase order
"""
# TODO(flwang): Partner id is hardcoded for Windows license provider
# location id is hardcoded since we don't care the delivery location
partner_id = 5618
localtion_id = 10
try:
order_dict = {
'partner_id': partner_id,
'pricelist_id': pricelist,
'partner_invoice_id': partner_id,
'partner_shipping_id': partner_id,
'date_order': billing_date,
'location_id': localtion_id,
}
order = shell.PurchaseOrder.create(order_dict)
for m in sorted(usage, key=lambda m: m['product']):
prod = find_oerp_product(shell, m['region'], m['product'])
usage_dict = {
'order_id': order,
'date_planned': billing_date,
'account_analytic_id': '',
'product_id': prod['id'],
'product_uom': prod['uom_id'][0],
'product_qty': math.fabs(m['volume']),
'name': m['resource_id'], # Hide the instance name
'price_unit': get_price(shell, pricelist, prod, m['volume'])
}
if usage_dict['product_qty'] < 0.005:
print(
'%s is too small for %s.' %
(str(usage_dict['product_qty']), m['name'])
)
continue
shell.PurchaseOrderline.create(usage_dict)
except odoorpc.error.RPCError as e:
exc_type, exc_value, exc_traceback = sys.exc_info()
traceback.print_exception(exc_type, exc_value, exc_traceback,
limit=2, file=sys.stdout)
raise e
except Exception as e:
exc_type, exc_value, exc_traceback = sys.exc_info()
traceback.print_exception(exc_type, exc_value, exc_traceback,
limit=2, file=sys.stdout)
raise e
def print_dict(d, max_column_width=80):
pt = prettytable.PrettyTable(['Property', 'Value'], caching=False)
pt.align = 'l'