예제 #1
0
파일: news.py 프로젝트: omarabdalhamid/boss
class News(db.Model, AccountDb):
    id_field = "news_id"
    unique_field = "subject"

    news_id = Column(db.Integer, primary_key=True)
    subject = Column(db.String(78))
    body = Column(db.Text)
    published = Column(db.DateTime())
    deleted = Column(db.DateTime())

    display_fields = frozenset(
        ['news_id', 'subject', 'body', 'published', 'deleted'])

    @classmethod
    @duplicate_handle(errors.NewsAlreadyExist)
    def create_news(cls, subject, body):
        news = cls()
        news.subject = subject
        news.body = body
        news.published = None
        news.deleted = None

        db.session.add(news)
        return news

    def update(self, new_parameters):
        if self.published:
            new_parameters['published'] = utcnow().datetime
        super().update(new_parameters)

    def publish(self, publish):
        self.published = utcnow().datetime if publish else None
예제 #2
0
class Consulta(db.Model):
    __tablename__ = 'consulta_medica_consulta'
    id = db.Column(db.String(64), primary_key=True)
    start_date = db.Column(db.DateTime(), nullable=False)
    end_date = db.Column(db.DateTime())
    physician_id = db.Column(db.String(64), nullable=False)
    patient_id = db.Column(db.String(64), nullable=False)
    price = db.Column(db.Numeric(17, 4), nullable=False)
예제 #3
0
class TimeState(db.Model, AccountDb):
    time_state_id = Column(db.Integer, primary_key=True)
    name = Column(db.String(64), nullable=False)
    customer_id = Column(db.Integer, ForeignKey("customer.customer_id", ondelete="CASCADE"), nullable=False)
    scheduled_at = Column(db.DateTime(), index=True, nullable=False)
    action = Column(db.String(64), nullable=False)
    step = Column(db.Integer)

    customer = relationship("Customer")

    __table_args__ = (UniqueConstraint('customer_id', 'name'),)

    def __str__(self):
        return "<TimeState %s of %s. %s scheduled at %s>" % (self.name, self.customer, self.action, self.scheduled_at)

    def __init__(self, name, customer_id, scheduled_at, action):
        self.name = name
        self.customer_id = customer_id
        self.scheduled_at = scheduled_at
        self.action = action
        self.step = 0

    @classmethod
    def get_actual_actions(cls, now=None):
        now = now or utcnow().datetime
        return cls.query.filter(cls.scheduled_at < now)

    def remove(self):
        db.session.delete(self)

    @classmethod
    def get_by_customer(cls, customer_id, name):
        return cls.query.filter(cls.customer_id == customer_id, cls.name == name).first()
예제 #4
0
class Tenant(db.Model, AccountDb):
    """Model for storage of metadata related to a tenant."""

    # ID is a uuid
    tenant_id = Column(String(32), primary_key=True, nullable=False)
    name = Column(Text, nullable=False)
    created = Column(DateTime, nullable=False)
    last_collected = Column(DateTime)
    deleted = Column(db.DateTime())

    @classmethod
    def create(cls, tenant_id, tenant_name, created_at=None):
        created_at = created_at or utcnow().datetime
        tenant = cls()
        tenant.tenant_id = tenant_id
        tenant.name = tenant_name
        tenant.created = created_at or utcnow().datetime
        tenant.last_collected = created_at
        db.session.add(tenant)
        db.session.flush()  # can't assume deferred constraints.
        return tenant

    def __str__(self):
        return "<Tenant %s %s>" % (self.tenant_id, self.name)

    @classmethod
    def all_active_tenants(cls):
        return cls.query.filter_by(deleted=None)

    def mark_removed(self):
        logbook.info("Remove {} from db", self)
        self.deleted = utcnow().datetime
예제 #5
0
class telegramGroup(db.Model):
    __tablename__ = 'telegramGroup'
    __table_args__ = {'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8mb4'}
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.VARCHAR(255))
    photo = db.Column(db.VARCHAR(255))
    username = db.Column(db.VARCHAR(255))
    date = db.Column(db.DateTime(6))

    def __init__(self, id, title, photo, username, date):
        self.id = id
        self.title = title
        self.photo = photo
        self.username = username
        self.date = date

    def __repr__(self):
        return "<teleGramGroup %r>" % self.username

    def to_json(self):
        item = self.__dict__
        if "_sa_instance_state" in item:
            del item["_sa_instance_state"]
        item['photo'] = 'http://localhost:5000/download?filepath=' + item[
            'photo']
        item['date'] = item['date'].strftime("%Y-%m-%d-%H")
        return item
예제 #6
0
class Message(db.Model):
    __table_name__ = 'message'
    msgId = db.Column(db.Integer, primary_key=True)
    isSend = db.Column(db.Integer)
    senderId = db.Column(db.Integer)
    content = db.Column(db.VARCHAR(1000))
    createTime = db.Column(db.DateTime(6))
    intent = db.Column(db.VARCHAR(255))

    def __init__(self, msgId, isSend, senderId, content, createTime, intent):
        self.senderId = senderId
        self.msgId = msgId
        self.isSend = isSend
        self.content = content
        self.intent = intent
        self.createTime = createTime

    def __repr__(self):
        return "<Message %r>" % self.msgId

    def to_json(self):
        item = self.__dict__
        if "_sa_instance_state" in item:
            del item["_sa_instance_state"]
        item['createTime'] = item['createTime'].strftime("%Y-%m-%d-%H")
        return item
