Skip to content

Commit

Permalink
go/common/node: Add custom text (un)marshaler for RolesMask type
Browse files Browse the repository at this point in the history
This will result in easy to understand "roles" fields in various CLI
commands that output JSON.
  • Loading branch information
tjanez committed Sep 2, 2021
1 parent a1a8f17 commit 5024c58
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 1 deletion.
44 changes: 43 additions & 1 deletion go/common/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import (
)

var (
// ErrInvalidRole is the error returned when a node role is invalid.
ErrInvalidRole = errors.New("node: invalid role")

// ErrInvalidTEEHardware is the error returned when a TEE hardware
// implementation is invalid.
ErrInvalidTEEHardware = errors.New("node: invalid TEE implementation")
Expand Down Expand Up @@ -109,6 +112,15 @@ const (
// RoleReserved are all the bits of the Oasis node roles bitmask
// that are reserved and must not be used.
RoleReserved RolesMask = ((1 << 32) - 1) & ^((RoleStorageRPC << 1) - 1)

RoleComputeWorkerName = "compute"
RoleStorageWorkerName = "storage"
RoleKeyManagerName = "key-manager"
RoleValidatorName = "validator"
RoleConsensusRPCName = "consensus-rpc"
RoleStorageRPCName = "storage-rpc"

RolesMaskStringSep = ","
)

// Roles returns a list of available valid roles.
Expand Down Expand Up @@ -154,7 +166,37 @@ func (m RolesMask) String() string {
ret = append(ret, "storage-rpc")
}

return strings.Join(ret, ",")
return strings.Join(ret, RolesMaskStringSep)
}

// MarshalText encodes a RolesMask into text form.
func (m RolesMask) MarshalText() ([]byte, error) {
return []byte(m.String()), nil
}

// UnmarshalText decodes a text slice into a RolesMask.
func (k *RolesMask) UnmarshalText(text []byte) error {
*k = 0
roles := strings.Split(string(text), RolesMaskStringSep)
for _, role := range roles {
switch role {
case RoleComputeWorkerName:
*k |= RoleComputeWorker
case RoleStorageWorkerName:
*k |= RoleStorageWorker
case RoleKeyManagerName:
*k |= RoleKeyManager
case RoleValidatorName:
*k |= RoleValidator
case RoleConsensusRPCName:
*k |= RoleConsensusRPC
case RoleStorageRPCName:
*k |= RoleStorageRPC
default:
return fmt.Errorf("%w: '%s'", ErrInvalidRole, role)
}
}
return nil
}

// ValidateBasic performs basic descriptor validity checks.
Expand Down
56 changes: 56 additions & 0 deletions go/common/node/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,62 @@ import (
"github.com/oasisprotocol/oasis-core/go/common/cbor"
)

func TestRolesMask(t *testing.T) {
require := require.New(t)

testVectors := []struct {
rolesMaskString string
rolesMaskStringUnmarshable bool
rolesMask RolesMask
rolesMaskStringCanonical bool
errMsg string
}{
// Valid single roles.
{"compute", true, 1, true, ""},
{"storage", true, 2, true, ""},
{"key-manager", true, 4, true, ""},
{"validator", true, 8, true, ""},
{"consensus-rpc", true, 16, true, ""},
{"storage-rpc", true, 32, true, ""},
// Valid multiple roles.
{"compute,storage", true, 3, true, ""},
{"compute,storage,validator", true, 11, true, ""},
{"compute,storage,validator,consensus-rpc", true, 27, true, ""},
{"validator,consensus-rpc", true, 24, true, ""},
{"storage,storage-rpc", true, 34, true, ""},

// Invalid.
{"compute ", false, 1, false, "node: invalid role: 'compute '"},
{"compute ,", false, 1, false, "node: invalid role: 'compute '"},
{" validator", false, 1, false, "node: invalid role: ' validator'"},
{"compute, storage", false, 1, false, "node: invalid role: ' storage'"},
{"master", false, 1, false, "node: invalid role: 'master'"},
{"storage-rpc,storage", true, 34, false, ""},
}

for _, v := range testVectors {
var unmarshaledRolesMask RolesMask
err := unmarshaledRolesMask.UnmarshalText([]byte(v.rolesMaskString))
if !v.rolesMaskStringUnmarshable {
require.EqualErrorf(
err,
v.errMsg,
"Unmarshaling invalid roles mask: '%s' should fail with expected error message",
v.rolesMaskString,
)
continue
}
require.NoErrorf(err, "Failed to unmarshal a valid roles mask: '%s'", v.rolesMaskString)
require.Equal(v.rolesMask, unmarshaledRolesMask, "Unmarshaled roles mask doesn't equal expected roles mask")

textRolesMask, err := v.rolesMask.MarshalText()
require.NoError(err, "Failed to marshal a valid roles mask: '%s'", v.rolesMask)
if v.rolesMaskStringCanonical {
require.Equal(v.rolesMaskString, string(textRolesMask), "Marshaled roles mask doesn't equal expected text roles mask")
}
}
}

func TestNodeDescriptor(t *testing.T) {
require := require.New(t)

Expand Down

0 comments on commit 5024c58

Please sign in to comment.