#!/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 . import json import uuid import logging import re import importlib from uncloud import UncloudException from uncloud.hack.db import DB, db_logentry log = logging.getLogger(__name__) class ProductOrder(object): def __init__(self, config, product_entry=None, db_entry=None): self.config = config self.db = DB(self.config, prefix="/orders") self.db_entry = {} self.db_entry["product"] = product_entry # 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! def list_orders(self, filter_key=None, filter_regexp=None): for entry in self.db.list_and_filter("", filter_key, filter_regexp): yield self.__class__(self.config, db_entry=entry) def set_required_values(self): """Set values that are required to make the db entry valid""" 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" 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 def validate_status(self): if "status" in self.db_entry: if self.db_entry["status"] in [ "NEW", "SCHEDULED", "CREATED_ACTIVE", "CANCELLED", "REJECTED" ]: return False return True def order(self): self.set_required_values() if not self.db_entry["status"] == "NEW": raise UncloudException("Cannot re-order same order. Status: {}".format(self.db_entry["status"])) self.db.set(self.db_entry["uuid"], self.db_entry, as_json=True) return self.db_entry["uuid"] def process_orders(self): """processing orders can be done stand alone on server side""" 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'] = [] is_valid = True # Verify the order entry for must_attribute in [ "owner", "product" ]: if not must_attribute in order.db_entry: 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)) 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 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) def __str__(self): return str(self.db_entry) class Product(object): def __init__(self, config, product_name, product_class, db_entry=None): self.config = config self.db = DB(self.config, prefix="/orders") self.db_entry = {} self.db_entry["product_name"] = product_name self.db_entry["python_product_class"] = product_class.__qualname__ self.db_entry["python_product_module"] = product_class.__module__ self.db_entry["db_version"] = 1 self.db_entry["log"] = [] self.db_entry["features"] = {} # Existing product? Read in db_entry if db_entry: self.db_entry = db_entry self.valid_periods = [ "per_year", "per_month", "per_week", "per_day", "per_hour", "per_minute", "per_second" ] def define_feature(self, name, one_time_price, recurring_price, recurring_period, minimum_period): 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 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)) self.db_entry['features'][name]['minimum_period'] = minimum_period def validate_product(self): for feature in self.db_entry['features']: pass def place_order(self, owner): """ Schedule creating the product in etcd """ order = ProductOrder(self.config, product_entry=self.db_entry) order.db_entry["owner"] = owner return order.order() def __str__(self): return json.dumps(self.db_entry)