예제 #7
0
class Tariff(db.Model, AccountDb):
    id_field = "tariff_id"
    unique_field = "localized_name"

    tariff_id = Column(db.Integer, primary_key=True)
    localized_name = relationship("TariffLocalization", cascade="all")
    description = Column(db.Text())
    currency = Column(db.String(3))

    parent_id = Column(db.Integer, ForeignKey('tariff.tariff_id', ondelete='CASCADE'))
    parent = relationship('Tariff', remote_side=[tariff_id])

    deleted = Column(db.DateTime())
    created = Column(db.DateTime())
    modified = Column(db.DateTime())
    services = relationship("ServicePrice", cascade="save-update, merge, delete, delete-orphan")
    mutable = Column(db.Boolean())
    default = Column(db.Boolean(), index=True)

    history = relationship('TariffHistory', remote_side=[tariff_id], lazy="dynamic", cascade="all")

    display_fields = frozenset(["description", "created", "deleted",
                                "tariff_id", "parent_id", "mutable", "default",
                                "currency", "modified"])

    def __str__(self):
        return "<Tariff %s>" % self.name

    @property
    def name(self):
        return self.localized_name_as_dict()[DEFAULT_LANGUAGE].localized_name

    def localized_name_as_dict(self):
        return {localization.language: localization for localization in self.localized_name}

    def update_localized_name(self, localized_name):
        current = self.localized_name_as_dict()
        for language, value in localized_name.items():
            if language in current:
                current[language].localized_name = value
            else:
                self.localized_name.append(TariffLocalization.create_localization(language, value))

    def get_localized_name(self, language):
        localized_name = self.localized_name_as_dict()
        return (localized_name.get(language) or localized_name[DEFAULT_LANGUAGE]).localized_name

    @classmethod
    @duplicate_handle(errors.TariffAlreadyExists)
    def create_tariff(cls, localized_name, description, currency, parent_id=None, services=None):
        tariff = cls()
        tariff.description = description
        tariff.currency = currency.upper()
        tariff.parent_id = parent_id
        now = utcnow().datetime
        tariff.created = now
        tariff.modified = now
        tariff.deleted = None
        tariff.update_localized_name(localized_name)
        tariff.mutable = True
        if parent_id and not services:
            tariff.update_services(Tariff.get_by_id(parent_id).services)
        if services:
            tariff.update_services(services)
        db.session.add(tariff)
        db.session.flush()
        return tariff

    @duplicate_handle(errors.TariffAlreadyExists)
    def update(self, localized_name=None, description=None, services=None, currency=None):
        if not self.mutable:
            if services and self.services_to_change():
                return self.update_new_vm_services(services)
            raise errors.ImmutableTariff()
        if self.deleted:
            raise errors.RemovedTariff()
        if localized_name:
            self.update_localized_name(localized_name)
        if description:
            self.description = description
        if services:
            self.update_services(services)
        if currency:
            self.currency = currency.upper()
        if db.session.is_modified(self):
            self.modified = utcnow().datetime
            return True
        return False

    def services_as_dict(self, lower=False):
        lower_func = (lambda x: x.lower()) if lower else lambda x: x

        return {lower_func(service.service_id): service for service in self.services}

    def service_price(self, service_id):
        service_id = str(service_id)
        sp = self.services_as_dict(lower=True).get(service_id.lower())
        if not sp:
            logbook.warning("Tariff {} don't have service {}", self, service_id)
            return None
        return sp.price

    def services_to_change(self):
        return {service.service_id for service in self.services if service.need_changing}

    def service_ids(self):
        return {service.service_id for service in self.services}

    def flavors(self):
        services = set()
        for service_price in self.services:
            service = service_price.service
            if service.category_id == Category.VM:
                services.add(service.flavor.flavor_id)
        return frozenset(services)

    def update_new_vm_services(self, services):
        current_services = self.services_as_dict()
        services_to_update = self.services_to_change()
        for service in services:
            service_id = service['service_id']
            if service_id in services_to_update:
                current_services[service_id].price = service['price']
                current_services[service_id].need_changing = False

    def update_services(self, services):
        current_services = self.services_as_dict()
        new_services = set()
        for service in services:
            if isinstance(service, dict):
                service_id = service["service_id"]
                price = service["price"]
            else:
                service_id = service.service_id
                price = service.price

            if service_id in current_services:
                current_services[service_id].price = price
            else:
                self.services.append(ServicePrice(service_id, price))
            new_services.add(service_id)
        removed_services = set(current_services.keys()) - new_services
        if removed_services:
            logbook.debug("Remove services {} from tariff {}", removed_services, self)
            for service_id in removed_services:
                self.services.remove(current_services[service_id])

    def display(self, short=True):
        res = super().display(short)
        res["localized_name"] = {loc.language: loc.localized_name for loc in self.localized_name}
        if not short:
            res["services"] = [s.display() for s in self.services]

        # HACK to show used customers by request
        if isinstance(short, list):
            res["used"] = self.used()

        return res

    def display_for_customer(self):
        tariff_info = {
            "services": [s.display() for s in self.services],
            "localized_name": {loc.language: loc.localized_name for loc in self.localized_name}
        }
        return tariff_info

    def remove(self):
        if self.deleted:
            return False
        if self.used() > 0 or self.deferred_changes():
            raise errors.RemoveUsedTariff()
        self.deleted = utcnow().datetime
        return True

    def mark_immutable(self):
        if self.mutable:
            self.mutable = False
            self.modified = utcnow().datetime
            return True
        return False

    @classmethod
    def delete_by_prefix(cls, prefix, field=None):
        field = field or cls.unique_field
        if not field:
            raise Exception("Field for removing is not set")
        member = getattr(cls, field)
        if field == "localized_name":
            query = cls.query.join(Tariff.localized_name).\
                filter(TariffLocalization.localized_name.ilike("%{}%".format(prefix + "%")))
        else:
            query = cls.query.filter(member.like(prefix + "%"))

        query = query.filter(or_(cls.default == None, cls.default == False))
        ids = [tariff_id for tariff_id, in query.with_entities(cls.id_field)]
        if not ids:
            return 0
        ServicePrice.query.filter(ServicePrice.tariff_id.in_(ids)).delete(False)
        TariffHistory.query.filter(TariffHistory.tariff_id.in_(ids)).delete(False)
        TariffLocalization.query.filter(TariffLocalization.parent_id.in_(ids)).delete(False)
        return cls.query.filter(cls.tariff_id.in_(ids)).delete("fetch")

    @classmethod
    def get_default(cls):
        return cls.query.filter(cls.default == True)

    def make_default(self):
        if self.default:
            return
        if self.deleted:
            raise errors.RemovedTariff()
        if self.mutable:
            raise errors.MutableTariffCantBeDefault()
        self.get_default().update({"default": False})
        db.session.flush()
        self.default = True

    def used(self):
        from model import Customer
        """
        Returns number of customer who use this tariff
        """
        return Customer.query.filter(Customer.tariff_id == self.tariff_id, Customer.deleted == None).count()

    def deferred_changes(self):
        from model import Deferred
        """
        Returns number of usages in deferred changes
        """
        return Deferred.query.filter(Deferred.tariff_id == self.tariff_id).count()

    def get_history(self, date_before, date_after):
        history = self.history.filter(TariffHistory.tariff_id == self.tariff_id)
        if date_after:
            history = history.filter(TariffHistory.date > date_after)
        if date_before:
            history = history.filter(TariffHistory.date < date_before)

        return history.order_by(desc(TariffHistory.history_id))
예제 #8
0
class TariffHistory(db.Model, AccountDb):
    EVENT_CREATE = "create"
    EVENT_UPDATE = "update"
    EVENT_DELETE = "delete"
    EVENT_ASSIGN = "assign"
    EVENT_UNASSIGN = "unassign"
    EVENT_IMMUTABLE = "immutable"

    EVENTS = {EVENT_CREATE, EVENT_UPDATE, EVENT_DELETE}
    MAX_AGE_OF_UPDATES_TO_AUTO_COLLAPSE = timedelta(seconds=conf.backend.tariff.max_age_of_updates_to_auto_collapse)

    history_id = Column(db.Integer, primary_key=True)

    event = Column(db.String(16))
    user_id = Column(db.Integer, ForeignKey('user.user_id', ondelete="set null"))
    user = relationship("User")

    tariff_id = Column(db.Integer, ForeignKey('tariff.tariff_id'))
    tariff = relationship("Tariff")

    customer_id = Column(db.Integer, ForeignKey('customer.customer_id'))
    customer = relationship("Customer")

    date = Column(db.DateTime())
    snapshot = deferred(Column(db.Text()))

    display_fields = frozenset(("history_id", "user", "event", "date", "localized_name", "snapshot"))
    display_fields_short = frozenset(("history_id", "user", "event", "date", "localized_name"))
    extract_fields = {"user": User.display}

    def __str__(self):
        return "<TariffHistory '%s' %s by %s>" % (self.tariff.name, self.event, self.date)

    def __repr__(self):
        return str(self)

    @classmethod
    def create(cls, event, user_id, tariff, customer=None):
        history_item = cls()
        history_item.event = event
        history_item.user_id = user_id
        history_item.tariff = tariff
        history_item.customer = customer
        history_item.date = tariff.modified
        history_item.snapshot = json.dumps(tariff.display(short=True), cls=DateTimeJSONEncoder)
        db.session.add(history_item)
        db.session.flush()

        if event == cls.EVENT_UPDATE:
            history_item._reduce_history_of_update_operations()

        return history_item

    def _reduce_history_of_update_operations(self):
        query = self.query.filter(TariffHistory.history_id != self.history_id,
                                  TariffHistory.tariff_id == self.tariff_id,
                                  TariffHistory.event == self.EVENT_UPDATE,
                                  TariffHistory.user_id == self.user_id,
                                  TariffHistory.date <= self.date + timedelta(seconds=1),
                                  TariffHistory.date >= self.date - self.MAX_AGE_OF_UPDATES_TO_AUTO_COLLAPSE)
        n = query.delete(False)
        logbook.debug("Reduced {} history records for tariff {}", n, self.tariff)
        return n

    @property
    def localized_name(self):
        return conf.tariff.events.get(self.event)
예제 #9
0
class User(db.Model, AccountDb):
    id_field = "user_id"
    unique_field = "email"

    user_id = Column(db.Integer, primary_key=True)
    email = Column(db.String(254), nullable=False, unique=True)
    password = Column(db.String(100), nullable=False)
    role = Column(db.String(16), nullable=False)
    name = Column(db.String(256))
    deleted = Column(db.DateTime())
    created = Column(db.DateTime())

    display_fields = frozenset(("user_id", "email", "role", "name", "created", "deleted"))
    display_fields_short = frozenset(("user_id", "name"))
    extract_fields = {"role": Role.display}

    def __str__(self):
        return "<User {0.email} ({0.role})>".format(self)

    @classmethod
    def password_hashing(cls, raw_password):
        return pbkdf2_sha256.encrypt(raw_password, rounds=conf.user.salt_rounds, salt_size=10)

    def check_password(self, raw_password):
        return pbkdf2_sha256.verify(raw_password, self.password)

    @classmethod
    @duplicate_handle(errors.UserAlreadyExists)
    def new_user(cls, email, password, role, name=None):
        user = cls.get_by_email(email)
        if user:
            if user.deleted is None:
                raise errors.UserAlreadyExists()
            existed = True
        else:
            user = cls()
            existed = False

        user.email = email
        user.password = cls.password_hashing(password) if password else ""
        user.role = role
        user.name = name
        user.deleted = None
        user.created = utcnow().datetime

        if not existed:
            db.session.add(user)
        return user

    @classmethod
    def get_by_email(cls, email, include_deleted=False):
        query = cls.query.filter_by(email=email)
        if not include_deleted:
            query.filter_by(deleted=None)
        return query.first()

    @classmethod
    def login(cls, email, password):
        user = User.get_by_email(email)

        if user is None:
            logbook.info("User {} is not found", email)
            raise errors.UserUnauthorized()
        if user.deleted:
            logbook.info("User {} is deleted at {}", email, user.deleted)
            raise errors.UserUnauthorized()

        if not user.check_password(password):
            logbook.info("Password mismatch for user {}", email)
            raise errors.UserUnauthorized()

        token = UserToken.create(user)

        return token, user

    def update(self, new_parameters):
        password = new_parameters.get("password")
        if password:
            new_parameters["password"] = self.password_hashing(password)
        return super().update(new_parameters)

    def mark_removed(self):
        res = super().mark_removed()
        UserToken.remove_by(self.user_id)
        return res

    def password_reset(self, password):
        self.password = self.password_hashing(password)
