Skip to content

Commit

Permalink
Merge pull request #528 from anandrgitnirman/license
Browse files Browse the repository at this point in the history
License
  • Loading branch information
anandrgitnirman authored Feb 25, 2021
2 parents f110ca9 + cd1dac1 commit 823da18
Show file tree
Hide file tree
Showing 6 changed files with 776 additions and 0 deletions.
46 changes: 46 additions & 0 deletions license_server/license_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package license_server

import (
"math/big"
)

type LicenseService interface {
GetLicenseUsage(key LicenseUsageTrackerKey) (*LicenseUsageTrackerData, bool, error)
UpdateLicenseUsage(channelId *big.Int, serviceId string, revisedUsage *big.Int, updateUsageType string, licenseType string) error
GetLicenseForChannel(key LicenseDetailsKey) (*LicenseDetailsData, bool, error)
UpdateLicenseForChannel(channelId *big.Int, serviceId string, license License) error
}

type LicenseFilterCriteria struct {
}

type LicenseTransaction interface {
ServiceId() string
ChannelId() *big.Int
Usage() *big.Int
Commit() error
Rollback() error
}

//this is used to track the license Usage
type licenseUsageTrackerTransactionImpl struct {
channelId *big.Int
usage Usage
serviceId string
}

func (transaction licenseUsageTrackerTransactionImpl) ServiceId() string {
return transaction.serviceId
}
func (transaction licenseUsageTrackerTransactionImpl) ChannelId() *big.Int {
return transaction.channelId
}
func (transaction licenseUsageTrackerTransactionImpl) Usage() Usage {
return transaction.usage
}
func (transaction licenseUsageTrackerTransactionImpl) Commit() error {
return nil
}
func (transaction licenseUsageTrackerTransactionImpl) Rollback() error {
return nil
}
1 change: 1 addition & 0 deletions license_server/license_contract.proto
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ message CheckLicenseUsageRequest {
CallerAuthentication auth = 1;
uint64 channel_id = 2;
string service_id = 3;
uint64 price_in_cogs = 4;

}

Expand Down
237 changes: 237 additions & 0 deletions license_server/license_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
package license_server

import (
"fmt"
"github.com/singnet/snet-daemon/blockchain"
"github.com/singnet/snet-daemon/storage"
"math/big"
"strings"
)

type LockingLicenseService struct {
LicenseDetailsStorage storage.TypedAtomicStorage
LicenseUsageStorage storage.TypedAtomicStorage
Org *blockchain.OrganizationMetaData
}

//Will be used in the components to create a new instance of LicenseService
func NewLicenseService(
detailsStorage storage.TypedAtomicStorage,
licenseStorage storage.TypedAtomicStorage, orgData *blockchain.OrganizationMetaData,
) *LockingLicenseService {
return &LockingLicenseService{
LicenseDetailsStorage: detailsStorage,
LicenseUsageStorage: licenseStorage,
Org: orgData,
}
}

func (h *LockingLicenseService) CreateOrUpdateLicense(channelId *big.Int, serviceId string) (err error) {
return h.UpdateLicenseUsage(channelId, serviceId, nil, PLANNED, SUBSCRIPTION)
}

func (h *LockingLicenseService) GetLicenseUsage(key LicenseUsageTrackerKey) (*LicenseUsageTrackerData, bool, error) {
value, ok, err := h.LicenseUsageStorage.Get(key)
if err != nil || !ok {
return nil, ok, err
}
return value.(*LicenseUsageTrackerData), ok, err

}

func (h *LockingLicenseService) GetLicenseForChannel(key LicenseDetailsKey) (*LicenseDetailsData, bool, error) {
value, ok, err := h.LicenseDetailsStorage.Get(key)
if err != nil || !ok {
return nil, ok, err
}
return value.(*LicenseDetailsData), ok, err
}

func (h *LockingLicenseService) UpdateLicenseForChannel(channelId *big.Int, serviceId string, license License) error {
return h.LicenseDetailsStorage.Put(LicenseDetailsKey{ServiceID: serviceId, ChannelID: channelId}, &LicenseDetailsData{License: license})
}

//Defines the condition that needs to be met, it generates the respective typed Data when
//conditions are satisfied, you define your own validations in here
//It takes in the latest typed values read.
type ConditionFuncForLicense func(conditionValues []storage.TypedKeyValueData,
incrementUsage *big.Int, channelId *big.Int, serviceId string) ([]storage.TypedKeyValueData, error)

