VOL-4394 Panic occurs when an ID is requested from an exhausted resource pool

Change-Id: Ie5710a50b550ec0c41bb1609e58f8f90ebd2830d
diff --git a/VERSION b/VERSION
index 4489f5a..2be8aeb 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-7.0.4
+7.0.5
diff --git a/pkg/ponresourcemanager/ponresourcemanager.go b/pkg/ponresourcemanager/ponresourcemanager.go
index ba67aeb..e7e8401 100755
--- a/pkg/ponresourcemanager/ponresourcemanager.go
+++ b/pkg/ponresourcemanager/ponresourcemanager.go
@@ -1257,12 +1257,10 @@
 	*/
 	ByteArray, err := ToByte(Resource[POOL])
 	if err != nil {
-		logger.Error(ctx, "Failed to convert resource to byte array")
 		return 0, err
 	}
 	Data := bitmap.TSFromData(ByteArray, false)
 	if Data == nil {
-		logger.Error(ctx, "Failed to get data from byte array")
 		return 0, errors.New("Failed to get data from byte array")
 	}
 
@@ -1273,6 +1271,9 @@
 			break
 		}
 	}
+	if Idx == Len {
+		return 0, errors.New("resource-exhausted--no-free-id-in-the-pool")
+	}
 	Data.Set(Idx, true)
 	res := uint32(Resource[START_IDX].(float64))
 	Resource[POOL] = Data.Data(false)
@@ -1297,6 +1298,10 @@
 		return false
 	}
 	Idx := Id - uint32(Resource[START_IDX].(float64))
+	if Idx >= uint32(Data.Len()) {
+		logger.Errorf(ctx, "ID %d is out of the boundaries of the pool", Id)
+		return false
+	}
 	Data.Set(int(Idx), false)
 	Resource[POOL] = Data.Data(false)
 
@@ -1314,6 +1319,10 @@
 		return false
 	}
 	Idx := Id - StartIndex
+	if Idx >= uint32(Data.Len()) {
+		logger.Errorf(ctx, "Reservation failed. ID %d is out of the boundaries of the pool", Id)
+		return false
+	}
 	Data.Set(int(Idx), true)
 	return true
 }
diff --git a/pkg/ponresourcemanager/ponresourcemanager_test.go b/pkg/ponresourcemanager/ponresourcemanager_test.go
index a849033..2e4b6e1 100644
--- a/pkg/ponresourcemanager/ponresourcemanager_test.go
+++ b/pkg/ponresourcemanager/ponresourcemanager_test.go
@@ -20,6 +20,7 @@
 	"context"
 	"encoding/json"
 	"errors"
+	"github.com/boljen/go-bitmap"
 	"strings"
 	"testing"
 	"time"
@@ -203,3 +204,155 @@
 	}
 
 }