예제 #10
0
class Customer(db.Model, AccountDb):
    id_field = "customer_id"
    unique_field = "email"

    CUSTOMER_TYPE_PRIVATE_PERSON = "private"
    CUSTOMER_TYPE_LEGAL_ENTITY = "entity"

    CUSTOMER_PRODUCTION_MODE = "production"
    CUSTOMER_PENDING_PRODUCTION_MODE = "pending_prod"
    CUSTOMER_TEST_MODE = "test"

    OPENSTACK_DASHBOARD_HORIZON = "horizon"
    OPENSTACK_DASHBOARD_SKYLINE = "skyline"
    OPENSTACK_DASHBOARD_BOTH = "both"

    ALL_MODES = [CUSTOMER_TEST_MODE, CUSTOMER_PENDING_PRODUCTION_MODE, CUSTOMER_PRODUCTION_MODE]
    ALL_TYPES = [CUSTOMER_TYPE_PRIVATE_PERSON, CUSTOMER_TYPE_LEGAL_ENTITY]
    ALL_PANELS = [OPENSTACK_DASHBOARD_HORIZON, OPENSTACK_DASHBOARD_SKYLINE, OPENSTACK_DASHBOARD_BOTH]
    CUSTOMER_MODE = Enum(*ALL_MODES)
    CUSTOMER_TYPE = Enum(*ALL_TYPES)
    CUSTOMER_PANEL = Enum(*ALL_PANELS)

    customer_id = Column(db.Integer, primary_key=True)
    email = Column(db.String(254), nullable=False, unique=True)
    password = Column(db.String(100), nullable=False)
    deleted = Column(db.DateTime())
    created = Column(db.DateTime())
    email_confirmed = Column(db.Boolean)
    tariff_id = Column(db.Integer, ForeignKey("tariff.tariff_id"), index=True)
    blocked = Column(db.Boolean)
    customer_type = Column(CUSTOMER_TYPE, nullable=False, default=CUSTOMER_TYPE_PRIVATE_PERSON)
    customer_mode = Column(CUSTOMER_MODE, nullable=False)
    withdraw_period = Column(db.String(16))
    balance_limit = Column(db.DECIMAL(precision=conf.backend.decimal.precision, scale=conf.backend.decimal.scale))
    auto_withdraw_enabled = Column(db.Boolean)
    auto_withdraw_balance_limit = Column(db.Integer)
    auto_withdraw_amount = Column(db.Integer)

    locale = Column(db.String(32))

    tariff = relationship("Tariff")

    # Each customer has his own OpenStack tenant_id
    os_tenant_id = Column(db.String(32), ForeignKey("tenant.tenant_id"), nullable=True, unique=True, default=None)
    # Each customer has his own OpenStack User account
    os_user_id = Column(db.String(32), nullable=True, unique=True, default=None)
    # Each customer has his own OpenStack Password
    os_user_password = Column(db.String(100), nullable=True, default=None)
    os_username = Column(db.String(64), nullable=True, default=None)
    # OpenStack dashboard view
    os_dashboard = Column(CUSTOMER_PANEL, nullable=False, default=OPENSTACK_DASHBOARD_HORIZON)

    subscriptions = relationship("Subscription", lazy='dynamic')
    switches = relationship("SubscriptionSwitch", lazy='dynamic')
    quota = relationship("Quote", lazy='dynamic')

    display_fields = frozenset(["customer_id", "email", "created", "deleted",
                                "email_confirmed", "tariff_id", "blocked", "customer_mode",
                                "customer_type", "withdraw_period", "balance_limit",
                                "os_tenant_id", "os_user_id", "locale", "os_username", "os_dashboard"])
    accounts = relationship("Account", cascade="all, delete-orphan")
    accounts_history = relationship("AccountHistory", cascade="all, delete-orphan", lazy='dynamic')
    history = relationship("CustomerHistory", cascade="all, delete-orphan", lazy='dynamic')
    private_info = relationship("PrivateCustomerInfo", uselist=False, backref="customer", cascade="all, delete-orphan")
    entity_info = relationship("EntityCustomerInfo", uselist=False, backref="customer", cascade="all, delete-orphan")
    tenant = relationship("Tenant")
    tasks = relationship("ScheduledTask", cascade="all, delete-orphan")

    AUTO_REPORT_TASK = "auto_report"

    role = "customer"  # stab for logging

    def __str__(self):
        return "<Customer %s blocked:%s, deleted:%s>" % (self.email, self.blocked, self.deleted)

    @property
    def info(self):
        return self.private_info if self.customer_type == self.CUSTOMER_TYPE_PRIVATE_PERSON else self.entity_info

    @classmethod
    def password_hashing(cls, raw_password):
        return pbkdf2_sha256.encrypt(raw_password, rounds=conf.customer.salt_rounds, salt_size=10)

    def check_password(self, raw_password):
        if not self.password:
            return False
        return pbkdf2_sha256.verify(raw_password, self.password)

    @classmethod
    @duplicate_handle(errors.CustomerAlreadyExists)
    def new_customer(cls, email, password, creator_id, detailed_info=None, comment="New customer",
                     withdraw_period=None, make_prod=False, customer_type=None, promo_code=None, locale=None):
        from model import Tariff, Account, CustomerHistory

        customer = cls()
        customer.email = email
        customer.password = cls.password_hashing(password) if password else ""
        customer.deleted = None
        customer.created = utcnow().datetime
        customer.email_confirmed = False
        customer.blocked = False
        customer.customer_mode = cls.CUSTOMER_PRODUCTION_MODE if make_prod else cls.CUSTOMER_TEST_MODE
        customer.customer_type = customer_type if customer_type else cls.CUSTOMER_TYPE_PRIVATE_PERSON
        customer.withdraw_period = withdraw_period or conf.customer.default_withdraw_period
        customer.auto_withdraw_enabled = conf.payments.auto_withdraw_enable
        customer.auto_withdraw_balance_limit = conf.payments.auto_withdraw_balance_limit
        customer.auto_withdraw_amount = conf.payments.auto_withdraw_amount
        customer.locale = locale or conf.customer.default_locale
        customer.os_dashboard = conf.customer.default_openstack_dashboard

        default_tariff = Tariff.get_default().first()
        if not default_tariff:
            default_tariff = Tariff.query.filter_by(mutable=False).first() or Tariff.query.filter_by().first()
            if not default_tariff:
                raise errors.TariffNotFound()
        customer.tariff_id = default_tariff.tariff_id
        customer.balance_limit = conf.customer.balance_limits.get(default_tariff.currency,
                                                                  conf.customer.balance_limits.default)
        db.session.add(customer)
        db.session.flush()

        customer.init_subscriptions()
        template = 'customer' if make_prod else 'test_customer'
        customer.quota_init(template)
        customer.accounts.append(Account.create(default_tariff.currency, customer, creator_id, comment))

        if promo_code is not None:
            PromoCode.new_code(promo_code, customer.customer_id)

        CustomerHistory.new_customer(customer, creator_id, comment)
        if detailed_info:
            customer.create_info(customer.customer_type, detailed_info)
        auto_report_task = ScheduledTask(cls.AUTO_REPORT_TASK, customer.customer_id, customer.withdraw_period)
        db.session.add(auto_report_task)

        logbook.info("New customer created: {}", customer)
        if not make_prod:
            currency = customer.tariff.currency.upper()
            initial_balance = conf.customer.test_customer.balance.get(currency)
            logbook.debug("Initial balance for customer {}: {} {}", customer, initial_balance, currency)
            if initial_balance:
                customer.modify_balance(Decimal(initial_balance), currency, None, "Initial test balance")

        return customer

    def update_os_credentials(self, tenant_id, tenant_name, user_id, username, password):
        from model import Tenant
        """Update os_tenant_id and os_user_id for customer with given email address."""
        if self.os_tenant_id is not None and self.os_tenant_id != tenant_id and tenant_id:
            logbook.warning("Customer {} already has tenant in open stack {}. New value: {}",
                            self, self.os_tenant_id, tenant_id)
            old_tenant = Tenant.get_by_id(self.os_tenant_id)
            if old_tenant:
                old_tenant.mark_removed()
        if self.os_user_id is not None and self.os_user_id != user_id and user_id:
            logbook.warning("Customer {} already has user in open stack {}. New value: {}",
                            self, self.os_user_id, user_id)
        self.os_user_password = password
        if tenant_id is not None and self.os_tenant_id != tenant_id:
            logbook.info("Create tenant in db {} {}", tenant_id, tenant_name)
            Tenant.create(tenant_id, tenant_name)
        self.os_tenant_id = tenant_id
        self.os_user_id = user_id
        self.os_username = username

    def update_os_password(self, password):
        self.os_user_password = password
        db.session.flush()

    @classmethod
    def get_by_email(cls, email, include_deleted=False):
        query = cls.query.filter_by(email=email)
        if not include_deleted:
            query.filter_by(deleted=None)
        return query.first()

    @classmethod
    def get_by_id(cls, customer_id, include_deleted=False):
        query = cls.query.filter_by(customer_id=customer_id)
        if not include_deleted:
            query.filter_by(deleted=None)
        return query.first()

    @classmethod
    def login(cls, email, password):
        customer = cls.get_by_email(email)

        if customer is None:
            logbook.info("Customer {} is not found", email)
            raise errors.CustomerUnauthorized()
        if customer.deleted:
            logbook.info("Customer {} is deleted at {}", email, customer.deleted)
            raise errors.CustomerUnauthorized()

        if not customer.check_password(password):
            logbook.info("Password mismatch for customer {}", email)
            raise errors.CustomerUnauthorized()

        token = CustomerToken.create(customer)

        return token, customer

    @classmethod
    def filter_by_customer_type(cls, query_parameters, customer_info, query):
        from model import PrivateCustomerInfo
        customer_type = cls.CUSTOMER_TYPE_PRIVATE_PERSON if customer_info is PrivateCustomerInfo \
            else cls.CUSTOMER_TYPE_LEGAL_ENTITY
        query = query.outerjoin(customer_info).filter(cls.customer_type == customer_type)
        for k, v in query_parameters.items():
            if hasattr(cls, k):
                query = query.filter(getattr(cls, k) == v)
            elif k.endswith('_before'):
                query = query.filter(getattr(cls, k.partition('_before')[0]) < v)
            elif k.endswith('_after'):
                query = query.filter(getattr(cls, k.partition('_after')[0]) > v)
            elif hasattr(customer_info, k):
                query = query.filter_by(**{k: v})

        return query

    @classmethod
    def filter(cls, query_parameters, visibility=None):
        from model import PrivateCustomerInfo, EntityCustomerInfo
        limit = query_parameters.pop("limit")
        page = query_parameters.pop("page")
        sort = query_parameters.pop("sort", None)

        query = cls.query
        if visibility == "all":
            pass
        elif visibility == "visible":
            query = cls.query.filter(cls.deleted.is_(None))
        elif visibility == "deleted":
            query = cls.query.filter(cls.deleted.isnot(None))

        if query_parameters:
            tariff_ids = query_parameters.pop("tariff_ids", None)
            if tariff_ids:
                query = query.filter(cls.tariff_id.in_(tariff_ids))

            customer_type = query_parameters.get('customer_type')

            if customer_type == cls.CUSTOMER_TYPE_PRIVATE_PERSON:
                result_query = cls.filter_by_customer_type(query_parameters, PrivateCustomerInfo, query)
            elif customer_type == cls.CUSTOMER_TYPE_LEGAL_ENTITY:
                result_query = cls.filter_by_customer_type(query_parameters, EntityCustomerInfo, query)
            else:
                private_query = cls.filter_by_customer_type(query_parameters, PrivateCustomerInfo, query).all()
                entity_query = cls.filter_by_customer_type(query_parameters, EntityCustomerInfo, query).all()
                result_query = private_query + entity_query

            customer_ids = {item.customer_id for item in result_query}
            query = cls.query.filter(cls.customer_id.in_(customer_ids))

        if sort:
            query = cls.sort_query(query, sort)
        return query.paginate(page, limit)

    def update(self, new_common_params=None, new_info_params=None, user_id=None, comment=None):
        from model import CustomerHistory
        if self.deleted:
            raise errors.CustomerRemoved()

        if not (new_common_params or new_info_params):
            return

        if new_common_params:
            assert "tariff_id" not in new_common_params
            assert "tariff" not in new_common_params
            password = new_common_params.get("password")
            if password:
                new_common_params["password"] = self.password_hashing(password)
                CustomerHistory.change_password(self, comment)
            withdraw_period = new_common_params.get("withdraw_period")
            if withdraw_period:
                task = ScheduledTask.get_by_customer(self.customer_id, self.AUTO_REPORT_TASK)
                task.update_period(withdraw_period)
            super().update(new_common_params)

        if new_info_params:
            if not self.info:
                self.create_info(self.customer_type, new_info_params)
            else:
                self.info.update(new_info_params)

        CustomerHistory.change_info(self, user_id, comment)

    def update_tariff(self, new_tariff_id, user_id, comment=None):
        from model import Tariff, CustomerHistory
        from task.customer import change_flavors
        if self.deleted:
            raise errors.CustomerRemoved()

        new_tariff = Tariff.get_by_id(new_tariff_id)
        if new_tariff.mutable:
            raise errors.AssignMutableTariff()
        if new_tariff.deleted:
            raise errors.RemovedTariff()

        if self.tariff.currency != new_tariff.currency:
            # TODO implement logic
            pass
        logbook.info("Change tariff from {} to {} for customer {}", self.tariff, new_tariff, self)
        self.tariff_id = new_tariff_id
        if self.os_tenant_id:
            change_flavors.delay(self.os_tenant_id, new_tariff.flavors())
        CustomerHistory.tariff_changed(self, user_id, comment)

    def mark_removed(self):
        res = super().mark_removed()
        CustomerToken.remove_by(self.customer_id)
        ScheduledTask.remove_by_customer_id(self.customer_id)
        return res

    def password_reset(self, password):
        from model import CustomerHistory
        self.password = self.password_hashing(password)
        CustomerHistory.reset_password(self, "password was reset ed")

    def confirm_email(self):
        from model import CustomerHistory
        from task.openstack import task_os_create_tenant_and_user

        self.email_confirmed = True
        logbook.info("Email for customer {} confirmed", self)
        CustomerHistory.email_confirmed(self, "Email is confirmed")
        if self.customer_mode == self.CUSTOMER_TEST_MODE:
            TestPeriodOver.create(self.customer_id)

        db.session.commit()

        logbook.info("Creating tenants for customer {}", self)
        task_os_create_tenant_and_user.delay(self.customer_id, self.email)

    def get_deferred(self):
        from model.account.deferred import Deferred
        return Deferred.get_by_customer(self.customer_id)

    def account_dict(self):
        return {account.currency: {
                "balance": account.balance,
                "withdraw": account.withdraw,
                "current": account.current} for account in self.accounts}

    def balance_dict(self):
        return {account.currency: account.balance for account in self.accounts}

    def display(self, short=True):
        res = super().display(short)
        res["account"] = self.account_dict()
        res["currency"] = self.tariff.currency
        res["tariff_id"] = self.tariff_id
        auto_report_task = ScheduledTask.get_by_customer(self.customer_id, self.AUTO_REPORT_TASK)
        res["withdraw_date"] = auto_report_task.next_scheduled if auto_report_task else None
        res["detailed_info"] = self.info.display(short) if self.info else {}
        res["promo_code"] = display(PromoCode.get_by_customer_id(self.customer_id))
        return res

    def report_info(self):
        report_fields = ["email", "locale"]
        report_info = {field: getattr(self, field, None) for field in report_fields}

        report_info["name"] = self.get_name()
        entity_info = self.entity_info
        if entity_info:
            report_info["entity_info"] = entity_info.display()
        return report_info

    def get_account(self, currency):
        currency = currency.upper()
        for account in self.accounts:
            if account.currency == currency:
                return account
        return None

    def get_or_create_account(self, currency, user_id):
        from model import Account

        account = self.get_account(currency)
        if account:
            return account

        account = Account.create(currency, self, user_id)
        self.accounts.append(account)
        db.session.flush()
        return account

    def get_name(self):
        return self.info.name if self.info and self.info.name else self.email

    def check_balance(self, account, currency):
        if currency == self.tariff.currency:
            if not self.blocked and account.balance - account.withdraw < self.balance_limit:
                self.block(blocked=True, user_id=None, message='insufficient funds')
                return

            from model import CustomerHistory
            block_event = CustomerHistory.get_last_block_event(self)
            if self.blocked and account.balance - account.withdraw > self.balance_limit and not block_event.user_id:
                self.block(blocked=False, user_id=None, message='balance is positive')

    def is_test_mode(self):
        return self.customer_mode == self.CUSTOMER_TEST_MODE

    def modify_balance(self, account_delta, currency, user_id, comment, transaction_id=None, cleanup=False):
        logbook.info("Changing balance of {}: {} {} by user {} with comment {}",
                     self, account_delta, currency, user_id, comment)
        if Decimal(account_delta) > 0 and not cleanup:
            self.check_wait_production()
        if not currency:
            currency = self.tariff.currency
        currency = currency.upper()
        account = self.get_or_create_account(currency, user_id)
        account.modify(self, account_delta, user_id, comment, transaction_id=transaction_id)
        PaymentService.send_email_about_balance_modifying(self, account_delta, currency,
                                                          account.balance - account.withdraw, comment)
        self.check_balance(account, currency)
        db.session.flush()

    def withdraw(self, delta, currency=None):
        assert isinstance(delta, Decimal)
        assert delta >= 0
        currency = (currency or self.tariff.currency).upper()
        logbook.debug("Withdraw {} {} of {}", delta, currency, self)
        account = self.get_account(currency)
        if account is None:
            raise Exception("Customer %s doesn't have account in %s, but withdraw %s %s is required" %
                            (self, currency, delta, currency))
        account.charge(delta)
        self.check_balance(account, currency)
        db.session.flush()

    def change_auto_withdraw(self, enabled, balance_limit, payment_amount):
        if enabled is not None:
            self.auto_withdraw_enabled = enabled
        if payment_amount is not None:
            assert payment_amount > 0
            self.auto_withdraw_amount = payment_amount
        if balance_limit is not None:
            self.auto_withdraw_balance_limit = balance_limit

    def display_auto_withdraw(self):
        return {
            'enabled': self.auto_withdraw_enabled,
            'balance_limit': self.auto_withdraw_balance_limit,
            'payment_amount': self.auto_withdraw_amount
        }

    def get_account_history(self, after=None, before=None, limit=1000):
        from model import AccountHistory
        query = self.accounts_history.filter(AccountHistory.customer_id == self.customer_id)
        if after:
            query = query.filter(AccountHistory.date >= after)
        if before:
            query = query.filter(AccountHistory.date < before)
        query = query.order_by(desc(AccountHistory.account_history_id)).limit(limit)
        return query

    def check_account_history_transaction(self, transaction_id):
        from model import AccountHistory
        return self.accounts_history.filter(AccountHistory.customer_id == self.customer_id,
                                            AccountHistory.transaction_id == transaction_id).count()

    @classmethod
    def get_customers_by_prefix_info_field(cls, prefix, field):
        from model import PrivateCustomerInfo, EntityCustomerInfo

        if not field:
            raise Exception("Field for is not set")

        customers = []
        if getattr(PrivateCustomerInfo, field, None):
            query = cls.query.join(PrivateCustomerInfo).filter(cls.customer_type == cls.CUSTOMER_TYPE_PRIVATE_PERSON,
                                                               getattr(PrivateCustomerInfo, field).ilike(prefix + "%"))
            customers.extend(query.all())
        if getattr(EntityCustomerInfo, field, None):
            query = cls.query.join(EntityCustomerInfo).filter(cls.customer_type == cls.CUSTOMER_TYPE_LEGAL_ENTITY,
                                                              getattr(EntityCustomerInfo, field).ilike(prefix + "%"))
            customers.extend(query.all())
        return customers

    @classmethod
    def delete_by_prefix(cls, prefix, field=None):
        from model import Account, AccountHistory, Tenant
        from task.openstack import final_delete_from_os

        field = field or cls.unique_field
        if not field:
            raise Exception("Field for removing is not set")

        if getattr(cls, field, None):
            query = cls.query.filter(getattr(cls, field).like(prefix + "%"))
            ids = [customer_id for customer_id, in query.with_entities(cls.id_field)]
        else:
            ids = [item.customer_id for item in cls.get_customers_by_prefix_info_field(prefix, field)]

        if not ids:
            return 0
        logbook.info("Remove customers by prefix: {}", prefix)
        Subscription.query.filter(Subscription.customer_id.in_(ids)).delete(False)
        SubscriptionSwitch.query.filter(SubscriptionSwitch.customer_id.in_(ids)).delete(False)
        AccountHistory.query.filter(AccountHistory.customer_id.in_(ids)).delete(False)
        Account.query.filter(Account.customer_id.in_(ids)).delete(False)
        Quote.query.filter(Quote.customer_id.in_(ids)).delete(False)
        CustomerCard.query.filter(CustomerCard.customer_id.in_(ids)).delete(False)
        customers = cls.query.filter(cls.customer_id.in_(ids))
        if conf.devel.debug:
            logbook.debug("Remove customers {}", customers.all())

        tenants_ids = []
        for customer in customers:
            if customer.os_tenant_id:
                logbook.info("Final remove tenant {} for customer {}", customer.os_tenant_id, customer)
                final_delete_from_os.delay(customer.os_tenant_id, customer.os_user_id)
                tenants_ids.append(customer.os_tenant_id)
                customer.os_tenant_id = None
                customer.os_user_id = None
        if tenants_ids:
            Tenant.query.filter(Tenant.tenant_id.in_(tenants_ids)).delete(False)

        return customers.delete(False)

    def send_email_about_blocking(self, blocked, user_id):
        subscription_info = self.subscription_info()['status']
        if subscription_info['enable']:
            block_date = utcnow().datetime
            if blocked:
                if user_id:
                    template_id = MessageTemplate.CUSTOMER_BLOCKED_BY_MANAGER
                    subject, body = MessageTemplate.get_rendered_message(template_id, language=self.locale_language(),
                                                                         block_date=block_date, email=self.email)
                else:
                    template_id = MessageTemplate.CUSTOMER_BLOCKED
                    recommended_payment = conf.customer.minimum_recommended_payment.get(self.tariff.currency)
                    recommended_payment = {'money': Decimal(recommended_payment),
                                           'currency': self.tariff.currency}
                    subject, body = MessageTemplate.get_rendered_message(template_id, language=self.locale_language(),
                                                                         block_date=block_date, email=self.email,
                                                                         minimum_recomended_payment=recommended_payment)
            else:
                template_id = MessageTemplate.CUSTOMER_UNBLOCKED
                subject, body = MessageTemplate.get_rendered_message(template_id, language=self.locale_language())
            send_email.delay(subscription_info['email'], subject, body)

    def block(self, blocked, user_id, message=""):
        from model import CustomerHistory
        from task.openstack import block_user

        if self.blocked == blocked:
            return False

        logbook.debug("Customer {} is marked as {} because '{}'", self, "blocked" if blocked else "unblocked", message)
        self.blocked = blocked
        CustomerHistory.blocked(blocked, self, user_id, message)
        if self.os_user_id:
            block_user.delay(self.os_user_id, blocked)
            if self.customer_mode != self.CUSTOMER_TEST_MODE:
                if blocked:
                    BlockCustomer.create(self.customer_id)
                else:
                    BlockCustomer.stop(self.customer_id)

        self.send_email_about_blocking(blocked, user_id)

    def remove(self, user_id, comment):
        from model import CustomerHistory, Tenant
        from task.openstack import final_delete_from_os

        if not self.mark_removed():
            raise errors.CustomerRemoved()

        CustomerHistory.remove_customer(self, user_id, comment)
        if self.os_tenant_id or self.os_user_id:
            final_delete_from_os.delay(self.os_tenant_id, self.os_user_id)
        tenant_id = self.os_tenant_id
        self.update_os_credentials(None, None, None, None, None)
        Tenant.query.filter_by(tenant_id=tenant_id).delete(False)

    def get_history(self, after=None, before=None, limit=1000):
        from model import CustomerHistory
        query = self.history.filter(CustomerHistory.customer_id == self.customer_id)
        if after:
            query = query.filter(CustomerHistory.date >= after)
        if before:
            query = query.filter(CustomerHistory.date < before)
        query = query.order_by(desc(CustomerHistory.customer_history_id)).limit(limit)
        return query

    @classmethod
    def news_subscribers(cls):
        return cls.query.filter(cls.switches.any(name='news', enable=True))

    def subscription_info(self):
        subscriptions = self.subscriptions
        switches = self.switches

        result = {}

        for name in conf.subscription:
            subscription = subscriptions.filter(Subscription.name == name).all()
            switch = switches.filter(SubscriptionSwitch.name == name).one()
            emails = [s.email for s in subscription]
            result[name] = {'enable': switch.enable, 'email': emails}
        return result

    def subscriptions_update(self, data, user_id, comment=None):
        from model import CustomerHistory
        if self.deleted:
            raise errors.CustomerRemoved()

        subscriptions = self.subscriptions
        switches = self.switches
        for key, value in data.items():
            subscription = subscriptions.filter(Subscription.name == key)

            new_emails = set(value['email'])
            current_emails = {s.email for s in subscription}

            emails_to_add = new_emails - current_emails
            emails_to_delete = current_emails - new_emails

            if emails_to_add:
                for email in emails_to_add:
                    Subscription.new_subscription(name=key, email=email, customer_id=self.customer_id)
            if emails_to_delete:
                subscription.filter(Subscription.email.in_(emails_to_delete)).delete(False)

            switches.filter(SubscriptionSwitch.name == key).update({'enable': value["enable"]})
        CustomerHistory.change_info(self, user_id, comment)

    def init_subscriptions(self):
        for name in conf.subscription:
            if name in conf.customer.automatic_subscriptions:
                Subscription.new_subscription(name=name, email=self.email, customer_id=self.customer_id)
                SubscriptionSwitch.new_switch(name=name, enable=True, customer_id=self.customer_id)
            else:
                SubscriptionSwitch.new_switch(name=name, enable=False, customer_id=self.customer_id)

    def quota_info(self):
        quotas = self.quota
        return [quota.display() for quota in quotas]

    def quota_update(self, data, user_id):
        from task.customer import update_quota

        for name, value in data.items():
            self.quota.filter(Quote.name == name).update({'value': value})
        update_quota.delay(self.customer_id, user_id, data)

    def quota_init(self, template='test_customer'):
        for name, value in conf.template[template].items():
            Quote.new_quota(name=name, value=value, customer_id=self.customer_id)

    def make_production(self, user_id, comment):
        if not self.email_confirmed:
            raise errors.CustomerEmailIsNotConfirmed()
        account = self.get_account(self.tariff.currency)
        if account.current > self.balance_limit:
            self._make_prod(user_id, comment)
        else:
            self._make_pending_prod(user_id, comment)

    def check_wait_production(self):
        if self.customer_mode == self.CUSTOMER_PENDING_PRODUCTION_MODE:
            self._make_prod()

    def send_email_make_production(self):
        template_id = MessageTemplate.CUSTOMER_MAKE_PRODUCTION
        subject, body = MessageTemplate.get_rendered_message(template_id, language=self.locale_language())
        send_email.delay(self.email, subject, body)

    def _make_prod(self, user_id=None, comment=''):
        from model import CustomerHistory
        from task.customer import clean_up_customer_service_usage

        TestPeriodOver.stop(self.customer_id)
        account = self.get_account(self.tariff.currency)
        if account.current < 0:
            self.clean_up_balance(user_id)
        time_end = utcnow().datetime
        self.clean_up_service_usage(time_end)
        clean_up_customer_service_usage.apply_async((self.customer_id, time_end), eta=time_end + timedelta(hours=2))

        self.quota_update(conf.template['customer'], user_id)
        if self.blocked:
            self.block(blocked=False, user_id=user_id)
        openstack.change_tenant_quotas(self.os_tenant_id, **conf.template['customer'])
        self.update({'customer_mode': self.CUSTOMER_PRODUCTION_MODE})
        CustomerHistory.make_prod(self, user_id, comment)
        self.send_email_make_production()

    def _make_pending_prod(self, user_id, comment):
        from model import CustomerHistory

        TestPeriodOver.stop(self.customer_id)
        self.update({'customer_mode': self.CUSTOMER_PENDING_PRODUCTION_MODE})
        CustomerHistory.make_pending_prod(self, user_id, comment)

    def clean_up_balance(self, user_id=None):
        for currency, balance in self.balance_dict().items():
            self.modify_balance(-balance, currency, user_id,
                                'Customer balance is reset to 0.0 by system during switching to production mode',
                                cleanup=True)
        db.session.flush()

    def clean_up_service_usage(self, time_end):
        from model import ServiceUsage
        service_usages_to_clean = ServiceUsage.query.filter(ServiceUsage.tenant_id == self.os_tenant_id,
                                                            ServiceUsage.end <= time_end)
        total_cost = self.calculate_usage_cost(service_usages_to_clean.all())
        self.get_account(self.tariff.currency).charge(-total_cost)
        service_usages_to_clean.delete(False)

    @classmethod
    def get_by_tenant_id(cls, tenant_id):
        return cls.query.filter_by(os_tenant_id=tenant_id).first()

    def calculate_usage_cost(self, usages):
        from model import Service, Category, ServicePrice
        from task.notifications import notify_managers_about_new_service_in_tariff

        total_cost = Decimal()
        tariff = self.tariff
        if not tariff:
            raise Exception("Tariff is not set for customer %s" % self)

        services = tariff.services_as_dict(lower=True)
        for usage in usages:
            db.session.add(usage)
            service_id = usage.service_id.lower() if isinstance(usage.service_id, str) else str(usage.service_id)
            service_price = services.get(service_id)
            service = Service.get_by_id(service_id)

            usage.tariff_id = tariff.tariff_id
            usage.customer_mode = self.customer_mode

            if service is None:
                logbook.error("Not found declaration service {} during calculate usage for {}",
                              usage.service_id, self)
                continue

            usage_volume = service.calculate_volume_usage(usage)
            usage.usage_volume = usage_volume

            if service_price is None:
                if service.category_id == Category.VM:
                    if service.deleted:
                        logbook.error("Service {} not found in {} for {}. But this service is archived",
                                      service_id, tariff, self)
                    else:
                        service_price = ServicePrice(service_id=service_id, price=Decimal(0), need_changing=True)
                        self.tariff.services.append(service_price)
                        services = tariff.services_as_dict(lower=True)
                        flavor_name = Service.get_by_id(service_id).flavor.flavor_id
                        notify_managers_about_new_service_in_tariff.delay(self.customer_id, flavor_name)
                else:
                    logbook.warning("Service {} not found in {} for {}. Allowed services: {}",
                                    service_id, tariff, self, services.keys())

            if service_price:
                usage_cost = usage_volume * service_price.price / service.hours
            else:
                usage_cost = Decimal(0)
            total_cost += usage_cost
            usage.cost = usage_cost

        logbook.info("Found {} usages for customer {}. Total cost of used resources is: {}",
                     len(usages), self, total_cost)

        return total_cost

    def create_info(self, info_type: str, detailed_info):
        from model import PrivateCustomerInfo, EntityCustomerInfo
        if info_type == self.CUSTOMER_TYPE_PRIVATE_PERSON:
            self.private_info = PrivateCustomerInfo.create(self.customer_id, detailed_info)
        elif info_type == self.CUSTOMER_TYPE_LEGAL_ENTITY:
            self.entity_info = EntityCustomerInfo.create(self.customer_id, detailed_info)

    def locale_language(self):
        return language_from_locale(self.locale) or DEFAULT_LANGUAGE

    @classmethod
    def auto_withdraw_query(cls, email_prefix=None, now=None):
        query = None
        if email_prefix:
            query = cls.query.filter(cls.email.ilike("{}%".format(email_prefix)))
        query = ScheduledTask.scheduled_tasks(cls.AUTO_REPORT_TASK, now=now, query=query)
        return query

    @staticmethod
    def fake_usage(customer, start, finish, service_id, resource_id, volume, resource_name=None):
        from model import ServiceUsage
        from fitter.aggregation.timelabel import TimeLabel
        if customer.os_tenant_id is None:
            raise errors.TenantIsnotCreated()

        time_label = TimeLabel(start)
        finish_time_label = TimeLabel(finish)
        total_cost = Decimal(0)
        while time_label <= finish_time_label:
            st, fn = time_label.datetime_range()
            st = max(st, start)
            fn = min(fn, finish)
            service_usage = ServiceUsage(customer.os_tenant_id, service_id, time_label, resource_id,
                                         customer.tariff, volume, st, fn, resource_name=resource_name)
            db.session.add(service_usage)
            cost = customer.calculate_usage_cost([service_usage])
            customer.withdraw(cost)
            total_cost += cost
            time_label = time_label.next()
        return total_cost

    def pdf_invoice(self, amount, date, currency=None, number=None):
        from report.pdf_render import PdfRender

        entity_info = self.entity_info
        if entity_info is None:
            raise errors.CustomerIsNotEntity()
        number_str = str(number)
        if not number:
            number_str = "______"
            number = '<xpre>%s</xpre>' % number_str
        data = {"number": number,
                "number_str": number_str,
                "date": date,
                "currency": currency or self.tariff.currency,
                "amount": amount,
                "entity_info": entity_info,
                "nds": amount / Decimal(1.18) * Decimal(0.18)

                }
        data.update(conf.report.invoice)
        return PdfRender().render(data, "invoice", locale="ru_ru")

    @classmethod
    def customers_stat(cls):
        result = {"total": cls.query.count()}

        total_deleted = 0
        total_by_mode = Counter()
        total_blocked = 0
        for customer_type in cls.ALL_TYPES:
            query_by_type = cls.query.filter_by(customer_type=customer_type)
            for mode in cls.ALL_MODES:
                query = cls.query.filter_by(customer_mode=mode, customer_type=customer_type)
                metric_name = "%s_%s" % (mode, customer_type)
                count = query.count()
                total_by_mode[mode] += count
                result[metric_name] = count
                blocked = query.filter(cls.blocked.is_(True)).count()
                result[metric_name + "_blocked"] = blocked
                total_blocked += blocked

            deleted = query_by_type.filter(cls.deleted.isnot(None)).count()
            result[customer_type + "_deleted"] = deleted
            total_deleted += deleted
        result["total_deleted"] = total_deleted
        result["total_blocked"] = total_blocked
        for mode, count in total_by_mode.items():
            result["total_" + mode] = count

        return result

    def get_used_quotas(self):
        logbook.debug("Getting used quotas from openstack for {}", self)
        if self.os_tenant_id and not self.blocked:
            try:
                quotas = openstack.get_limits(self.os_tenant_id, self.os_username, self.os_user_password)
            except Unauthorized as e:
                logbook.warning("Customer {} is not blocked but can't sign in OpenStack account. Error message: {}",
                                self, e)
                quotas = {}
        else:
            quotas = {}

        if quotas:
            quota_cache.set(self, quotas)
        return quotas

    def used_quotas(self, force=False):
        from task.customer import get_used_quotas

        if not self.os_tenant_id:
            logbook.debug("Quotas are empty because tenant is not configured for {}", self)
            return {}, None

        if self.blocked:
            logbook.debug("Quotas are empty because {} is blocked", self)
            return {}, None

        quota = quota_cache.get(self)
        if not quota:
            logbook.debug("Quotas are missed in cache for {}", self)
            get_used_quotas.delay(self.customer_id)
            return None, None

        if quota.fresh:
            if force and quota.live_time > conf.customer.quota.min_live_time:
                get_used_quotas.delay(self.customer_id)
            return quota.used, quota.live_time

        get_used_quotas.delay(self.customer_id)
        return quota.used, quota.live_time

    @classmethod
    def active_tenants(cls):
        return [c.os_tenant_id for c in cls.query.filter(cls.os_tenant_id.isnot(None))]
