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:
parent
3efcfcdf6a
commit
ddf0f5bbf6
@ -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
|
||||
|
@ -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'
|
||||
|
Loading…
x
Reference in New Issue
Block a user