Repository: scionproto/scion
Pull request #3752 (2020-05-07 11:31:00)

Add COLIBRI E2E application level types
Adds the E2E reservation and refactors some tests to be reused.
- [x] rebase (some commits already merged)

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/scionproto/scion/3752)
<!-- Reviewable:end -->



Patch:
diff --git a/go/cs/reservation/index.go b/go/cs/reservation/index.go
index 864e1f3ae..ab8fe6a51 100644
--- a/go/cs/reservation/index.go
+++ b/go/cs/reservation/index.go
@@ -65,3 +65,17 @@ func ValidateIndices(indices IndicesInterface) error {
 	}
 	return nil
 }
+
+// FindIndex returns the slice index for the passed IndexNumber.
+func FindIndex(indices IndicesInterface, idx reservation.IndexNumber) (int, error) {
+	var firstIdx reservation.IndexNumber = 0
+	if indices.Len() > 0 {
+		firstIdx = indices.GetIndexNumber(0)
+	}
+	sliceIndex := int(idx.Sub(firstIdx))
+	if sliceIndex > indices.Len()-1 {
+		return 0, serrors.New("index not found in this reservation", "index_number", idx,
+			"indices length", indices.Len())
+	}
+	return sliceIndex, nil
+}
diff --git a/go/cs/reservation/segment/BUILD.bazel b/go/cs/reservation/segment/BUILD.bazel
index 620ec0b6c..1e6795702 100644
--- a/go/cs/reservation/segment/BUILD.bazel
+++ b/go/cs/reservation/segment/BUILD.bazel
@@ -22,14 +22,14 @@ go_library(
 go_test(
     name = "go_default_test",
     srcs = [
+        "export_test.go",
         "path_test.go",
         "reservation_test.go",
     ],
     embed = [":go_default_library"],
     deps = [
+        "//go/cs/reservation/segmenttest:go_default_library",
         "//go/lib/colibri/reservation:go_default_library",
-        "//go/lib/common:go_default_library",
-        "//go/lib/xtest:go_default_library",
         "@com_github_stretchr_testify//require:go_default_library",
     ],
 )
diff --git a/go/cs/reservation/segment/reservation.go b/go/cs/reservation/segment/reservation.go
index b21c8710c..a5f17e77d 100644
--- a/go/cs/reservation/segment/reservation.go
+++ b/go/cs/reservation/segment/reservation.go
@@ -30,6 +30,12 @@ type Reservation struct {
 	activeIndex int // -1 <= activeIndex < len(Indices)
 }
 
+func NewReservation() *Reservation {
+	return &Reservation{
+		activeIndex: -1,
+	}
+}
+
 // Validate will return an error for invalid values.
 func (r *Reservation) Validate() error {
 	if err := base.ValidateIndices(r.Indices); err != nil {
@@ -85,7 +91,7 @@ func (r *Reservation) NewIndex(expTime time.Time) (reservation.IndexNumber, erro
 // SetIndexConfirmed sets the index as IndexPending (confirmed but not active). If the requested
 // index has state active, it will emit an error.
 func (r *Reservation) SetIndexConfirmed(idx reservation.IndexNumber) error {
-	sliceIndex, err := r.findIndex(idx)
+	sliceIndex, err := base.FindIndex(r.Indices, idx)
 	if err != nil {
 		return err
 	}
@@ -99,7 +105,7 @@ func (r *Reservation) SetIndexConfirmed(idx reservation.IndexNumber) error {
 // SetIndexActive sets the index as active. If the reservation had already an active state,
 // it will remove all previous indices.
 func (r *Reservation) SetIndexActive(idx reservation.IndexNumber) error {
-	sliceIndex, err := r.findIndex(idx)
+	sliceIndex, err := base.FindIndex(r.Indices, idx)
 	if err != nil {
 		return err
 	}
@@ -125,7 +131,7 @@ func (r *Reservation) SetIndexActive(idx reservation.IndexNumber) error {
 
 // RemoveIndex removes all indices from the beginning until this one, inclusive.
 func (r *Reservation) RemoveIndex(idx reservation.IndexNumber) error {
-	sliceIndex, err := r.findIndex(idx)
+	sliceIndex, err := base.FindIndex(r.Indices, idx)
 	if err != nil {
 		return err
 	}
@@ -136,16 +142,3 @@ func (r *Reservation) RemoveIndex(idx reservation.IndexNumber) error {
 	}
 	return nil
 }
-
-func (r *Reservation) findIndex(idx reservation.IndexNumber) (int, error) {
-	var firstIdx reservation.IndexNumber = 0
-	if len(r.Indices) > 0 {
-		firstIdx = r.Indices[0].Idx
-	}
-	sliceIndex := int(idx.Sub(firstIdx))
-	if sliceIndex > len(r.Indices)-1 {
-		return 0, serrors.New("index not found in this reservation", "index_number", idx,
-			"indices length", len(r.Indices))
-	}
-	return sliceIndex, nil
-}
diff --git a/go/lib/colibri/reservation/types.go b/go/lib/colibri/reservation/types.go
index 1e19a774f..cea6dd581 100644
--- a/go/lib/colibri/reservation/types.go
+++ b/go/lib/colibri/reservation/types.go
@@ -78,6 +78,16 @@ type E2EID struct {
 
 const E2EIDLen = 16
 
+// NewSegmentID returns a new SegmentID
+func NewE2EID(AS addr.AS, suffix []byte) (*E2EID, error) {
+	if len(suffix) != 10 {
+		return nil, serrors.New("wrong suffix length, should be 10", "actual_len", len(suffix))
+	}
+	id := E2EID{ASID: AS}
+	copy(id.Suffix[:], suffix)
+	return &id, nil
+}
+
 // E2EIDFromRaw constructs an E2EID parsing a buffer.
 func E2EIDFromRaw(raw []byte) (*E2EID, error) {
 	if len(raw) < E2EIDLen {


Test patch:
diff --git a/go/cs/reservation/e2e/BUILD.bazel b/go/cs/reservation/e2e/BUILD.bazel
index 40e7419f5..c96a3ef8a 100644
--- a/go/cs/reservation/e2e/BUILD.bazel
+++ b/go/cs/reservation/e2e/BUILD.bazel
@@ -1,15 +1,31 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 
 go_library(
     name = "go_default_library",
     srcs = [
+        "index.go",
         "request.go",
         "reservation.go",
     ],
     importpath = "github.com/scionproto/scion/go/cs/reservation/e2e",
     visibility = ["//visibility:public"],
     deps = [
+        "//go/cs/reservation:go_default_library",
         "//go/cs/reservation/segment:go_default_library",
         "//go/lib/colibri/reservation:go_default_library",
+        "//go/lib/serrors:go_default_library",
+    ],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = ["reservation_test.go"],
+    embed = [":go_default_library"],
+    deps = [
+        "//go/cs/reservation/segment:go_default_library",
+        "//go/cs/reservation/segmenttest:go_default_library",
+        "//go/lib/colibri/reservation:go_default_library",
+        "//go/lib/xtest:go_default_library",
+        "@com_github_stretchr_testify//require:go_default_library",
     ],
 )
diff --git a/go/cs/reservation/e2e/index.go b/go/cs/reservation/e2e/index.go
new file mode 100644
index 000000000..6540c8f79
--- /dev/null
+++ b/go/cs/reservation/e2e/index.go
@@ -0,0 +1,39 @@
+// Copyright 2020 ETH Zurich, Anapaya Systems
+//
+// 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.
+
+package e2e
+
+import (
+	"time"
+
+	base "github.com/scionproto/scion/go/cs/reservation"
+	"github.com/scionproto/scion/go/lib/colibri/reservation"
+)
+
+// Index represents an E2E index. These are interpreted as "active", so the reservation initiator
+// must cleanup indices along the path when the setup didn't finish correctly.
+type Index struct {
+	Idx        reservation.IndexNumber
+	Expiration time.Time
+	AllocBW    reservation.BWCls // also present in the token
+	Token      reservation.Token
+}
+
+type Indices []Index
+
+var _ base.IndicesInterface = (*Indices)(nil)
+
+func (idxs Indices) Len() int                                     { return len(idxs) }
+func (idxs Indices) GetIndexNumber(i int) reservation.IndexNumber { return idxs[i].Idx }
+func (idxs Indices) GetExpiration(i int) time.Time                { return idxs[i].Expiration }
diff --git a/go/cs/reservation/e2e/reservation.go b/go/cs/reservation/e2e/reservation.go
index 9f9106dce..c0b8b5ad9 100644
--- a/go/cs/reservation/e2e/reservation.go
+++ b/go/cs/reservation/e2e/reservation.go
@@ -15,11 +15,69 @@
 package e2e
 
 import (
+	"time"
+
+	base "github.com/scionproto/scion/go/cs/reservation"
 	"github.com/scionproto/scion/go/cs/reservation/segment"
 	"github.com/scionproto/scion/go/lib/colibri/reservation"
+	"github.com/scionproto/scion/go/lib/serrors"
 )
 
+// Reservation represents an E2E reservation.
 type Reservation struct {
-	ID                 reservation.E2EID
-	SegmentReservation *segment.Reservation
+	ID                  reservation.E2EID
+	SegmentReservations []*segment.Reservation // stitched segment reservations
+	Indices             Indices
+}
+
+// Validate will return an error for invalid values.
+func (r *Reservation) Validate() error {
+	if err := base.ValidateIndices(r.Indices); err != nil {
+		return err
+	}
+	if len(r.SegmentReservations) < 1 || len(r.SegmentReservations) > 3 {
+		return serrors.New("wrong number of segment reservations referenced in E2E reservation",
+			"number", len(r.SegmentReservations))
+	}
+	// TODO(juagargi) check for valid stitching? path properties and correct end/start AS ID?
+	for i, rsv := range r.SegmentReservations {
+		if rsv == nil {
+			return serrors.New("invalid segment reservation referenced by e2e, is nil",
+				"slice_index", i)
+		}
+		if err := rsv.Validate(); err != nil {
+			return serrors.New("invalid segment reservation referenced by e2e one",
+				"slice_index", i, "segment_id", rsv.ID)
+		}
+	}
+	return nil
+}
+
+// NewIndex creates a new index in this reservation.
+func (r *Reservation) NewIndex(expTime time.Time) (reservation.IndexNumber, error) {
+	idx := reservation.IndexNumber(0)
+	if len(r.Indices) > 0 {
+		idx = r.Indices[len(r.Indices)-1].Idx.Add(1)
+	}
+	newIndices := make(Indices, len(r.Indices)+1)
+	copy(newIndices, r.Indices)
+	newIndices[len(newIndices)-1] = Index{
+		Expiration: expTime,
+		Idx:        idx,
+	}
+	if err := base.ValidateIndices(newIndices); err != nil {
+		return 0, err
+	}
+	r.Indices = newIndices
+	return r.Indices[len(r.Indices)-1].Idx, nil
+}
+
+// RemoveIndex removes all indices from the beginning until this one, inclusive.
+func (r *Reservation) RemoveIndex(idx reservation.IndexNumber) error {
+	sliceIndex, err := base.FindIndex(r.Indices, idx)
+	if err != nil {
+		return err
+	}
+	r.Indices = r.Indices[sliceIndex+1:]
+	return nil
 }
diff --git a/go/cs/reservation/e2e/reservation_test.go b/go/cs/reservation/e2e/reservation_test.go
new file mode 100644
index 000000000..e7628f9e2
--- /dev/null
+++ b/go/cs/reservation/e2e/reservation_test.go
@@ -0,0 +1,113 @@
+// Copyright 2020 ETH Zurich, Anapaya Systems
+//
+// 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.
+
+package e2e
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/scionproto/scion/go/cs/reservation/segment"
+	"github.com/scionproto/scion/go/cs/reservation/segmenttest"
+	"github.com/scionproto/scion/go/lib/colibri/reservation"
+	"github.com/scionproto/scion/go/lib/xtest"
+)
+
+func TestValidate(t *testing.T) {
+	// alright
+	r := newReservation()
+	err := r.Validate()
+	require.NoError(t, err)
+
+	// no segment reservations
+	r = newReservation()
+	r.SegmentReservations = make([]*segment.Reservation, 0)
+	err = r.Validate()
+	require.Error(t, err)
+
+	// nil segment reservation
+	r = newReservation()
+	r.SegmentReservations[0] = nil
+	err = r.Validate()
+	require.Error(t, err)
+
+	// invalid segment reservation
+	r = newReservation()
+	r.SegmentReservations[0].Path = segment.Path{}
+	err = r.Validate()
+	require.Error(t, err)
+
+	// more than 3 segment reservations
+	r = newReservation()
+	r.SegmentReservations = []*segment.Reservation{
+		newSegmentReservation("ff00:0:111", "ff00:0:110"),
+		newSegmentReservation("ff00:0:111", "ff00:0:110"),
+		newSegmentReservation("ff00:0:111", "ff00:0:110"),
+		newSegmentReservation("ff00:0:111", "ff00:0:110"),
+	}
+	err = r.Validate()
+	require.Error(t, err)
+}
+
+func TestNewIndex(t *testing.T) {
+	r := newReservation()
+	expTime := time.Unix(1, 0)
+	index, err := r.NewIndex(expTime)
+	require.NoError(t, err)
+	require.Len(t, r.Indices, 1)
+	require.Equal(t, r.Indices[0].Idx, index)
+}
+
+func TestRemoveIndex(t *testing.T) {
+	r := newReservation()
+	expTime := time.Unix(1, 0)
+	idx, _ := r.NewIndex(expTime)
+	err := r.RemoveIndex(idx)
+	require.NoError(t, err)
+	require.Len(t, r.Indices, 0)
+}
+
+func newSegmentReservation(asidPath ...string) *segment.Reservation {
+	if len(asidPath) < 2 {
+		panic("at least source and destination in the path")
+	}
+	r := segmenttest.NewReservation()
+	// use the asid to create an ID and a path and use them in the reservation
+	pathComponents := make([]interface{}, len(asidPath)*3)
+	for i := range asidPath {
+		pathComponents[i*3] = i * 2
+		pathComponents[i*3+1] = asidPath[i]
+		pathComponents[i*3+2] = i*2 + 1
+	}
+	pathComponents[len(pathComponents)-1] = 0
+	r.Path = segmenttest.NewPathFromComponents(pathComponents...)
+	return r
+}
+
+func newReservation() *Reservation {
+	id, err := reservation.NewE2EID(xtest.MustParseAS("ff00:0:111"),
+		xtest.MustParseHexString("beefcafebeefcafebeef"))
+	if err != nil {
+		panic(err)
+	}
+	rsv := Reservation{
+		ID: *id,
+		SegmentReservations: []*segment.Reservation{
+			newSegmentReservation("ff00:0:111", "ff00:0:110"),
+		},
+	}
+	return &rsv
+}
diff --git a/go/cs/reservation/segment/export_test.go b/go/cs/reservation/segment/export_test.go
new file mode 100644
index 000000000..66589500b
--- /dev/null
+++ b/go/cs/reservation/segment/export_test.go
@@ -0,0 +1,25 @@
+// Copyright 2020 ETH Zurich, Anapaya Systems
+//
+// 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.
+package segment
+
+func (r *Reservation) GetActiveIndexForTesting() int {
+	return r.activeIndex
+}
+func (r *Reservation) SetActiveIndexForTesting(index int) {
+	r.activeIndex = index
+}
+
+func (index *Index) SetStateForTesting(state IndexState) {
+	index.state = state
+}
diff --git a/go/cs/reservation/segment/path_test.go b/go/cs/reservation/segment/path_test.go
index 248362f36..f32c36a69 100644
--- a/go/cs/reservation/segment/path_test.go
+++ b/go/cs/reservation/segment/path_test.go
@@ -12,32 +12,32 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package segment
+package segment_test
 
 import (
 	"testing"
 
 	"github.com/stretchr/testify/require"
 
-	"github.com/scionproto/scion/go/lib/common"
-	"github.com/scionproto/scion/go/lib/xtest"
+	"github.com/scionproto/scion/go/cs/reservation/segment"
+	"github.com/scionproto/scion/go/cs/reservation/segmenttest"
 )
 
 func TestValidatePath(t *testing.T) {
 	tc := map[string]struct {
-		Path    Path
+		Path    segment.Path
 		IsValid bool
 	}{
 		"src-dst": {
-			Path:    newPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 0),
+			Path:    segmenttest.NewPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 0),
 			IsValid: true,
 		},
 		"invalid dst": {
-			Path:    newPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 2),
+			Path:    segmenttest.NewPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 2),
 			IsValid: false,
 		},
 		"invalid src": {
-			Path:    newPathFromComponents(2, "ff00:0:1", 1, 1, "ff00:0:2", 0),
+			Path:    segmenttest.NewPathFromComponents(2, "ff00:0:1", 1, 1, "ff00:0:2", 0),
 			IsValid: false,
 		},
 	}
@@ -57,38 +57,41 @@ func TestValidatePath(t *testing.T) {
 
 func TestEqualPath(t *testing.T) {
 	tc := map[string]struct {
-		Path1   Path
-		Path2   Path
+		Path1   segment.Path
+		Path2   segment.Path
 		IsEqual bool
 	}{
 		"eq1": {
-			Path1:   newPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 0),
-			Path2:   newPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 0),
+			Path1:   segmenttest.NewPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 0),
+			Path2:   segmenttest.NewPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 0),
 			IsEqual: true,
 		},
 		"eq2": {
-			Path1:   newPathFromComponents(0, "ff00:0:1", 1, 2, "ff00:1:10", 3, 1, "ff00:0:2", 0),
-			Path2:   newPathFromComponents(0, "ff00:0:1", 1, 2, "ff00:1:10", 3, 1, "ff00:0:2", 0),
+			Path1: segmenttest.NewPathFromComponents(0, "ff00:0:1", 1, 2, "ff00:1:10", 3,
+				1, "ff00:0:2", 0),
+			Path2: segmenttest.NewPathFromComponents(0, "ff00:0:1", 1, 2, "ff00:1:10", 3,
+				1, "ff00:0:2", 0),
 			IsEqual: true,
 		},
 		"neq1": {
-			Path1:   newPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 0),
-			Path2:   newPathFromComponents(1, "ff00:0:1", 1, 1, "ff00:0:2", 0),
+			Path1:   segmenttest.NewPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 0),
+			Path2:   segmenttest.NewPathFromComponents(1, "ff00:0:1", 1, 1, "ff00:0:2", 0),
 			IsEqual: false,
 		},
 		"neq2": {
-			Path1:   newPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 0),
-			Path2:   newPathFromComponents(0, "ff00:0:3", 1, 1, "ff00:0:2", 0),
+			Path1:   segmenttest.NewPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 0),
+			Path2:   segmenttest.NewPathFromComponents(0, "ff00:0:3", 1, 1, "ff00:0:2", 0),
 			IsEqual: false,
 		},
 		"neq3": {
-			Path1:   newPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 0),
-			Path2:   newPathFromComponents(0, "ff00:0:1", 2, 1, "ff00:0:2", 0),
+			Path1:   segmenttest.NewPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 0),
+			Path2:   segmenttest.NewPathFromComponents(0, "ff00:0:1", 2, 1, "ff00:0:2", 0),
 			IsEqual: false,
 		},
 		"neq4": {
-			Path1:   newPathFromComponents(0, "ff00:0:1", 1, 2, "ff00:1:10", 3, 1, "ff00:0:2", 0),
-			Path2:   newPathFromComponents(0, "ff00:0:1", 1, 2, "ff00:1:10", 3),
+			Path1: segmenttest.NewPathFromComponents(0, "ff00:0:1", 1, 2, "ff00:1:10", 3,
+				1, "ff00:0:2", 0),
+			Path2:   segmenttest.NewPathFromComponents(0, "ff00:0:1", 1, 2, "ff00:1:10", 3),
 			IsEqual: false,
 		},
 	}
