#! /usr/bin/python # # Copyright (c) 2014 IBM, Corp. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from __future__ import print_function from __future__ import division from __future__ import absolute_import import datetime import netaddr import sys import six from six.moves import range from dateutil import parser as datetime_parser from oslo_config import types BUILTIN_NAMESPACE = 'builtin' class DatetimeBuiltins(object): # casting operators (used internally) @classmethod def to_timedelta(cls, x): if isinstance(x, six.string_types): fields = x.split(":") num_fields = len(fields) args = {} keys = ['seconds', 'minutes', 'hours', 'days', 'weeks'] for i in range(0, len(fields)): args[keys[i]] = int(fields[num_fields - 1 - i]) return datetime.timedelta(**args) else: return datetime.timedelta(seconds=x) @classmethod def to_datetime(cls, x): return datetime_parser.parse(x, ignoretz=True) # current time @classmethod def now(cls): return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # extraction and creation of datetimes @classmethod def unpack_time(cls, x): x = cls.to_datetime(x) return (x.hour, x.minute, x.second) @classmethod def unpack_date(cls, x): x = cls.to_datetime(x) return (x.year, x.month, x.day) @classmethod def unpack_datetime(cls, x): x = cls.to_datetime(x) return (x.year, x.month, x.day, x.hour, x.minute, x.second) @classmethod def pack_time(cls, hour, minute, second): return "{}:{}:{}".format(hour, minute, second) @classmethod def pack_date(cls, year, month, day): return "{}-{}-{}".format(year, month, day) @classmethod def pack_datetime(cls, year, month, day, hour, minute, second): return "{}-{}-{} {}:{}:{}".format( year, month, day, hour, minute, second) # extraction/creation convenience function @classmethod def extract_date(cls, x): return str(cls.to_datetime(x).date()) @classmethod def extract_time(cls, x): return str(cls.to_datetime(x).time()) # conversion to seconds @classmethod def datetime_to_seconds(cls, x): since1900 = cls.to_datetime(x) - datetime.datetime(year=1900, month=1, day=1) return int(since1900.total_seconds()) # native operations on datetime @classmethod def datetime_plus(cls, x, y): return str(cls.to_datetime(x) + cls.to_timedelta(y)) @classmethod def datetime_minus(cls, x, y): return str(cls.to_datetime(x) - cls.to_timedelta(y)) @classmethod def datetime_lessthan(cls, x, y): return cls.to_datetime(x) < cls.to_datetime(y) @classmethod def datetime_lessthanequal(cls, x, y): return cls.to_datetime(x) <= cls.to_datetime(y) @classmethod def datetime_greaterthan(cls, x, y): return cls.to_datetime(x) > cls.to_datetime(y) @classmethod def datetime_greaterthanequal(cls, x, y): return cls.to_datetime(x) >= cls.to_datetime(y) @classmethod def datetime_equal(cls, x, y): return cls.to_datetime(x) == cls.to_datetime(y) class NetworkAddressBuiltins(object): @classmethod def ips_equal(cls, ip1, ip2): return netaddr.IPAddress(ip1) == netaddr.IPAddress(ip2) @classmethod def ips_lessthan(cls, ip1, ip2): return netaddr.IPAddress(ip1) < netaddr.IPAddress(ip2) @classmethod def ips_lessthan_equal(cls, ip1, ip2): return netaddr.IPAddress(ip1) <= netaddr.IPAddress(ip2) @classmethod def ips_greaterthan(cls, ip1, ip2): return netaddr.IPAddress(ip1) > netaddr.IPAddress(ip2) @classmethod def ips_greaterthan_equal(cls, ip1, ip2): return netaddr.IPAddress(ip1) >= netaddr.IPAddress(ip2) @classmethod def networks_equal(cls, cidr1, cidr2): return netaddr.IPNetwork(cidr1) == netaddr.IPNetwork(cidr2) @classmethod def networks_overlap(cls, cidr1, cidr2): cidr1_obj = netaddr.IPNetwork(cidr1) cidr2_obj = netaddr.IPNetwork(cidr2) return (cidr1_obj.first <= cidr2_obj.first <= cidr1_obj.last or cidr1_obj.first <= cidr2_obj.last <= cidr1_obj.last) @classmethod def ip_in_network(cls, ip, cidr): cidr_obj = netaddr.IPNetwork(cidr) ip_obj = netaddr.IPAddress(ip) return ip_obj in cidr_obj class OptTypeBuiltins(object): """Builtins to validate option values for config validator. It leverages oslog_config types module to check values. """ @classmethod def validate_int(cls, minv, maxv, value): """Check that the value is indeed an integer Optionnally checks the integer is between given bounds if provided. :param minv: minimal value or empty string :param maxv: maximal value or empty string :param value: value to check :return: an empty string if ok or an error string. """ maxv = None if maxv == '' else maxv minv = None if minv == '' else minv try: types.Integer(min=minv, max=maxv)(value) except (ValueError, TypeError): _, err, _ = sys.exc_info() return str(err) return '' @classmethod def validate_float(cls, minv, maxv, value): """Check that the value is a float Optionnally checks the float is between given bounds if provided. :param minv: minimal value or empty string :param maxv: maximal value or empty string :param value: value to check :return: an empty string if ok or an error string. """ maxv = None if maxv == '' else maxv minv = None if minv == '' else minv try: types.Float(min=minv, max=maxv)(value) except (ValueError, TypeError): _, err, _ = sys.exc_info() return str(err) return '' @classmethod def validate_string(cls, regex, max_length, quotes, ignore_case, value): """Check that the value is a string Optionnally checks the string against typical requirements. :param regex: a regular expression the value should follow or empty :param max_length: an integer bound on the size of the string or empty :param quotes: whether to include quotes or not :param ignore_case: whether to ignore case or not :param value: the value to check :return: an empty string if ok or an error string. """ regex = None if regex == '' else regex try: types.String(regex=regex, max_length=max_length, quotes=quotes, ignore_case=ignore_case)(value) except (ValueError, TypeError): _, err, _ = sys.exc_info() return str(err) return '' # the registry for builtins _builtin_map = { 'comparison': [ {'func': 'lt(x,y)', 'num_inputs': 2, 'code': lambda x, y: x < y}, {'func': 'lteq(x,y)', 'num_inputs': 2, 'code': lambda x, y: x <= y}, {'func': 'equal(x,y)', 'num_inputs': 2, 'code': lambda x, y: x == y}, {'func': 'gt(x,y)', 'num_inputs': 2, 'code': lambda x, y: x > y}, {'func': 'gteq(x,y)', 'num_inputs': 2, 'code': lambda x, y: x >= y}, {'func': 'max(x,y,z)', 'num_inputs': 2, 'code': lambda x, y: max(x, y)}], 'arithmetic': [ {'func': 'plus(x,y,z)', 'num_inputs': 2, 'code': lambda x, y: x + y}, {'func': 'minus(x,y,z)', 'num_inputs': 2, 'code': lambda x, y: x - y}, {'func': 'mul(x,y,z)', 'num_inputs': 2, 'code': lambda x, y: x * y}, {'func': 'div(x,y,z)', 'num_inputs': 2, 'code': lambda x, y: ((x // y) if (type(x) == int and type(y) == int) else (x / y))}, {'func': 'float(x,y)', 'num_inputs': 1, 'code': lambda x: float(x)}, {'func': 'int(x,y)', 'num_inputs': 1, 'code': lambda x: int(x)}], 'string': [ {'func': 'concat(x,y,z)', 'num_inputs': 2, 'code': lambda x, y: x + y}, {'func': 'len(x, y)', 'num_inputs': 1, 'code': lambda x: len(x)}], 'datetime': [ {'func': 'now(x)', 'num_inputs': 0, 'code': DatetimeBuiltins.now}, {'func': 'unpack_date(x, year, month, day)', 'num_inputs': 1, 'code': DatetimeBuiltins.unpack_date}, {'func': 'unpack_time(x, hours, minutes, seconds)', 'num_inputs': 1, 'code': DatetimeBuiltins.unpack_time}, {'func': 'unpack_datetime(x, y, m, d, h, i, s)', 'num_inputs': 1, 'code': DatetimeBuiltins.unpack_datetime}, {'func': 'pack_time(hours, minutes, seconds, result)', 'num_inputs': 3, 'code': DatetimeBuiltins.pack_time}, {'func': 'pack_date(year, month, day, result)', 'num_inputs': 3, 'code': DatetimeBuiltins.pack_date}, {'func': 'pack_datetime(y, m, d, h, i, s, result)', 'num_inputs': 6, 'code': DatetimeBuiltins.pack_datetime}, {'func': 'extract_date(x, y)', 'num_inputs': 1, 'code': DatetimeBuiltins.extract_date}, {'func': 'extract_time(x, y)', 'num_inputs': 1, 'code': DatetimeBuiltins.extract_time}, {'func': 'datetime_to_seconds(x, y)', 'num_inputs': 1, 'code': DatetimeBuiltins.datetime_to_seconds}, {'func': 'datetime_plus(x,y,z)', 'num_inputs': 2, 'code': DatetimeBuiltins.datetime_plus}, {'func': 'datetime_minus(x,y,z)', 'num_inputs': 2, 'code': DatetimeBuiltins.datetime_minus}, {'func': 'datetime_lt(x,y)', 'num_inputs': 2, 'code': DatetimeBuiltins.datetime_lessthan}, {'func': 'datetime_lteq(x,y)', 'num_inputs': 2, 'code': DatetimeBuiltins.datetime_lessthanequal}, {'func': 'datetime_gt(x,y)', 'num_inputs': 2, 'code': DatetimeBuiltins.datetime_greaterthan}, {'func': 'datetime_gteq(x,y)', 'num_inputs': 2, 'code': DatetimeBuiltins.datetime_greaterthanequal}, {'func': 'datetime_equal(x,y)', 'num_inputs': 2, 'code': DatetimeBuiltins.datetime_equal}], 'netaddr': [ {'func': 'ips_equal(x,y)', 'num_inputs': 2, 'code': NetworkAddressBuiltins.ips_equal}, {'func': 'ips_lt(x,y)', 'num_inputs': 2, 'code': NetworkAddressBuiltins.ips_lessthan}, {'func': 'ips_lteq(x,y)', 'num_inputs': 2, 'code': NetworkAddressBuiltins.ips_lessthan_equal}, {'func': 'ips_gt(x,y)', 'num_inputs': 2, 'code': NetworkAddressBuiltins.ips_greaterthan}, {'func': 'ips_gteq(x,y)', 'num_inputs': 2, 'code': NetworkAddressBuiltins.ips_greaterthan_equal}, {'func': 'networks_equal(x,y)', 'num_inputs': 2, 'code': NetworkAddressBuiltins.networks_equal}, {'func': 'networks_overlap(x,y)', 'num_inputs': 2, 'code': NetworkAddressBuiltins.networks_overlap}, {'func': 'ip_in_network(x,y)', 'num_inputs': 2, 'code': NetworkAddressBuiltins.ip_in_network}], 'type': [ {'func': 'validate_int(max, min, value, result)', 'num_inputs': 3, 'code': OptTypeBuiltins.validate_int}, {'func': 'validate_float(max, min, value, result)', 'num_inputs': 3, 'code': OptTypeBuiltins.validate_float}, {'func': 'validate_string(regex, max_length, quotes, ignore_case,' ' value, result)', 'num_inputs': 5, 'code': OptTypeBuiltins.validate_string}], } class CongressBuiltinPred(object): def __init__(self, name, arglist, num_inputs, code): self.predname = name self.predargs = arglist self.num_inputs = num_inputs self.code = code self.num_outputs = len(arglist) - num_inputs def string_to_pred(self, predstring): try: self.predname = predstring.split('(')[0] self.predargs = predstring.split('(')[1].split(')')[0].split(',') except Exception: print("Unexpected error in parsing predicate string") def __str__(self): return self.predname + '(' + ",".join(self.predargs) + ')' class CongressBuiltinCategoryMap(object): def __init__(self, start_builtin_map): self.categorydict = dict() self.preddict = dict() for key, value in start_builtin_map.items(): self.categorydict[key] = [] for predtriple in value: pred = self.dict_predtriple_to_pred(predtriple) self.categorydict[key].append(pred) self.sync_with_predlist(pred.predname, pred, key, 'add') def mapequal(self, othercbc): if self.categorydict == othercbc.categorydict: return True else: return False def dict_predtriple_to_pred(self, predtriple): ncode = predtriple['code'] ninputs = predtriple['num_inputs'] nfunc = predtriple['func'] nfunc_pred = nfunc.split("(")[0] nfunc_arglist = nfunc.split("(")[1].split(")")[0].split(",") pred = CongressBuiltinPred(nfunc_pred, nfunc_arglist, ninputs, ncode) return pred def add_map(self, newmap): for key, value in newmap.items(): if key not in self.categorydict: self.categorydict[key] = [] for predtriple in value: pred = self.dict_predtriple_to_pred(predtriple) if not self.builtin_is_registered(pred): self.categorydict[key].append(pred) self.sync_with_predlist(pred.predname, pred, key, 'add') def delete_map(self, newmap): for key, value in newmap.items(): for predtriple in value: predtotest = self.dict_predtriple_to_pred(predtriple) for pred in self.categorydict[key]: if pred.predname == predtotest.predname: if pred.num_inputs == predtotest.num_inputs: self.categorydict[key].remove(pred) self.sync_with_predlist(pred.predname, pred, key, 'del') if self.categorydict[key] == []: del self.categorydict[key] def sync_with_predlist(self, predname, pred, category, operation): if operation == 'add': self.preddict[predname] = [pred, category] if operation == 'del': if predname in self.preddict: del self.preddict[predname] def delete_builtin(self, category, name, inputs): if category not in self.categorydict: self.categorydict[category] = [] for pred in self.categorydict[category]: if pred.num_inputs == inputs and pred.predname == name: self.categorydict[category].remove(pred) self.sync_with_predlist(name, pred, category, 'del') def get_category_name(self, predname, predinputs): if predname in self.preddict: if self.preddict[predname][0].num_inputs == predinputs: return self.preddict[predname][1] return None def exists_category(self, category): return category in self.categorydict def insert_category(self, category): self.categorydict[category] = [] def delete_category(self, category): if category in self.categorydict: categorypreds = self.categorydict[category] for pred in categorypreds: self.sync_with_predlist(pred.predname, pred, category, 'del') del self.categorydict[category] def insert_to_category(self, category, pred): if category in self.categorydict: self.categorydict[category].append(pred) self.sync_with_predlist(pred.predname, pred, category, 'add') else: assert("Category does not exist") def delete_from_category(self, category, pred): if category in self.categorydict: self.categorydict[category].remove(pred) self.sync_with_predlist(pred.predname, pred, category, 'del') else: assert("Category does not exist") def delete_all_in_category(self, category): if category in self.categorydict: categorypreds = self.categorydict[category] for pred in categorypreds: self.sync_with_predlist(pred.predname, pred, category, 'del') self.categorydict[category] = [] else: assert("Category does not exist") def builtin_is_registered(self, predtotest): """Given a CongressBuiltinPred, check if it has been registered.""" pname = predtotest.predname if pname in self.preddict: if self.preddict[pname][0].num_inputs == predtotest.num_inputs: return True return False def is_builtin(self, table, arity=None): """Given a Tablename and arity, check if it is a builtin.""" # Note: for now we grandfather in old builtin tablenames but will # deprecate those tablenames in favor of builtin:tablename if ((table.service == BUILTIN_NAMESPACE and table.table in self.preddict) or table.table in self.preddict): # grandfather if not arity: return True if len(self.preddict[table.table][0].predargs) == arity: return True return False def builtin(self, table): """Return a CongressBuiltinPred for given Tablename or None.""" if not isinstance(table, six.string_types): table = table.table if table in self.preddict: return self.preddict[table][0] return None def list_available_builtins(self): """Print out the list of builtins, by category.""" for key, value in self.categorydict.items(): predlist = self.categorydict[key] for pred in predlist: print(str(pred)) # a Singleton that serves as the entry point for builtin functionality builtin_registry = CongressBuiltinCategoryMap(_builtin_map)