-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #528 from anandrgitnirman/license
License
- Loading branch information
Showing
6 changed files
with
776 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
|
||
} |
Oops, something went wrong.