Files
cgrates/engine/aliases.go
2015-09-18 13:32:19 +03:00

466 lines
12 KiB
Go

package engine
import (
"reflect"
"sort"
"strings"
"sync"
"github.com/cgrates/cgrates/cache2go"
"github.com/cgrates/cgrates/utils"
"github.com/cgrates/rpcclient"
)
// Temporary export AliasService for the ApierV1 to be able to emulate old APIs
func GetAliasService() AliasService {
return aliasService
}
type Alias struct {
Direction string
Tenant string
Category string
Account string
Subject string
Context string
Values AliasValues
}
type AliasValue struct {
DestinationId string
Pairs AliasPairs
Weight float64
}
func (av *AliasValue) Equals(other *AliasValue) bool {
return av.DestinationId == other.DestinationId &&
av.Pairs.Equals(other.Pairs) &&
av.Weight == other.Weight
}
type AliasPairs map[string]map[string]string
func (ap AliasPairs) Equals(other AliasPairs) bool {
if len(ap) != len(other) {
return false
}
for attribute, origAlias := range ap {
otherOrigAlias, ok := other[attribute]
if !ok || len(origAlias) != len(otherOrigAlias) {
return false
}
for orig := range origAlias {
if origAlias[orig] != otherOrigAlias[orig] {
return false
}
}
}
return true
}
type AliasValues []*AliasValue
func (avs AliasValues) Len() int {
return len(avs)
}
func (avs AliasValues) Swap(i, j int) {
avs[i], avs[j] = avs[j], avs[i]
}
func (avs AliasValues) Less(j, i int) bool { // get higher weight in front
return avs[i].Weight < avs[j].Weight
}
func (avs AliasValues) Sort() {
sort.Sort(avs)
}
func (avs AliasValues) GetValueByDestId(destID string) *AliasValue {
for _, value := range avs {
if value.DestinationId == destID {
return value
}
}
return nil
}
func (al *Alias) GetId() string {
return utils.ConcatenatedKey(al.Direction, al.Tenant, al.Category, al.Account, al.Subject, al.Context)
}
func (al *Alias) GenerateIds() []string {
var result []string
result = append(result, utils.ConcatenatedKey(al.Direction, al.Tenant, al.Category, al.Account, al.Subject, al.Context))
result = append(result, utils.ConcatenatedKey(al.Direction, al.Tenant, al.Category, al.Account, utils.ANY, al.Context))
result = append(result, utils.ConcatenatedKey(al.Direction, al.Tenant, al.Category, utils.ANY, utils.ANY, al.Context))
result = append(result, utils.ConcatenatedKey(al.Direction, al.Tenant, utils.ANY, utils.ANY, utils.ANY, al.Context))
result = append(result, utils.ConcatenatedKey(al.Direction, utils.ANY, utils.ANY, utils.ANY, utils.ANY, al.Context))
result = append(result, utils.ConcatenatedKey(utils.ANY, utils.ANY, utils.ANY, utils.ANY, al.Context))
return result
}
func (al *Alias) SetId(id string) error {
vals := strings.Split(id, utils.CONCATENATED_KEY_SEP)
if len(vals) != 6 {
return utils.ErrInvalidKey
}
al.Direction = vals[0]
al.Tenant = vals[1]
al.Category = vals[2]
al.Account = vals[3]
al.Subject = vals[4]
al.Context = vals[5]
return nil
}
type AttrMatchingAlias struct {
Destination string
Direction string
Tenant string
Category string
Account string
Subject string
Context string
Target string
Original string
}
type AttrReverseAlias struct {
Alias string
Target string
Context string
}
type AliasService interface {
SetAlias(Alias, *string) error
UpdateAlias(Alias, *string) error
RemoveAlias(Alias, *string) error
GetAlias(Alias, *Alias) error
GetMatchingAlias(AttrMatchingAlias, *string) error
GetReverseAlias(AttrReverseAlias, *map[string][]*Alias) error
RemoveReverseAlias(AttrReverseAlias, *string) error
}
type AliasHandler struct {
accountingDb AccountingStorage
mu sync.RWMutex
}
func NewAliasHandler(accountingDb AccountingStorage) *AliasHandler {
return &AliasHandler{
accountingDb: accountingDb,
}
}
func (am *AliasHandler) SetAlias(al Alias, reply *string) error {
am.mu.Lock()
defer am.mu.Unlock()
if err := am.accountingDb.SetAlias(&al); err != nil {
*reply = err.Error()
return err
} //add to cache
aliasesChanged := []string{utils.ALIASES_PREFIX + al.GetId()}
if err := am.accountingDb.CacheAccountingPrefixValues(map[string][]string{utils.ALIASES_PREFIX: aliasesChanged}); err != nil {
return utils.NewErrServerError(err)
}
*reply = utils.OK
return nil
}
func (am *AliasHandler) UpdateAlias(al Alias, reply *string) error {
am.mu.Lock()
defer am.mu.Unlock()
// get previous value
oldAlias := &Alias{}
if err := am.GetAlias(al, oldAlias); err != nil {
*reply = err.Error()
return err
}
for _, oldValue := range oldAlias.Values {
found := false
for _, value := range al.Values {
if oldValue.Equals(value) {
found = true
break
}
}
if !found {
al.Values = append(al.Values, oldValue)
}
}
if err := am.accountingDb.SetAlias(&al); err != nil {
*reply = err.Error()
return err
} //add to cache
aliasesChanged := []string{utils.ALIASES_PREFIX + al.GetId()}
if err := am.accountingDb.CacheAccountingPrefixValues(map[string][]string{utils.ALIASES_PREFIX: aliasesChanged}); err != nil {
return utils.NewErrServerError(err)
}
*reply = utils.OK
return nil
}
func (am *AliasHandler) RemoveAlias(al Alias, reply *string) error {
am.mu.Lock()
defer am.mu.Unlock()
if err := am.accountingDb.RemoveAlias(al.GetId()); err != nil {
*reply = err.Error()
return err
}
*reply = utils.OK
return nil
}
func (am *AliasHandler) RemoveReverseAlias(attr AttrReverseAlias, reply *string) error {
am.mu.Lock()
defer am.mu.Unlock()
rKey := utils.REVERSE_ALIASES_PREFIX + attr.Alias + attr.Target + attr.Context
if x, err := cache2go.Get(rKey); err == nil {
existingKeys := x.(map[string]bool)
for key := range existingKeys {
// get destination id
elems := strings.Split(key, utils.CONCATENATED_KEY_SEP)
if len(elems) > 0 {
key = strings.Join(elems[:len(elems)-1], utils.CONCATENATED_KEY_SEP)
}
if err := am.accountingDb.RemoveAlias(key); err != nil {
*reply = err.Error()
return err
}
}
}
*reply = utils.OK
return nil
}
func (am *AliasHandler) GetAlias(al Alias, result *Alias) error {
am.mu.RLock()
defer am.mu.RUnlock()
variants := al.GenerateIds()
for _, variant := range variants {
if r, err := am.accountingDb.GetAlias(variant, false); err == nil {
*result = *r
return nil
}
}
return utils.ErrNotFound
}
func (am *AliasHandler) GetReverseAlias(attr AttrReverseAlias, result *map[string][]*Alias) error {
am.mu.Lock()
defer am.mu.Unlock()
aliases := make(map[string][]*Alias)
rKey := utils.REVERSE_ALIASES_PREFIX + attr.Alias + attr.Target + attr.Context
if x, err := cache2go.Get(rKey); err == nil {
existingKeys := x.(map[string]bool)
for key := range existingKeys {
// get destination id
elems := strings.Split(key, utils.CONCATENATED_KEY_SEP)
var destID string
if len(elems) > 0 {
destID = elems[len(elems)-1]
key = strings.Join(elems[:len(elems)-1], utils.CONCATENATED_KEY_SEP)
}
if r, err := am.accountingDb.GetAlias(key, false); err != nil {
return err
} else {
aliases[destID] = append(aliases[destID], r)
}
}
}
*result = aliases
return nil
}
func (am *AliasHandler) GetMatchingAlias(attr AttrMatchingAlias, result *string) error {
response := Alias{}
if err := am.GetAlias(Alias{
Direction: attr.Direction,
Tenant: attr.Tenant,
Category: attr.Category,
Account: attr.Account,
Subject: attr.Subject,
Context: attr.Context,
}, &response); err != nil {
return err
}
// sort according to weight
values := response.Values
values.Sort()
// if destination does not metter get first alias
if attr.Destination == "" || attr.Destination == utils.ANY {
for _, value := range values {
if origAlias, ok := value.Pairs[attr.Target]; ok {
if alias, ok := origAlias[attr.Original]; ok {
*result = alias
return nil
}
}
}
return utils.ErrNotFound
}
// check destination ids
for _, p := range utils.SplitPrefix(attr.Destination, MIN_PREFIX_MATCH) {
if x, err := cache2go.Get(utils.DESTINATION_PREFIX + p); err == nil {
destIds := x.(map[interface{}]struct{})
for _, value := range values {
for idId := range destIds {
dId := idId.(string)
if value.DestinationId == utils.ANY || value.DestinationId == dId {
if origAliasMap, ok := value.Pairs[attr.Target]; ok {
if alias, ok := origAliasMap[attr.Original]; ok || attr.Original == "" || attr.Original == utils.ANY {
*result = alias
return nil
}
if alias, ok := origAliasMap[utils.ANY]; ok {
*result = alias
return nil
}
}
}
}
}
}
}
return utils.ErrNotFound
}
type ProxyAliasService struct {
Client *rpcclient.RpcClient
}
func NewProxyAliasService(addr string, attempts, reconnects int) (*ProxyAliasService, error) {
client, err := rpcclient.NewRpcClient("tcp", addr, attempts, reconnects, utils.GOB)
if err != nil {
return nil, err
}
return &ProxyAliasService{Client: client}, nil
}
func (ps *ProxyAliasService) SetAlias(al Alias, reply *string) error {
return ps.Client.Call("AliasesV1.SetAlias", al, reply)
}
func (ps *ProxyAliasService) UpdateAlias(al Alias, reply *string) error {
return ps.Client.Call("AliasesV1.UpdateAlias", al, reply)
}
func (ps *ProxyAliasService) RemoveAlias(al Alias, reply *string) error {
return ps.Client.Call("AliasesV1.RemoveAlias", al, reply)
}
func (ps *ProxyAliasService) GetAlias(al Alias, alias *Alias) error {
return ps.Client.Call("AliasesV1.GetAlias", al, alias)
}
func (ps *ProxyAliasService) GetMatchingAlias(attr AttrMatchingAlias, alias *string) error {
return ps.Client.Call("AliasesV1.GetMatchingAlias", attr, alias)
}
func (ps *ProxyAliasService) GetReverseAlias(attr AttrReverseAlias, alias *map[string][]*Alias) error {
return ps.Client.Call("AliasesV1.GetReverseAlias", attr, alias)
}
func (ps *ProxyAliasService) RemoveReverseAlias(attr AttrReverseAlias, reply *string) error {
return ps.Client.Call("AliasesV1.RemoveReverseAlias", attr, reply)
}
func (ps *ProxyAliasService) ReloadAliases(in string, reply *string) error {
return ps.Client.Call("AliasesV1.ReloadAliases", in, reply)
}
func LoadAlias(attr *AttrMatchingAlias, in interface{}, extraFields string) error {
if aliasService == nil { // no alias service => no fun
return nil
}
response := Alias{}
if err := aliasService.GetAlias(Alias{
Direction: attr.Direction,
Tenant: attr.Tenant,
Category: attr.Category,
Account: attr.Account,
Subject: attr.Subject,
Context: attr.Context,
}, &response); err != nil {
return err
}
// sort according to weight
values := response.Values
values.Sort()
var rightPairs AliasPairs
// if destination does not metter get first alias
if attr.Destination == "" || attr.Destination == utils.ANY {
rightPairs = values[0].Pairs
}
if rightPairs == nil {
// check destination ids
for _, p := range utils.SplitPrefix(attr.Destination, MIN_PREFIX_MATCH) {
if x, err := cache2go.Get(utils.DESTINATION_PREFIX + p); err == nil {
destIds := x.(map[interface{}]struct{})
for _, value := range values {
for idId := range destIds {
dId := idId.(string)
if value.DestinationId == utils.ANY || value.DestinationId == dId {
rightPairs = value.Pairs
}
if rightPairs != nil {
break
}
}
if rightPairs != nil {
break
}
}
}
if rightPairs != nil {
break
}
}
}
if rightPairs != nil {
// change values in the given object
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
for target, originalAlias := range rightPairs {
for original, alias := range originalAlias {
field := v.FieldByName(target)
if field.IsValid() {
if field.Kind() == reflect.String {
if field.CanSet() && (original == "" || original == utils.ANY || field.String() == original) {
field.SetString(alias)
}
}
}
if extraFields != "" {
efField := v.FieldByName(extraFields)
if efField.IsValid() && efField.Kind() == reflect.Map {
keys := efField.MapKeys()
for _, key := range keys {
if key.Kind() == reflect.String && key.String() == target {
if original == "" || original == utils.ANY || efField.MapIndex(key).String() == original {
efField.SetMapIndex(key, reflect.ValueOf(alias))
}
}
}
}
}
}
}
}
return nil
}