예제 #11
0
class CustomerCard(db.Model, AccountDb):
    STATUS_ACTIVE = 0
    STATUS_DISABLED = 1
    STATUS_INVALID = 2

    status_info = {
        STATUS_ACTIVE: 'active',
        STATUS_INVALID: 'invalid',
        STATUS_DISABLED: 'disabled'}

    card_id = Column(db.Integer, primary_key=True)
    last_four = Column(db.String(4))
    card_type = Column(db.String(16))
    status = Column(db.Integer)
    token = Column(db.String(64))
    customer_id = Column(db.Integer, ForeignKey("customer.customer_id"))
    deleted = Column(db.DateTime())

    display_fields = frozenset(["card_id", "last_four", "card_type"])

    def __str__(self):
        return "<Customer card id:%s %s %s deleted:%s>" % (self.card_id, self.card_type, self.last_four, self.deleted)

    def display(self, short=True):
        res = super().display(short)
        res["status"] = self.status_info[self.status]
        return res

    @classmethod
    @duplicate_handle(errors.CustomerPaymentCardAlreadyExists)
    def add_card(cls, customer_id, last_four, card_type, token, active=False):
        new_card = cls()
        new_card.last_four = last_four
        new_card.card_type = card_type
        new_card.token = token
        new_card.customer_id = customer_id
        new_card.deleted = None
        if active:
            new_card.status = cls.STATUS_ACTIVE
            card = cls.get_active_card(customer_id)
            if card:
                card.status = cls.STATUS_DISABLED
        else:
            new_card.status = cls.STATUS_DISABLED

        db.session.add(new_card)
        return new_card

    @classmethod
    def delete_card(cls, card_id):
        card = cls.get_by_id(card_id)
        if not card:
            raise errors.NotFound
        if card.deleted is not None:
            raise errors.PaymentCardRemoved
        card.deleted = utcnow().datetime

    @classmethod
    def get_all_cards(cls, customer_id, include_deleted=False):
        query = CustomerCard.query.filter_by(customer_id=customer_id)
        if not include_deleted:
            query = query.filter_by(deleted=None)
        return query

    @classmethod
    def get_one(cls, card_id, customer_id=None, include_deleted=False):
        query = CustomerCard.query.filter_by(card_id=card_id)
        if customer_id:
            query = query.filter_by(customer_id=customer_id)
        if not include_deleted:
            query = query.filter_by(deleted=None)
        return query.first()

    @classmethod
    def get_active_card(cls, customer_id, include_deleted=False):
        query = cls.get_all_cards(customer_id, include_deleted).filter_by(status=CustomerCard.STATUS_ACTIVE)
        return query.first()

    def enable(self):
        if self.status != self.STATUS_ACTIVE:
            self.status = self.STATUS_ACTIVE
        return self

    def change_status(self, new_status):
        if self.deleted:
            raise errors.PaymentCardRemoved
        if new_status not in [CustomerCard.STATUS_ACTIVE,
                              CustomerCard.STATUS_DISABLED,
                              CustomerCard.STATUS_INVALID]:
            return
        self.status = new_status