func (h *LockingLicenseService) UpdateLicenseUsage(channelId *big.Int, serviceId string, incrementUsage *big.Int, updateUsageType string, licenseType string) error {
var conditionFunc ConditionFuncForLicense = nil

switch updateUsageType {
case USED:
conditionFunc = IncrementUsedUsage

case PLANNED:
conditionFunc = UpdatePlannedUsage

case REFUND:
conditionFunc = IncrementRefundUsage

default:
return fmt.Errorf("unknown update type %v", updateUsageType)
}

typedUpdateFunc := func(conditionValues []storage.TypedKeyValueData) (update []storage.TypedKeyValueData, ok bool, err error) {
var newValues []storage.TypedKeyValueData
if newValues, err = conditionFunc(conditionValues, incrementUsage, channelId, serviceId); err != nil {
return nil, false, err
}
return newValues, true, nil
}
typedKeys := getAllLicenseKeys(channelId, serviceId)
request := storage.TypedCASRequest{
Update: typedUpdateFunc,
RetryTillSuccessOrError: true,
ConditionKeys: typedKeys,
}
ok, err := h.LicenseUsageStorage.ExecuteTransaction(request)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("Error in executing ExecuteTransaction for usage type"+
" %v on channel %v ", updateUsageType, channelId)
}
return nil
}
func getAllLicenseKeys(channelId *big.Int, serviceId string) []interface{} {
keys := make([]interface{}, 3)
for i, usageType := range []string{REFUND, PLANNED, USED} {
keys[i] = LicenseUsageTrackerKey{ChannelID: channelId, ServiceID: serviceId, UsageType: usageType}
}
return keys
}

//this function will be used to read typed data ,convert it in to a business structure
//on which validations can be easily performed and return back the business structure.
func convertTypedDataToLicenseDataUsage(data []storage.TypedKeyValueData) (new *LicenseUsageData, err error) {
usageData := &LicenseUsageData{
Planned: &UsageInAmount{Amount: big.NewInt(0), UsageType: PLANNED},
Used: &UsageInAmount{Amount: big.NewInt(0), UsageType: USED},
Refund: &UsageInAmount{Amount: big.NewInt(0), UsageType: REFUND},
}
for _, usageType := range data {
key := usageType.Key.(LicenseUsageTrackerKey)
usageData.ChannelID = key.ChannelID
usageData.ServiceID = key.ServiceID
if !usageType.Present {
continue
}
data := usageType.Value.(*LicenseUsageTrackerData)
if strings.Compare(key.UsageType, USED) == 0 {
usageData.Used = data.Usage
} else if strings.Compare(key.UsageType, PLANNED) == 0 {
usageData.Planned = data.Usage
} else if strings.Compare(key.UsageType, REFUND) == 0 {
usageData.Refund = data.Usage
} else {
return nil, fmt.Errorf("unknown usage type %v", key.UsageType)
}
}
return usageData, nil
}

func BuildOldAndNewLicenseUsageValuesForCAS(data *LicenseUsageData) (newValues []storage.TypedKeyValueData, err error) {
updateUsageData := &LicenseUsageTrackerData{ChannelID: data.ChannelID, ServiceID: data.ServiceID}
updateUsageKey := LicenseUsageTrackerKey{ChannelID: data.ChannelID, ServiceID: data.ServiceID,
UsageType: data.UpdateUsageType}
if usage, err := data.GetUsageForUsageType(); err != nil {
return nil, err
} else {
updateUsageData.Usage = usage
}
newValue := storage.TypedKeyValueData{Key: updateUsageKey, Value: updateUsageData, Present: true}
newValues = make([]storage.TypedKeyValueData, 1)
newValues[0] = newValue

return newValues, nil
}

var (
IncrementUsedUsage ConditionFuncForLicense = func(conditionValues []storage.TypedKeyValueData, incrementUsage *big.Int,
channelId *big.Int, serviceId string) (newValues []storage.TypedKeyValueData, err error) {
oldState, err := convertTypedDataToLicenseDataUsage(conditionValues)
if err != nil {
return nil, err
}
oldState.ChannelID = channelId
oldState.ServiceID = serviceId
newState := oldState.Clone()
usageKey := LicenseUsageTrackerKey{UsageType: USED, ChannelID: oldState.ChannelID, ServiceID: serviceId}
if incrementUsage.Cmp(big.NewInt(0)) > 0 {
updateLicenseUsageData(newState, usageKey, incrementUsage)
if newState.Used.GetUsage().Cmp(oldState.Planned.GetUsage().Add(oldState.Planned.GetUsage(), oldState.Refund.GetUsage())) > 0 {
return nil, fmt.Errorf("usage exceeded on channel Id %v", oldState.ChannelID)
}
} else {
newState.UpdateUsageType = USED
newState.Used.SetUsage(big.NewInt(0))
}
return BuildOldAndNewLicenseUsageValuesForCAS(newState)

}
//Make sure you update the planned amount ONLY when the new value is greater than what was last persisted
UpdatePlannedUsage ConditionFuncForLicense = func(conditionValues []storage.TypedKeyValueData, incrementUsage *big.Int,
channelId *big.Int, serviceId string) (newValues []storage.TypedKeyValueData, err error) {
oldState, err := convertTypedDataToLicenseDataUsage(conditionValues)
if err != nil {
return nil, err
}
//Assuming there are no entries yet on this channel, it is very easy to pass the channel ID to the condition
//function and pick it from there
oldState.ChannelID = channelId
oldState.ServiceID = serviceId
newState := oldState.Clone()
usageKey := LicenseUsageTrackerKey{UsageType: PLANNED, ChannelID: oldState.ChannelID, ServiceID: serviceId}
updateLicenseUsageData(newState, usageKey, incrementUsage)
return BuildOldAndNewLicenseUsageValuesForCAS(newState)

}
//If there is no refund amount yet, put it , else add latest value in DB with the additional refund to be done
IncrementRefundUsage ConditionFuncForLicense = func(conditionValues []storage.TypedKeyValueData, incrementUsage *big.Int,
channelId *big.Int, serviceId string) (newValues []storage.TypedKeyValueData, err error) {
newState, err := convertTypedDataToLicenseDataUsage(conditionValues)
if err != nil {
return nil, err
}
newState.ChannelID = channelId
newState.ServiceID = serviceId
usageKey := LicenseUsageTrackerKey{UsageType: REFUND, ChannelID: channelId, ServiceID: serviceId}
if incrementUsage.Cmp(big.NewInt(0)) > 0 {
updateLicenseUsageData(newState, usageKey, incrementUsage)
} else {
newState.UpdateUsageType = REFUND
newState.Refund.SetUsage(big.NewInt(0))
}
return BuildOldAndNewLicenseUsageValuesForCAS(newState)

}
)

