2020-02-09 08:36:50 +00:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# 2020 Nico Schottelius (nico.schottelius at ungleich.ch)
#
# This file is part of uncloud.
#
# uncloud is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# uncloud is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with uncloud. If not, see <http://www.gnu.org/licenses/>.
import json
import uuid
2020-02-09 11:12:15 +00:00
import logging
2020-02-09 11:54:52 +00:00
import re
2020-02-09 18:27:24 +00:00
import importlib
2020-02-09 08:36:50 +00:00
from uncloud import UncloudException
2020-02-09 11:54:52 +00:00
from uncloud . hack . db import DB , db_logentry
2020-02-09 08:36:50 +00:00
2020-02-09 11:12:15 +00:00
log = logging . getLogger ( __name__ )
2020-02-09 10:14:50 +00:00
class ProductOrder ( object ) :
2020-02-09 11:12:15 +00:00
def __init__ ( self , config , product_entry = None , db_entry = None ) :
2020-02-09 10:14:50 +00:00
self . config = config
self . db = DB ( self . config , prefix = " /orders " )
2020-02-09 11:12:15 +00:00
self . db_entry = { }
self . db_entry [ " product " ] = product_entry
2020-02-09 10:14:50 +00:00
2020-02-09 11:12:15 +00:00
# Overwrite if we are loading an existing product order
if db_entry :
self . db_entry = db_entry
# FIXME: this should return a list of our class!
2020-02-09 11:54:52 +00:00
def list_orders ( self , filter_key = None , filter_regexp = None ) :
2020-02-09 18:27:24 +00:00
for entry in self . db . list_and_filter ( " " , filter_key , filter_regexp ) :
yield self . __class__ ( self . config , db_entry = entry )
2020-02-09 11:12:15 +00:00
def set_required_values ( self ) :
2020-02-09 11:54:52 +00:00
""" Set values that are required to make the db entry valid """
2020-02-09 11:12:15 +00:00
if not " uuid " in self . db_entry :
self . db_entry [ " uuid " ] = str ( uuid . uuid4 ( ) )
if not " status " in self . db_entry :
self . db_entry [ " status " ] = " NEW "
if not " owner " in self . db_entry :
self . db_entry [ " owner " ] = " UNKNOWN "
2020-02-09 11:54:52 +00:00
if not " log " in self . db_entry :
self . db_entry [ " log " ] = [ ]
if not " db_version " in self . db_entry :
self . db_entry [ " db_version " ] = 1
2020-02-09 11:12:15 +00:00
def validate_status ( self ) :
if " status " in self . db_entry :
2020-02-09 11:54:52 +00:00
if self . db_entry [ " status " ] in [ " NEW " ,
" SCHEDULED " ,
" CREATED_ACTIVE " ,
" CANCELLED " ,
" REJECTED " ] :
2020-02-09 11:12:15 +00:00
return False
return True
def order ( self ) :
2020-02-09 18:27:24 +00:00
self . set_required_values ( )
2020-02-09 11:12:15 +00:00
if not self . db_entry [ " status " ] == " NEW " :
raise UncloudException ( " Cannot re-order same order. Status: {} " . format ( self . db_entry [ " status " ] ) )
2020-02-09 18:27:24 +00:00
self . db . set ( self . db_entry [ " uuid " ] , self . db_entry , as_json = True )
return self . db_entry [ " uuid " ]
2020-02-09 08:36:50 +00:00
2020-02-09 11:54:52 +00:00
def process_orders ( self ) :
2020-02-09 18:27:24 +00:00
""" processing orders can be done stand alone on server side """
2020-02-09 11:54:52 +00:00
for order in self . list_orders ( ) :
if order . db_entry [ " status " ] == " NEW " :
log . info ( " Handling new order: {} " . format ( order ) )
# FIXME: these all should be a transactions! -> fix concurrent access! !
if not " log " in order . db_entry :
order . db_entry [ ' log ' ] = [ ]
2020-02-09 18:27:24 +00:00
is_valid = True
# Verify the order entry
2020-02-09 11:54:52 +00:00
for must_attribute in [ " owner " , " product " ] :
if not must_attribute in order . db_entry :
2020-02-09 18:27:24 +00:00
message = " Missing {} entry in order, rejecting order " . format ( must_attribute )
log . info ( " Rejecting order {} : {} " . format ( order . db_entry [ " uuid " ] , message ) )
order . db_entry [ ' log ' ] . append ( db_logentry ( message ) )
order . db_entry [ ' status ' ] = " REJECTED "
self . db . set ( order . db_entry [ ' uuid ' ] , order . db_entry , as_json = True )
is_valid = False
# Rejected the order
if not is_valid :
continue
# Verify the product entry
for must_attribute in [ " python_product_class " , " python_product_module " ] :
if not must_attribute in order . db_entry [ ' product ' ] :
message = " Missing {} entry in product of order, rejecting order " . format ( must_attribute )
log . info ( " Rejecting order {} : {} " . format ( order . db_entry [ " uuid " ] , message ) )
order . db_entry [ ' log ' ] . append ( db_logentry ( message ) )
2020-02-09 11:54:52 +00:00
order . db_entry [ ' status ' ] = " REJECTED "
self . db . set ( order . db_entry [ ' uuid ' ] , order . db_entry , as_json = True )
2020-02-09 18:27:24 +00:00
is_valid = False
# Rejected the order
if not is_valid :
continue
print ( order . db_entry [ " product " ] [ " python_product_class " ] )
# Create the product
m = importlib . import_module ( order . db_entry [ " product " ] [ " python_product_module " ] )
c = getattr ( m , order . db_entry [ " product " ] [ " python_product_class " ] )
product = c ( config , db_entry = order . db_entry [ " product " ] )
# STOPPED
product . create_product ( )
order . db_entry [ ' status ' ] = " SCHEDULED "
self . db . set ( order . db_entry [ ' uuid ' ] , order . db_entry , as_json = True )
2020-02-09 11:54:52 +00:00
def __str__ ( self ) :
return str ( self . db_entry )
2020-02-09 08:36:50 +00:00
class Product ( object ) :
2020-02-09 11:12:15 +00:00
def __init__ ( self ,
config ,
product_name ,
2020-02-09 18:27:24 +00:00
product_class ,
2020-02-09 11:12:15 +00:00
db_entry = None ) :
2020-02-09 08:36:50 +00:00
self . config = config
self . db = DB ( self . config , prefix = " /orders " )
self . db_entry = { }
self . db_entry [ " product_name " ] = product_name
2020-02-09 18:27:24 +00:00
self . db_entry [ " python_product_class " ] = product_class . __qualname__
self . db_entry [ " python_product_module " ] = product_class . __module__
2020-02-09 08:36:50 +00:00
self . db_entry [ " db_version " ] = 1
2020-02-09 11:54:52 +00:00
self . db_entry [ " log " ] = [ ]
2020-02-09 11:12:15 +00:00
self . db_entry [ " features " ] = { }
2020-02-09 08:36:50 +00:00
# Existing product? Read in db_entry
if db_entry :
self . db_entry = db_entry
2020-02-09 11:12:15 +00:00
self . valid_periods = [ " per_year " , " per_month " , " per_week " ,
" per_day " , " per_hour " ,
" per_minute " , " per_second " ]
2020-02-09 08:36:50 +00:00
def define_feature ( self ,
name ,
one_time_price ,
recurring_price ,
recurring_period ,
minimum_period ) :
2020-02-09 11:12:15 +00:00
self . db_entry [ ' features ' ] [ name ] = { }
self . db_entry [ ' features ' ] [ name ] [ ' one_time_price ' ] = one_time_price
self . db_entry [ ' features ' ] [ name ] [ ' recurring_price ' ] = recurring_price
2020-02-09 08:36:50 +00:00
2020-02-09 11:12:15 +00:00
if not recurring_period in self . valid_periods :
raise UncloudException ( " Invalid recurring period: {} " . format ( recurring_period ) )
self . db_entry [ ' features ' ] [ name ] [ ' recurring_period ' ] = recurring_period
if not minimum_period in self . valid_periods :
raise UncloudException ( " Invalid recurring period: {} " . format ( recurring_period ) )
recurring_index = self . valid_periods . index ( recurring_period )
minimum_index = self . valid_periods . index ( minimum_period )
if minimum_index < recurring_index :
raise UncloudException ( " Minimum period for product ' {} ' feature ' {} ' must be shorter or equal than/as recurring period: {} > {} " . format ( self . db_entry [ ' product_name ' ] , name , minimum_period , recurring_period ) )
2020-02-09 10:14:50 +00:00
2020-02-09 11:12:15 +00:00
self . db_entry [ ' features ' ] [ name ] [ ' minimum_period ' ] = minimum_period
def validate_product ( self ) :
for feature in self . db_entry [ ' features ' ] :
pass
2020-02-09 08:36:50 +00:00
2020-02-09 18:27:24 +00:00
def place_order ( self , owner ) :
2020-02-09 08:36:50 +00:00
""" Schedule creating the product in etcd """
2020-02-09 11:54:52 +00:00
order = ProductOrder ( self . config , product_entry = self . db_entry )
2020-02-09 18:27:24 +00:00
order . db_entry [ " owner " ] = owner
return order . order ( )
2020-02-09 08:36:50 +00:00
def __str__ ( self ) :
2020-02-09 10:14:50 +00:00
return json . dumps ( self . db_entry )