예제 #12
0
class Deferred(db.Model, AccountDb):
    id_field = "deferred_id"

    deferred_id = Column(db.Integer, primary_key=True)
    user_id = Column(db.Integer, ForeignKey("user.user_id"))
    customer_id = Column(db.Integer,
                         ForeignKey("customer.customer_id"),
                         unique=True)
    tariff_id = Column(db.Integer, ForeignKey("tariff.tariff_id"))
    date = Column(db.DateTime(), index=True)
    comment = Column(db.Text())

    tariff = relationship("Tariff")
    user = relationship("User")
    customer = relationship("Customer")

    def __str__(self):
        return "<Deferred %s %s>" % (self.tariff.name, self.date)

    def __repr__(self):
        return str(self)

    @classmethod
    def create(cls, customer_id, tariff_id, user_id, date, comment):
        deferred = cls.query.filter_by(customer_id=customer_id).first()
        if not deferred:
            deferred = cls()
            deferred.customer_id = customer_id
            db.session.add(deferred)
        deferred.tariff_id = tariff_id
        deferred.user_id = user_id
        deferred.date = date
        deferred.comment = comment

        return deferred

    @classmethod
    def find_deferred(cls, now=None):
        now = now or utcnow()
        return cls.query.filter(cls.date <= now)

    def display(self, short=True):
        return {
            "tariff": self.tariff.display(short=True),
            "date": self.date,
            "user": self.user.display(),
            "comment": self.comment
        }

    @classmethod
    def delete_by_customer(cls, customer_id):
        return cls.query.filter_by(customer_id=customer_id).delete(False)

    @classmethod
    def get_by_customer(cls, customer_id):
        return cls.query.filter_by(customer_id=customer_id).first()

    @classmethod
    def process_pending_deferred_changes(cls, time_now=None, name_prefix=""):
        db.session.rollback()
        logbook.debug(
            "Process pending deferred changes task for customer prefix {} and time {}",
            name_prefix, time_now)
        time_now = time_now or utcnow().datetime
        query = cls.find_deferred(time_now)
        if name_prefix:
            customer_ids = [
                c.customer_id
                for c in Customer.get_customers_by_prefix_info_field(
                    name_prefix, "name")
            ]
            query = query.filter(
                cls.customer_id.in_(customer_ids)) if customer_ids else []

        count = 0
        for deferred in query:
            cls.do_deferred_changes(deferred)
            count += 1
        logbook.debug("Processed {} pending deferred changes", count)

        db.session.commit()
        return count

    @classmethod
    @autocommit
    def do_deferred_changes(cls, deferred_changes):
        logbook.info("Process pending deferred change {}", deferred_changes)

        deferred_changes.customer.update_tariff(deferred_changes.tariff_id,
                                                deferred_changes.user_id,
                                                deferred_changes.comment)
        Deferred.delete_by_customer(deferred_changes.customer_id)