func updateLicenseUsageData(usageData *LicenseUsageData, key LicenseUsageTrackerKey, usage *big.Int) {
usageData.ChannelID = key.ChannelID
usageData.UpdateUsageType = key.UsageType
var oldUsage *big.Int
switch key.UsageType {
case USED:
{
oldUsage = usageData.Used.GetUsage()
usageData.Used.SetUsage(oldUsage.Add(oldUsage, usage))
}
case PLANNED:
//reset the counter , Planned Amount will be updated ONLY when the License is Purchased or Renewed
{
usageData.Planned.SetUsage(usage)
usageData.Used.SetUsage(big.NewInt(0))
usageData.Refund.SetUsage(big.NewInt(0))
}
case REFUND:
{
oldUsage = usageData.Refund.GetUsage()
usageData.Refund.SetUsage(oldUsage.Add(oldUsage, usage))
}
}
}
42 changes: 42 additions & 0 deletions license_server/license_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package license_server

import (
"github.com/singnet/snet-daemon/blockchain"
"github.com/singnet/snet-daemon/storage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"math/big"
"testing"
)

var testJsonOrgGroupData = "{ \"org_name\": \"organization_name\", \"org_id\": \"org_id1\", \"groups\": [ { \"group_name\": \"default_group2\", \"group_id\": \"99ybRIg2wAx55mqVsA6sB4S7WxPQHNKqa4BPu/bhj+U=\", \"payment\": { \"payment_address\": \"0x671276c61943A35D5F230d076bDFd91B0c47bF09\", \"payment_expiration_threshold\": 40320, \"payment_channel_storage_type\": \"etcd\", \"payment_channel_storage_client\": { \"connection_timeout\": \"15s\", \"request_timeout\": \"13s\", \"endpoints\": [ \"http://127.0.0.1:2379\" ] } } }, { \"group_name\": \"default_group\", \"group_id\": \"99ybRIg2wAx55mqVsA6sB4S7WxPQHNKqa4BPu/bhj+U=\", \"payment\": { \"payment_address\": \"0x671276c61943A35D5F230d076bDFd91B0c47bF09\", \"payment_expiration_threshold\": 40320, \"payment_channel_storage_type\": \"etcd\", \"payment_channel_storage_client\": { \"connection_timeout\": \"15s\", \"request_timeout\": \"13s\", \"endpoints\": [ \"http://127.0.0.1:2379\" ] } } } ] }"

type LicenseServiceTestSuite struct {
suite.Suite
service LicenseService
licenseDetailsStorage storage.TypedAtomicStorage
licenseUsageStorage storage.TypedAtomicStorage
orgMetaData *blockchain.OrganizationMetaData
channelID *big.Int
}

func (suite *LicenseServiceTestSuite) SetupSuite() {
suite.channelID = big.NewInt(1)
suite.orgMetaData, _ = blockchain.InitOrganizationMetaDataFromJson(testJsonOrgGroupData)
suite.licenseDetailsStorage = NewLicenseDetailsStorage(storage.NewMemStorage())
suite.licenseUsageStorage = NewLicenseUsageTrackerStorage(storage.NewMemStorage())
suite.service = NewLicenseService(suite.licenseDetailsStorage, suite.licenseUsageStorage, suite.orgMetaData)
}
func TestTokenServiceTestSuite(t *testing.T) {
suite.Run(t, new(LicenseServiceTestSuite))
}

func (suite *LicenseServiceTestSuite) TestCreateLicense() {
err := suite.service.UpdateLicenseUsage(suite.channelID,
"serviceId1", big.NewInt(100), PLANNED, "Subscription")
assert.Nil(suite.T(), err)
usage, ok, err := suite.service.GetLicenseUsage(LicenseUsageTrackerKey{ChannelID: suite.channelID, ServiceID: "serviceId1", UsageType: PLANNED})
assert.True(suite.T(), ok)
assert.Equal(suite.T(), usage.Usage.GetUsage(), big.NewInt(100))

}
Loading

0 comments on commit 823da18

Please sign in to comment.