+
+func TestResourcePoolOverflow(t *testing.T) {
+	ctx := context.Background()
+	PONRMgr, err := NewPONResourceManager(ctx, "gpon", "onu", "olt1",
+		"etcd", "1:1", "service/voltha")
+	if err != nil {
+		return
+	}
+	PONRMgr.KVStore = &db.Backend{
+		Client: newMockKvClient(ctx),
+	}
+
+	PONRMgr.KVStoreForConfig = &db.Backend{
+		Client: newMockKvClient(ctx),
+	}
+	// create a pool in the range of [1,16]
+	StartIndex := uint32(1)
+	EndIndex := uint32(16)
+
+	FormatResult, err := PONRMgr.FormatResource(ctx, 1, StartIndex, EndIndex, []uint32{})
+	if err != nil {
+		t.Error("Failed to format resource", err)
+		return
+	}
+
+	// Add resource as json in kv store.
+	err = PONRMgr.KVStore.Put(ctx, GEMPORT_ID_POOL_PATH, FormatResult)
+	if err != nil {
+		t.Error("Error in posting data to kv store", GEMPORT_ID_POOL_PATH)
+		return
+	}
+
+	for i := 1; i <= 20; i++ {
+		// get gem port id pool from the kv store
+		resource, err := PONRMgr.GetResource(context.Background(), GEMPORT_ID_POOL_PATH)
+		if err != nil {
+			t.Error("Failed to get resource from gem port id pool", err)
+			return
+		}
+		// get a gem port id from the pool
+		nextID, err := PONRMgr.GenerateNextID(ctx, resource)
+		// all free ids in the pool will be consumed by the first 16 steps of the loop
+		// resource-exhausted error is expected from the pool at the 17th step of the loop
+		if i > int(EndIndex) {
+			assert.NotNil(t, err)
+		} else if err != nil {
+			t.Error("Failed to get gem port id from the pool", err)
+			return
+		} else {
+			assert.NotEqual(t, 0, nextID)
+			// put updated gem port id pool into the kv store
+			err = PONRMgr.UpdateResource(context.Background(), GEMPORT_ID_POOL_PATH, resource)
+			if err != nil {
+				t.Error("Failed to put updated gem port id pool into the kv store", err)
+				return
+			}
+		}
+	}
+}
+
+func TestPONResourceManager_ReleaseInvalidID(t *testing.T) {
+	ctx := context.Background()
+	PONRMgr, err := NewPONResourceManager(ctx, "gpon", "onu", "olt1",
+		"etcd", "1:1", "service/voltha")
+	if err != nil {
+		return
+	}
+	PONRMgr.KVStore = &db.Backend{
+		Client: newMockKvClient(ctx),
+	}
+
+	PONRMgr.KVStoreForConfig = &db.Backend{
+		Client: newMockKvClient(ctx),
+	}
+	// create a pool in the range of [1,16]
+	StartIndex := uint32(1)
+	EndIndex := uint32(16)
+
+	FormatResult, err := PONRMgr.FormatResource(ctx, 1, StartIndex, EndIndex, []uint32{})
+	if err != nil {
+		t.Error("Failed to format resource", err)
+		return
+	}
+
+	// Add resource as json in kv store.
+	err = PONRMgr.KVStore.Put(ctx, GEMPORT_ID_POOL_PATH, FormatResult)
+	if err != nil {
+		t.Error("Error in posting data to kv store", GEMPORT_ID_POOL_PATH)
+		return
+	}
+
+	// get gem port id pool from the kv store
+	resource, err := PONRMgr.GetResource(context.Background(), GEMPORT_ID_POOL_PATH)
+	if err != nil {
+		t.Error("Failed to get resource from gem port id pool", err)
+		return
+	}
+	//try to release an ID whose value is out of the boundaries of the pool and expect false
+	released := PONRMgr.ReleaseID(ctx, resource, uint32(EndIndex+1))
+	assert.Equal(t, false, released)
+}
+
+func TestPONResourceManager_ReserveInvalidID(t *testing.T) {
+	ctx := context.Background()
+	PONRMgr, err := NewPONResourceManager(ctx, "gpon", "onu", "olt1",
+		"etcd", "1:1", "service/voltha")
+	if err != nil {
+		return
+	}
+	PONRMgr.KVStore = &db.Backend{
+		Client: newMockKvClient(ctx),
+	}
+
+	PONRMgr.KVStoreForConfig = &db.Backend{
+		Client: newMockKvClient(ctx),
+	}
+	// create a pool in the range of [1,16]
+	StartIndex := uint32(1)
+	EndIndex := uint32(16)
+
+	FormatResult, err := PONRMgr.FormatResource(ctx, 1, StartIndex, EndIndex, []uint32{})
+	if err != nil {
+		t.Error("Failed to format resource", err)
+		return
+	}
+
+	// Add resource as json in kv store.
+	err = PONRMgr.KVStore.Put(ctx, GEMPORT_ID_POOL_PATH, FormatResult)
+	if err != nil {
+		t.Error("Error in posting data to kv store", GEMPORT_ID_POOL_PATH)
+		return
+	}
+	// get gem port id pool from the kv store
+	resource, err := PONRMgr.GetResource(context.Background(), GEMPORT_ID_POOL_PATH)
+	if err != nil {
+		t.Error("Failed to get resource from gem port id pool", err)
+		return
+	}
+	ByteArray, err := ToByte(resource[POOL])
+	if err != nil {
+		t.Error(ctx, "Failed to convert resource to byte array")
+		return
+	}
+	Data := bitmap.TSFromData(ByteArray, false)
+	if Data == nil {
+		t.Error(ctx, "Failed to get resource pool")
+		return
+	}
+	//try to reserve an ID whose value is out of the boundaries of the pool and expect false
+	reserved := PONRMgr.reserveID(ctx, Data, StartIndex, EndIndex+1)
+	assert.Equal(t, false, reserved)
+}