예제 #13
0
class Service(db.Model, AccountDb, BaseService):
    service_id = Column(db.Integer, primary_key=True)
    localized_name = relationship("ServiceLocalization", cascade="all")
    description = relationship("ServiceDescription", cascade="all")
    measure_id = Column(db.String(32))
    category_id = Column(Enum(Category.CUSTOM, Category.VM))
    mutable = Column(db.Boolean())
    deleted = Column(db.DateTime())

    flavor = relationship("Flavor",
                          uselist=False,
                          backref="service",
                          cascade="all, delete-orphan")

    display_fields = ("service_id", "mutable", "deleted", "fixed")
    unique_field = "localized_name"
    id_field = "service_id"

    @classmethod
    @duplicate_handle(errors.ServiceAlreadyExisted)
    def create_service(cls,
                       localized_name,
                       category,
                       measure,
                       description=None,
                       mutable=True):
        assert isinstance(measure, Measure)

        if measure.measure_type != Measure.TIME:
            raise Exception("Custom services can have only time measure")
        service = cls()
        service.update_localized_name(localized_name)
        if description:
            service.update_description(description)
        service.measure_id = measure.measure_id
        service.mutable = mutable
        service.category_id = category

        db.session.add(service)
        db.session.flush()

        return service

    @classmethod
    def create_vm(cls,
                  localized_name,
                  description=None,
                  flavor_info=None,
                  mutable=True):
        measure = Measure('hour')
        service = Service.create_service(localized_name, Category.VM, measure,
                                         description, mutable)

        service.flavor = Flavor.new_flavor(service.service_id, **flavor_info)

        return service

    @classmethod
    def create_custom(
        cls,
        localized_name,
        measure,
        description=None,
    ):
        service = Service.create_service(localized_name, Category.CUSTOM,
                                         measure, description)
        return service

    def localized_name_as_dict(self):
        return {
            localization.language: localization
            for localization in self.localized_name
        }

    def description_as_dict(self):
        return {
            description.language: description
            for description in self.description
        }

    def get_localized_name(self, language):
        localized_name = self.localized_name_as_dict()
        return localized_name.get(
            language).localized_name or localized_name[DEFAULT_LANGUAGE]

    def update_localized_name(self, localized_name):
        current = self.localized_name_as_dict()
        for language, value in localized_name.items():
            if language in current:
                current[language].localized_name = value
            else:
                self.localized_name.append(
                    ServiceLocalization.create_localization(language, value))

    def update_description(self, localized_description):
        current = self.description_as_dict()
        for language, value in localized_description.items():
            if language in current:
                current[language].localized_description = value
            else:
                self.description.append(
                    ServiceDescription.create_localization(language, value))

    @classmethod
    def list(cls, only_categories=None, visibility=None):
        result = []

        query = cls.query
        if visibility == "all":
            pass
        elif visibility == "visible":
            query = cls.query.filter(cls.deleted == None)
        elif visibility == "deleted":
            query = cls.query.filter(cls.deleted != None)

        if not only_categories:
            result.extend(query)
        else:
            if Category.CUSTOM in only_categories:
                result.extend(query.filter(cls.category_id == Category.CUSTOM))
            if Category.VM in only_categories:
                result.extend(query.filter(cls.category_id == Category.VM))
        if visibility != 'deleted':
            result.extend(FixedService.list(only_categories))
        return result

    @classmethod
    def get_by_id(cls, service_id):
        try:
            # if service_id is int
            service_id = int(service_id)
            return super().get_by_id(service_id)
        except ValueError:
            pass

        return FixedService.get_by_id(service_id)

    def display(self, short=True):
        res = super().display(short)
        res["category"] = self.category.display()
        res["measure"] = self.measure.display()
        res["localized_name"] = {
            loc.language: loc.localized_name
            for loc in self.localized_name
        }
        res["description"] = {
            description.language: description.localized_description
            for description in self.description
        }
        if self.category_id == Category.VM:
            res["flavor"] = self.flavor.display()

        return res

    @duplicate_handle(errors.ServiceAlreadyExisted)
    def update_names(self, localized_name=None, description=None):
        if self.deleted:
            raise errors.RemovedService()

        if localized_name:
            self.update_localized_name(localized_name)
        if description:
            self.update_description(description)

        return True

    def update_custom(self,
                      localized_name=None,
                      description=None,
                      measure=None):
        if not self.mutable and measure:
            raise errors.ImmutableService()

        self.update_names(localized_name, description)

        if measure:
            if measure.measure_type != Measure.TIME:
                raise Exception("Custom services can have only time measure")
            self.measure_id = measure.measure_id

    def update_vm(self,
                  localized_name=None,
                  description=None,
                  flavor_info=None):
        if not self.mutable and flavor_info:
            raise errors.ImmutableService()

        self.update_names(localized_name=localized_name,
                          description=description)

        if flavor_info:
            self.flavor.update(
                {k: v
                 for k, v in flavor_info.items() if v is not None})

    @property
    def measure(self):
        return Measure(self.measure_id)

    @property
    def category(self):
        return Category.get_by_id(self.category_id)

    def mark_immutable(self):
        from task.openstack import create_flavor

        if self.deleted:
            raise errors.RemovedService()
        if self.mutable:
            self.mutable = False
            if self.category_id == Category.VM:
                vm = self.flavor
                try:
                    flavor = openstack.get_nova_flavor(vm.flavor_id)
                    logbook.info("Flavor {} already exists in OpenStack",
                                 vm.flavor_id)
                    if vm.vcpus != flavor.vcpus:
                        logbook.info(
                            "Flavor {} has different vcpus value(DB: {}, OS: {}). Changing DB value.",
                            vm.flavor_id, vm.vcpus, flavor.vcpus)
                        self.flavor.vcpus = flavor.vcpus
                    if vm.ram != flavor.ram:
                        logbook.info(
                            "Flavor {} has different ram value(DB: {}, OS: {}). Changing DB value.",
                            vm.flavor_id, vm.ram, flavor.ram)
                        self.flavor.ram = flavor.ram
                    if vm.disk != flavor.disk:
                        logbook.info(
                            "Flavor {} has different disk value(DB: {}, OS: {}). Changing DB value.",
                            vm.flavor_id, vm.disk, flavor.disk)
                        self.flavor.disk = flavor.disk
                except NotFound:
                    create_flavor.delay(vm.flavor_id,
                                        vm.vcpus,
                                        vm.ram,
                                        vm.disk,
                                        is_public=False)

            return True
        return False

    def used(self):
        from model import Tariff, ServicePrice
        """
        Returns number of tariffs with this service.
        """

        return Tariff.query.filter(
            ServicePrice.service_id == self.service_id, Tariff.deleted == None,
            Tariff.tariff_id == ServicePrice.tariff_id).count()

    def remove(self):
        if self.deleted:
            raise errors.RemovedService()
        if self.used() > 0:
            raise errors.RemovingUsedService()
        self.deleted = utcnow().datetime
        return True

    @classmethod
    def delete_by_prefix(cls, prefix, field=None):
        field = field or cls.unique_field
        if not field:
            raise Exception("Field for removing is not set")
        member = getattr(cls, field, None)
        if member:
            if field == "localized_name":
                query = cls.query.join(Service.localized_name).\
                    filter(ServiceLocalization.localized_name.ilike("%{}%".format(prefix + "%")))
            else:
                query = cls.query.filter(member.like(prefix + "%"))
            ids = [
                service_id
                for service_id, in query.with_entities(cls.service_id)
            ]
        else:
            ids = [
                flavor.service_id for flavor in Flavor.query.filter(
                    getattr(Flavor, field).ilike(prefix + "%"))
            ]
        if not ids:
            return 0
        ServiceLocalization.query.filter(
            ServiceLocalization.service_id.in_(ids)).delete(False)
        ServiceDescription.query.filter(
            ServiceDescription.service_id.in_(ids)).delete(False)
        Flavor.query.filter(Flavor.service_id.in_(ids)).delete(False)
        return cls.query.filter(cls.service_id.in_(ids)).delete(False)