@@ -101,18 +104,3 @@ func TestEqualPath(t *testing.T) {
 		})
 	}
 }
-
-func newPathFromComponents(chain ...interface{}) Path {
-	if len(chain)%3 != 0 {
-		panic("wrong number of arguments")
-	}
-	p := Path{}
-	for i := 0; i < len(chain); i += 3 {
-		p = append(p, PathStep{
-			Ingress: common.IFIDType(chain[i].(int)),
-			AS:      xtest.MustParseAS(chain[i+1].(string)),
-			Egress:  common.IFIDType(chain[i+2].(int)),
-		})
-	}
-	return p
-}
diff --git a/go/cs/reservation/segment/reservation_test.go b/go/cs/reservation/segment/reservation_test.go
index 61f9e5754..3dc2334e4 100644
--- a/go/cs/reservation/segment/reservation_test.go
+++ b/go/cs/reservation/segment/reservation_test.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package segment
+package segment_test
 
 import (
 	"testing"
@@ -20,12 +20,14 @@ import (
 
 	"github.com/stretchr/testify/require"
 
+	"github.com/scionproto/scion/go/cs/reservation/segment"
+	"github.com/scionproto/scion/go/cs/reservation/segmenttest"
 	"github.com/scionproto/scion/go/lib/colibri/reservation"
-	"github.com/scionproto/scion/go/lib/xtest"
 )
 
 func TestNewIndex(t *testing.T) {
-	r := newReservation()
+	// TODO(juagargi) move out of segment and to cs/reservation test
+	r := segmenttest.NewReservation()
 	require.Len(t, r.Indices, 0)
 	expTime := time.Unix(1, 0)
 	idx, err := r.NewIndex(expTime)
@@ -78,7 +80,7 @@ func TestNewIndex(t *testing.T) {
 	require.Equal(t, reservation.IndexNumber(3), idx)
 
 	// index number rollover
-	r = newReservation()
+	r = segmenttest.NewReservation()
 	r.NewIndex(expTime)
 	require.Len(t, r.Indices, 1)
 	r.Indices[0].Idx = r.Indices[0].Idx.Sub(1)
@@ -89,7 +91,7 @@ func TestNewIndex(t *testing.T) {
 	require.Equal(t, reservation.IndexNumber(0), idx)
 }
 func TestReservationValidate(t *testing.T) {
-	r := newReservation()
+	r := segmenttest.NewReservation()
 	err := r.Validate()
 	require.NoError(t, err)
 
@@ -107,17 +109,17 @@ func TestReservationValidate(t *testing.T) {
 	require.Error(t, err)
 
 	// wrong path
-	r.Path = Path{}
+	r.Path = segment.Path{}
 	err = r.Validate()
 	require.Error(t, err)
 
-	r = newReservation()
-	r.activeIndex = 0
+	r = segmenttest.NewReservation()
+	r.SetActiveIndexForTesting(0)
 	err = r.Validate()
 	require.Error(t, err)
 
 	// exp time is before
-	r = newReservation()
+	r = segmenttest.NewReservation()
 	expTime := time.Unix(1, 0)
 	r.NewIndex(expTime)
 	r.NewIndex(expTime)
@@ -150,29 +152,29 @@ func TestReservationValidate(t *testing.T) {
 	// more than one active index
 	r.Indices = r.Indices[:1]
 	r.NewIndex(expTime)
-	r.Indices[0].state = IndexActive
-	r.Indices[1].state = IndexActive
+	r.Indices[0].SetStateForTesting(segment.IndexActive)
+	r.Indices[1].SetStateForTesting(segment.IndexActive)
 	err = r.Validate()
 	require.Error(t, err)
 }
 
 func TestSetIndexConfirmed(t *testing.T) {
-	r := newReservation()
+	r := segmenttest.NewReservation()
 	expTime := time.Unix(1, 0)
 	id, _ := r.NewIndex(expTime)
-	require.Equal(t, IndexTemporary, r.Indices[0].state)
+	require.Equal(t, segment.IndexTemporary, r.Indices[0].State())
 	err := r.SetIndexConfirmed(id)
 	require.NoError(t, err)
-	require.Equal(t, IndexPending, r.Indices[0].state)
+	require.Equal(t, segment.IndexPending, r.Indices[0].State())
 
 	// confirm already confirmed
 	err = r.SetIndexConfirmed(id)
 	require.NoError(t, err)
-	require.Equal(t, IndexPending, r.Indices[0].state)
+	require.Equal(t, segment.IndexPending, r.Indices[0].State())
 }
 
 func TestSetIndexActive(t *testing.T) {
-	r := newReservation()
+	r := segmenttest.NewReservation()
 	expTime := time.Unix(1, 0)
 
 	// index not confirmed
@@ -184,8 +186,8 @@ func TestSetIndexActive(t *testing.T) {
 	r.SetIndexConfirmed(idx)
 	err = r.SetIndexActive(idx)
 	require.NoError(t, err)
-	require.Equal(t, IndexActive, r.Indices[0].state)
-	require.Equal(t, 0, r.activeIndex)
+	require.Equal(t, segment.IndexActive, r.Indices[0].State())
+	require.Equal(t, 0, r.GetActiveIndexForTesting())
 
 	// already active
 	err = r.SetIndexActive(idx)
@@ -195,30 +197,34 @@ func TestSetIndexActive(t *testing.T) {
 	r.NewIndex(expTime)
 	idx, _ = r.NewIndex(expTime)
 	require.Len(t, r.Indices, 3)
-	require.Equal(t, 0, r.activeIndex)
+	require.Equal(t, 0, r.GetActiveIndexForTesting())
 	r.SetIndexConfirmed(idx)
 	err = r.SetIndexActive(idx)
 	require.NoError(t, err)
 	require.Len(t, r.Indices, 1)
-	require.Equal(t, 0, r.activeIndex)
+	require.Equal(t, 0, r.GetActiveIndexForTesting())
 	require.True(t, r.Indices[0].Idx == idx)
 }
 
 func TestRemoveIndex(t *testing.T) {
-	r := newReservation()
+	r := segmenttest.NewReservation()
 	expTime := time.Unix(1, 0)
 	idx, _ := r.NewIndex(expTime)
 	err := r.RemoveIndex(idx)
 	require.NoError(t, err)
 	require.Len(t, r.Indices, 0)
 
+	// remove second index
 	idx, _ = r.NewIndex(expTime)
 	idx2, _ := r.NewIndex(expTime)
 	err = r.RemoveIndex(idx)
 	require.NoError(t, err)
 	require.Len(t, r.Indices, 1)
 	require.True(t, r.Indices[0].Idx == idx2)
+	err = r.Validate()
+	require.NoError(t, err)
 
+	// remove also removes older indices
 	expTime = expTime.Add(time.Second)
 	r.NewIndex(expTime)
 	idx, _ = r.NewIndex(expTime)
@@ -228,19 +234,6 @@ func TestRemoveIndex(t *testing.T) {
 	require.NoError(t, err)
 	require.Len(t, r.Indices, 1)
 	require.True(t, r.Indices[0].Idx == idx2)
-}
-
-func newReservation() *Reservation {
-	segID, err := reservation.NewSegmentID(xtest.MustParseAS("ff00:0:1"),
-		xtest.MustParseHexString("beefcafe"))
-	if err != nil {
-		panic(err)
-	}
-	p := newPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 0)
-	r := Reservation{
-		ID:          *segID,
-		Path:        p,
-		activeIndex: -1,
-	}
-	return &r
+	err = r.Validate()
+	require.NoError(t, err)
 }
diff --git a/go/cs/reservation/segmenttest/BUILD.bazel b/go/cs/reservation/segmenttest/BUILD.bazel
new file mode 100644
index 000000000..b69db98ca
--- /dev/null
+++ b/go/cs/reservation/segmenttest/BUILD.bazel
@@ -0,0 +1,14 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["common.go"],
+    importpath = "github.com/scionproto/scion/go/cs/reservation/segmenttest",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//go/cs/reservation/segment:go_default_library",
+        "//go/lib/colibri/reservation:go_default_library",
+        "//go/lib/common:go_default_library",
+        "//go/lib/xtest:go_default_library",
+    ],
+)
diff --git a/go/cs/reservation/segmenttest/common.go b/go/cs/reservation/segmenttest/common.go
new file mode 100644
index 000000000..6ff353432
--- /dev/null
+++ b/go/cs/reservation/segmenttest/common.go
@@ -0,0 +1,50 @@
+// Copyright 2020 ETH Zurich, Anapaya Systems
+//
+// 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.
+
+package segmenttest
+
+import (
+	"github.com/scionproto/scion/go/cs/reservation/segment"
+	"github.com/scionproto/scion/go/lib/colibri/reservation"
+	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/xtest"
+)
+
+func NewPathFromComponents(chain ...interface{}) segment.Path {
+	if len(chain)%3 != 0 {
+		panic("wrong number of arguments")
+	}
+	p := segment.Path{}
+	for i := 0; i < len(chain); i += 3 {
+		p = append(p, segment.PathStep{
+			Ingress: common.IFIDType(chain[i].(int)),
+			AS:      xtest.MustParseAS(chain[i+1].(string)),
+			Egress:  common.IFIDType(chain[i+2].(int)),
+		})
+	}
+	return p
+}
+
+func NewReservation() *segment.Reservation {
+	segID, err := reservation.NewSegmentID(xtest.MustParseAS("ff00:0:1"),
+		xtest.MustParseHexString("beefcafe"))
+	if err != nil {
+		panic(err)
+	}
+	p := NewPathFromComponents(0, "ff00:0:1", 1, 1, "ff00:0:2", 0)
+	r := segment.NewReservation()
+	r.ID = *segID
+	r.Path = p
+	return r
+}