예제 #14
0
class ScheduledTask(db.Model, AccountDb):
    id_field = "customer_id"
    unique_field = "email"

    task_id = Column(db.Integer, primary_key=True)
    task_name = Column(db.String(64), index=True)
    customer_id = Column(db.Integer, ForeignKey("customer.customer_id", ondelete="CASCADE"), index=True)
    started = Column(db.DateTime(), index=True)
    next_scheduled = Column(db.DateTime(timezone=True), index=True)
    frequency = Column(db.String(64), nullable=False)
    last = Column(db.DateTime(), index=True)

    default_cron_data = {"MIN": 0, "HOUR": 0, "DAY": 1, "WEEK_DAY": 1}

    def __init__(self, task_name, customer_id, frequency, cron_data=None, now=None):
        self.task_name = task_name
        self.customer_id = customer_id
        cron_frequency = self.cron_frequency(frequency, cron_data)
        logbook.debug("Setup task {} for customer {} with frequency: {}", task_name, customer_id, cron_frequency)
        now = now or utcnow().datetime
        try:
            cron = croniter(cron_frequency, start_time=now)
        except ValueError as e:
            logbook.error("Invalid frequency format {}: {}", cron_frequency, e)
            raise

        self.frequency = cron_frequency
        self.started = None
        self.next_scheduled = cron.get_next(datetime)
        self.last = now

    def cron_frequency(self, frequency, cron_data=None):
        data = self.default_cron_data.copy()
        conf_data = conf.event.event[self.task_name].get("periods", {}).get(frequency)
        if conf_data:
            data.update(conf_data)
        if cron_data:
            data.update(cron_data)
        cron_frequency = conf.event.period[frequency]["cron"]
        return cron_frequency.format(**data)

    def update_period(self, frequency, cron_data=None, now=None):
        cron_frequency = self.cron_frequency(frequency, cron_data=cron_data)
        now = now or utcnow().datetime
        try:
            cron = croniter(cron_frequency, start_time=now)
        except ValueError as e:
            logbook.error("Invalid frequency format {}: {}", cron_frequency, e)
            raise

        self.frequency = cron_frequency
        self.next_scheduled = cron.get_next(datetime)

    @classmethod
    def scheduled_tasks(cls, task_name, now=None, query=None):
        from model import Customer
        task_config = conf.event.event[task_name]
        now = now or utcnow().datetime
        now = now.replace(microsecond=0)

        query = query or cls.query

        query = query.filter(cls.task_name == task_name,
                             cls.next_scheduled < now,
                             or_(cls.started == None,
                                 cls.started < now - timedelta(seconds=task_config["task_hang"])))
        query = query.filter(cls.customer_id == Customer.customer_id).limit(task_config["limit"])
        return query

    @classmethod
    def get_by_customer(cls, customer_id, task_name):
        return cls.query.filter_by(customer_id=customer_id, task_name=task_name).first()

    def start(self, now=None, autocommit=True):
        self.started = now or utcnow().datetime
        if autocommit:
            db.session.commit()

    def completed(self, move_ahead=True, now=None):
        now = now or utcnow().datetime
        task_str = str(self)
        self.started = None
        if move_ahead:
            _, next_send = self.task_range(now)
            self.next_scheduled = next_send
            self.last = now
            logbook.info("Completed task {} and scheduled next at {}", task_str, next_send)
        else:
            logbook.info("Task {} failed and it will be rescheduled", task_str)
        db.session.flush()
        logbook.info("Task after prolongation {}", self)
        db.session.commit()

    def task_range(self, base_time=None, previous_interval=False, next_interval=False):
        if base_time is None:
            base_time = utcnow().datetime

        # Because croniter doesn't take seconds into account, add 1 minute
        base_time += timedelta(minutes=1)
        cron = croniter(self.frequency, base_time)
        if previous_interval:
            cron.get_prev(datetime)
        elif next_interval:
            cron.get_next(datetime)
            cron.get_next(datetime)
        return cron.get_prev(datetime), cron.get_next(datetime)

    @classmethod
    def remove_by_customer_id(cls, customer_id):
        return cls.query.filter_by(customer_id=customer_id).delete(False)