Repository: scionproto/scion
Pull request #3607 (2020-01-14 13:44:15)

Add primary attributes to topology file
Add the primary attributes to toplogy files. The primary attributes are described in:
https://github.com/scionproto/scion/blob/master/doc/ControlPlanePKI.md#primary-ases

This fixes the toplogy generator to handle this.

This is a breaking change, old topology file will have to be updated and replace the old `Core` boolean with the attributes array.
An easy migration strategy is to just put all attributes for previous cores, and no attributes for non-cores.

<!-- 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/3607)
<!-- Reviewable:end -->



Patch:
diff --git a/acceptance/br_child_acceptance/conf/topology.json b/acceptance/br_child_acceptance/conf/topology.json
index 316632b09..f1aea978a 100644
--- a/acceptance/br_child_acceptance/conf/topology.json
+++ b/acceptance/br_child_acceptance/conf/topology.json
@@ -1,7 +1,7 @@
 {
   "ISD_AS": "1-ff00:0:1",
   "Overlay": "UDP/IPv4",
-  "Core": false,
+  "Attributes": [],
   "MTU": 1472,
   "BorderRouters": {
     "brA": {
diff --git a/acceptance/br_core_childIf_acceptance/conf/topology.json b/acceptance/br_core_childIf_acceptance/conf/topology.json
index df6df1cc8..e8a6e5a4f 100644
--- a/acceptance/br_core_childIf_acceptance/conf/topology.json
+++ b/acceptance/br_core_childIf_acceptance/conf/topology.json
@@ -1,7 +1,7 @@
 {
   "ISD_AS": "1-ff00:0:1",
   "Overlay": "UDP/IPv4",
-  "Core": true,
+  "Attributes": ["authoritative", "core", "issuing", "voting"],
   "MTU": 1472,
   "BorderRouters": {
     "core-brA": {
diff --git a/acceptance/br_core_coreIf_acceptance/conf/topology.json b/acceptance/br_core_coreIf_acceptance/conf/topology.json
index 71f8e1509..4d52d71cd 100644
--- a/acceptance/br_core_coreIf_acceptance/conf/topology.json
+++ b/acceptance/br_core_coreIf_acceptance/conf/topology.json
@@ -1,7 +1,7 @@
 {
   "ISD_AS": "1-ff00:0:1",
   "Overlay": "UDP/IPv4",
-  "Core": true,
+  "Attributes": ["authoritative", "core", "issuing", "voting"],
   "MTU": 1472,
   "BorderRouters": {
     "core-brA": {
diff --git a/acceptance/br_core_multi_acceptance/conf/topology.json b/acceptance/br_core_multi_acceptance/conf/topology.json
index 043189e34..4de6ab6b9 100644
--- a/acceptance/br_core_multi_acceptance/conf/topology.json
+++ b/acceptance/br_core_multi_acceptance/conf/topology.json
@@ -1,7 +1,7 @@
 {
   "ISD_AS": "1-ff00:0:1",
   "Overlay": "UDP/IPv4",
-  "Core": true,
+  "Attributes": ["authoritative", "core", "issuing", "voting"],
   "MTU": 1472,
   "BorderRouters": {
     "core-brA": {
diff --git a/acceptance/br_multi_acceptance/conf/topology.json b/acceptance/br_multi_acceptance/conf/topology.json
index c30daf463..a50a97d08 100644
--- a/acceptance/br_multi_acceptance/conf/topology.json
+++ b/acceptance/br_multi_acceptance/conf/topology.json
@@ -1,7 +1,7 @@
 {
   "ISD_AS": "1-ff00:0:1",
   "Overlay": "UDP/IPv4",
-  "Core": false,
+  "Attributes": [],
   "MTU": 1472,
   "BorderRouters": {
     "brA": {
diff --git a/acceptance/br_parent_acceptance/conf/topology.json b/acceptance/br_parent_acceptance/conf/topology.json
index 8d419c09a..f01367080 100644
--- a/acceptance/br_parent_acceptance/conf/topology.json
+++ b/acceptance/br_parent_acceptance/conf/topology.json
@@ -1,7 +1,7 @@
 {
   "ISD_AS": "1-ff00:0:1",
   "Overlay": "UDP/IPv4",
-  "Core": false,
+  "Attributes": [],
   "MTU": 1472,
   "BorderRouters": {
     "brA": {
diff --git a/acceptance/br_peer_acceptance/conf/topology.json b/acceptance/br_peer_acceptance/conf/topology.json
index 0866839ee..8f244bbb7 100644
--- a/acceptance/br_peer_acceptance/conf/topology.json
+++ b/acceptance/br_peer_acceptance/conf/topology.json
@@ -1,7 +1,7 @@
 {
   "ISD_AS": "1-ff00:0:1",
   "Overlay": "UDP/IPv4",
-  "Core": false,
+  "Attributes": [],
   "MTU": 1472,
   "BorderRouters": {
     "brA": {
diff --git a/go/lib/infra/modules/itopo/BUILD.bazel b/go/lib/infra/modules/itopo/BUILD.bazel
index 5af1ca21a..f054c329d 100644
--- a/go/lib/infra/modules/itopo/BUILD.bazel
+++ b/go/lib/infra/modules/itopo/BUILD.bazel
@@ -32,6 +32,7 @@ go_test(
     data = glob(["testdata/**"]),
     embed = [":go_default_library"],
     deps = [
+        "//go/lib/scrypto/trc:go_default_library",
         "//go/lib/topology:go_default_library",
         "//go/lib/xtest:go_default_library",
         "//go/lib/xtest/mock_xtest:go_default_library",
diff --git a/go/lib/infra/modules/itopo/validate.go b/go/lib/infra/modules/itopo/validate.go
index 80813e908..f70493cc7 100644
--- a/go/lib/infra/modules/itopo/validate.go
+++ b/go/lib/infra/modules/itopo/validate.go
@@ -105,9 +105,9 @@ func (v *generalValidator) Immutable(topo, oldTopo *topology.RWTopology) error {
 		return common.NewBasicError("IA is immutable", nil,
 			"expected", oldTopo.IA, "actual", topo.IA)
 	}
-	if topo.Core != oldTopo.Core {
-		return common.NewBasicError("Core is immutable", nil,
-			"expected", oldTopo.Core, "actual", topo.Core)
+	if !topo.Attributes.Equal(oldTopo.Attributes) {
+		return common.NewBasicError("Attributes are immutable", nil,
+			"expected", oldTopo.Attributes, "actual", topo.Attributes)
 	}
 	if topo.MTU != oldTopo.MTU {
 		return common.NewBasicError("MTU is immutable", nil,
diff --git a/go/lib/scrypto/trc/primary.go b/go/lib/scrypto/trc/primary.go
index 2af3c847c..387a6be74 100644
--- a/go/lib/scrypto/trc/primary.go
+++ b/go/lib/scrypto/trc/primary.go
@@ -174,6 +174,18 @@ func (t Attributes) Contains(attr Attribute) bool {
 	return false
 }
 
+func (t Attributes) Equal(other Attributes) bool {
+	if len(t) != len(other) {
+		return false
+	}
+	for i := range t {
+		if t[i] != other[i] {
+			return false
+		}
+	}
+	return true
+}
+
 // Validate checks that the attributes list is valid.
 func (t *Attributes) Validate() error {
 	if len(*t) > 4 || len(*t) <= 0 {
diff --git a/go/lib/topology/BUILD.bazel b/go/lib/topology/BUILD.bazel
index 0254d250b..004651e12 100644
--- a/go/lib/topology/BUILD.bazel
+++ b/go/lib/topology/BUILD.bazel
@@ -16,6 +16,7 @@ go_library(
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/scmp:go_default_library",
+        "//go/lib/scrypto/trc:go_default_library",
         "//go/lib/serrors:go_default_library",
         "//go/lib/snet:go_default_library",
         "//go/lib/topology/json:go_default_library",
diff --git a/go/lib/topology/interface.go b/go/lib/topology/interface.go
index 19339e57c..0144fa317 100644
--- a/go/lib/topology/interface.go
+++ b/go/lib/topology/interface.go
@@ -21,6 +21,7 @@ import (
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/scmp"
+	"github.com/scionproto/scion/go/lib/scrypto/trc"
 	"github.com/scionproto/scion/go/lib/serrors"
 	"github.com/scionproto/scion/go/lib/snet"
 	"github.com/scionproto/scion/go/proto"
@@ -208,7 +209,7 @@ func (t *topologyS) MakeHostInfos(st proto.ServiceType) []net.UDPAddr {
 }
 
 func (t *topologyS) Core() bool {
-	return t.Topology.Core
+	return t.Topology.Attributes.Contains(trc.Core)
 }
 
 func (t *topologyS) BR(name string) (BRInfo, bool) {
diff --git a/go/lib/topology/json/BUILD.bazel b/go/lib/topology/json/BUILD.bazel
index 0cbfb2543..cbd6d419c 100644
--- a/go/lib/topology/json/BUILD.bazel
+++ b/go/lib/topology/json/BUILD.bazel
@@ -7,6 +7,7 @@ go_library(
     visibility = ["//visibility:public"],
     deps = [
         "//go/lib/common:go_default_library",
+        "//go/lib/scrypto/trc:go_default_library",
         "//go/lib/serrors:go_default_library",
     ],
 )
@@ -18,6 +19,7 @@ go_test(
     embed = [":go_default_library"],
     deps = [
         "//go/lib/common:go_default_library",
+        "//go/lib/scrypto/trc:go_default_library",
         "//go/lib/topology/overlay:go_default_library",
         "@com_github_stretchr_testify//assert:go_default_library",
         "@com_github_stretchr_testify//require:go_default_library",
diff --git a/go/lib/topology/json/json.go b/go/lib/topology/json/json.go
index d4d0c67d5..583f407cb 100644
--- a/go/lib/topology/json/json.go
+++ b/go/lib/topology/json/json.go
@@ -1,4 +1,5 @@
 // Copyright 2019 ETH Zurich
+// 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.
@@ -23,18 +24,24 @@ import (
 	"strings"
 
 	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/scrypto/trc"
 	"github.com/scionproto/scion/go/lib/serrors"
 )
 
 // Topology is the JSON type for the entire AS topology file.
 type Topology struct {
-	Timestamp          int64                  `json:"Timestamp"`
-	TimestampHuman     string                 `json:"TimestampHuman"`
-	TTL                uint32                 `json:"TTL"`
-	IA                 string                 `json:"ISD_AS"`
-	Overlay            string                 `json:"Overlay"`
-	MTU                int                    `json:"MTU"`
-	Core               bool                   `json:"Core"`
+	Timestamp      int64  `json:"Timestamp"`
+	TimestampHuman string `json:"TimestampHuman"`
+	TTL            uint32 `json:"TTL"`
+	IA             string `json:"ISD_AS"`
+	Overlay        string `json:"Overlay"`
+	MTU            int    `json:"MTU"`
+	// Attributes are the primary AS attributes as described in
+	// https://github.com/scionproto/scion/blob/master/doc/ControlPlanePKI.md#primary-ases
+	// We use the []trc.Attribute type so that we don't validate according to
+	// trc.Attributes, because that contains a length 0 check which is not
+	// suitable for topology.
+	Attributes         []trc.Attribute        `json:"Attributes"`
 	BorderRouters      map[string]*BRInfo     `json:"BorderRouters,omitempty"`
 	ZookeeperService   map[int]*Address       `json:"ZookeeperService,omitempty"`
 	BeaconService      map[string]*ServerInfo `json:"BeaconService,omitempty"`
diff --git a/go/lib/topology/topology.go b/go/lib/topology/topology.go
index fa14ad4bd..b48125e0b 100644
--- a/go/lib/topology/topology.go
+++ b/go/lib/topology/topology.go
@@ -26,6 +26,7 @@ import (
 
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/scrypto/trc"
 	"github.com/scionproto/scion/go/lib/serrors"
 	jsontopo "github.com/scionproto/scion/go/lib/topology/json"
 	"github.com/scionproto/scion/go/lib/topology/overlay"
@@ -56,11 +57,11 @@ type (
 	// there is again a sorted slice of names of the servers that provide the service.
 	// Additionally, there is a map from those names to TopoAddr structs.
 	RWTopology struct {
-		Timestamp time.Time
-		TTL       time.Duration
-		IA        addr.IA
-		Core      bool
-		MTU       int
+		Timestamp  time.Time
+		TTL        time.Duration
+		IA         addr.IA
+		Attributes trc.Attributes
+		MTU        int
 
 		BR        map[string]BRInfo
 		BRNames   []string
@@ -190,7 +191,7 @@ func (t *RWTopology) populateMeta(raw *jsontopo.Topology) error {
 		return err
 	}
 	t.MTU = raw.MTU
-	t.Core = raw.Core
+	t.Attributes = raw.Attributes
 	return nil
 }
 
@@ -235,7 +236,7 @@ func (t *RWTopology) populateBR(raw *jsontopo.Topology) error {
 				return err
 			}
 			ifinfo.LinkType = LinkTypeFromString(rawIntf.LinkTo)
-			if err = ifinfo.CheckLinks(t.Core, name); err != nil {
+			if err = ifinfo.CheckLinks(t.Attributes.Contains(trc.Core), name); err != nil {
 				return err
 			}
 			// These fields are only necessary for the border router.
diff --git a/python/lib/topology.py b/python/lib/topology.py
index f2fb0c87c..bd563a540 100644
--- a/python/lib/topology.py
+++ b/python/lib/topology.py
@@ -194,7 +194,6 @@ class Topology(object):
     The Topology class parses the topology file of an AS and stores such
     information for further use.
 
-    :ivar bool is_core_as: tells whether an AS is a core AS or not.
     :ivar ISD_AS isd_is: the ISD-AS identifier.
     :ivar list beacon_servers: beacons servers in the AS.
     :ivar list certificate_servers: certificate servers in the AS.
@@ -208,7 +207,6 @@ class Topology(object):
     :ivar list core_interfaces: BR interfaces linking to core ASes.
     """
     def __init__(self):  # pragma: no cover
-        self.is_core_as = False
         self.isd_as = None
         self.mtu = None
         self.beacon_servers = []
@@ -250,7 +248,6 @@ class Topology(object):
 
         :param dict topology: dictionary representation of a topology
         """
-        self.is_core_as = topology['Core']
         self.isd_as = ISD_AS(topology['ISD_AS'])
         self.mtu = topology['MTU']
         self.overlay = topology['Overlay']
diff --git a/python/topology/topo.py b/python/topology/topo.py
index 84c533255..0f9275fe8 100755
--- a/python/topology/topo.py
+++ b/python/topology/topo.py
@@ -183,11 +183,12 @@ class TopoGenerator(object):
     def _generate_as_topo(self, topo_id, as_conf):
         mtu = as_conf.get('mtu', self.args.default_mtu)
         assert mtu >= SCION_MIN_MTU, mtu
+        attributes = []
+        for attr in ['authoritative', 'core', 'issuing', 'voting']:
+            if as_conf.get(attr, False):
+                attributes.append(attr)
         self.topo_dicts[topo_id] = {
-            'Core': as_conf.get('core', False),
-            'Voting': as_conf.get('voting', False),
-            'Authoritative': as_conf.get('authoritative', False),
-            'Issuing': as_conf.get('issuing', False),
+            'Attributes': attributes,
             'ISD_AS': str(topo_id),
             'MTU': mtu,
             'Overlay': as_conf.get('underlay', DEFAULT_UNDERLAY),


Test patch:
diff --git a/acceptance/topo_invalid_reloads_acceptance/test b/acceptance/topo_invalid_reloads_acceptance/test
index b08d91ebc..ef63aa088 100755
--- a/acceptance/topo_invalid_reloads_acceptance/test
+++ b/acceptance/topo_invalid_reloads_acceptance/test
@@ -41,8 +41,8 @@ test_ps_immutable() {
 test_immutable() {
     jq '.ISD_AS = "1-ff00:0:111"' $TOPO | sponge $2
     check_no_reload "$1" 1 "$3: ISD_AS"
-    jq '.Core = true | del( .BorderRouters[].Interfaces )' $TOPO | sponge $2
-    check_no_reload "$1" 2 "$3: Core"
+    jq '.Attributes = ["core"] | del( .BorderRouters[].Interfaces )' $TOPO | sponge $2
+    check_no_reload "$1" 2 "$3: Attributes"
     jq '.MTU = 42' $TOPO | sponge $2
     check_no_reload "$1" 3 "$3: MTU"
 }
diff --git a/go/cs/beaconing/testdata/topology-core.json b/go/cs/beaconing/testdata/topology-core.json
index 033dccaec..392c0c8a2 100644
--- a/go/cs/beaconing/testdata/topology-core.json
+++ b/go/cs/beaconing/testdata/topology-core.json
@@ -2,7 +2,7 @@
   "Overlay": "UDP/IPv4",
   "MTU": 1472,
   "ISD_AS": "1-ff00:0:110",
-  "Core": true,
+  "Attributes": ["authoritative", "core", "issuing", "voting"],
   "BorderRouters": {
     "br1-ff00_0_111-1": {
       "InternalAddrs": {
diff --git a/go/cs/beaconing/testdata/topology.json b/go/cs/beaconing/testdata/topology.json
index 55bbdef13..8167ddd39 100644
--- a/go/cs/beaconing/testdata/topology.json
+++ b/go/cs/beaconing/testdata/topology.json
@@ -2,7 +2,7 @@
   "Overlay": "UDP/IPv4",
   "MTU": 1472,
   "ISD_AS": "1-ff00:0:111",
-  "Core": false,
+  "Attributes": [],
   "BorderRouters": {
     "br1-ff00_0_111-1": {
       "CtrlAddr": {
diff --git a/go/cs/handlers/testdata/topology_as1_132.json b/go/cs/handlers/testdata/topology_as1_132.json
index 46747b121..43975314f 100644
--- a/go/cs/handlers/testdata/topology_as1_132.json
+++ b/go/cs/handlers/testdata/topology_as1_132.json
@@ -43,7 +43,7 @@
       }
     }
   },
-  "Core": false,
+  "Attributes": [],
   "MTU": 1472,
   "PathService": {
     "ps1-ff00_0_132-1": {"Addrs": {
diff --git a/go/cs/handlers/testdata/topology_as2_211.json b/go/cs/handlers/testdata/topology_as2_211.json
index 4cc6ecf55..2736b325c 100644
--- a/go/cs/handlers/testdata/topology_as2_211.json
+++ b/go/cs/handlers/testdata/topology_as2_211.json
@@ -88,7 +88,7 @@
       }
     }
   },
-  "Core": false,
+  "Attributes": [],
   "MTU": 1472,
   "PathService": {
     "ps2-ff00_0_211-1": {"Addrs": {
diff --git a/go/cs/handlers/testdata/topology_as2_222.json b/go/cs/handlers/testdata/topology_as2_222.json
index 5c8780105..ad66168fa 100644
--- a/go/cs/handlers/testdata/topology_as2_222.json
+++ b/go/cs/handlers/testdata/topology_as2_222.json
@@ -43,7 +43,7 @@
       }
     }
   },
-  "Core": false,
+  "Attributes": [],
   "MTU": 1472,
   "PathService": {
     "ps2-ff00_0_222-1": {"Addrs": {
diff --git a/go/cs/ifstate/testdata/topology.json b/go/cs/ifstate/testdata/topology.json
index c00474d27..f1e50fbe9 100644
--- a/go/cs/ifstate/testdata/topology.json
+++ b/go/cs/ifstate/testdata/topology.json
@@ -2,7 +2,7 @@
   "Overlay": "UDP/IPv4",
   "MTU": 1472,
   "ISD_AS": "1-ff00:0:111",
-  "Core": false,
+  "Attributes": [],
   "BorderRouters": {
     "br1-ff00_0_111-2": {
       "CtrlAddr": {
diff --git a/go/cs/keepalive/testdata/topology.json b/go/cs/keepalive/testdata/topology.json
index 7baaaf016..ffec78ea3 100644
--- a/go/cs/keepalive/testdata/topology.json
+++ b/go/cs/keepalive/testdata/topology.json
@@ -2,7 +2,7 @@
   "Overlay": "UDP/IPv4",
   "MTU": 1472,
   "ISD_AS": "1-ff00:0:111",
-  "Core": false,
+  "Attributes": [],
   "BorderRouters": {
     "br1-ff00_0_111-1": {
       "InternalAddrs": {
diff --git a/go/lib/infra/modules/itopo/testdata/topo.json b/go/lib/infra/modules/itopo/testdata/topo.json
index b7a1a7249..263271de5 100644
--- a/go/lib/infra/modules/itopo/testdata/topo.json
+++ b/go/lib/infra/modules/itopo/testdata/topo.json
@@ -5,7 +5,7 @@
     "ISD_AS": "1-ff00:0:311",
     "MTU": 1472,
     "Overlay": "UDP/IPv4+6",
-    "Core": false,
+    "Attributes": [],
     "BorderRouters": {
         "br1-ff00:0:311-1": {
             "InternalAddrs": {
diff --git a/go/lib/infra/modules/itopo/validate_test.go b/go/lib/infra/modules/itopo/validate_test.go
index a1257231e..c9fc744f7 100644
--- a/go/lib/infra/modules/itopo/validate_test.go
+++ b/go/lib/infra/modules/itopo/validate_test.go
@@ -21,6 +21,7 @@ import (
 
 	. "github.com/smartystreets/goconvey/convey"
 
+	"github.com/scionproto/scion/go/lib/scrypto/trc"
 	"github.com/scionproto/scion/go/lib/topology"
 	"github.com/scionproto/scion/go/proto"
 )
@@ -243,8 +244,8 @@ func testGenImmutable(v internalValidator, topo, oldTopo *topology.RWTopology, t
 		topo.IA.I = 0
 		SoMsg("err", v.Immutable(topo, oldTopo), ShouldNotBeNil)
 	})
-	Convey("Updating the core flag is not allowed", func() {
-		topo.Core = true
+	Convey("Updating the attributes is not allowed", func() {
+		topo.Attributes = trc.Attributes{trc.Core}
 		SoMsg("err", v.Immutable(topo, oldTopo), ShouldNotBeNil)
 	})
 	Convey("Updating the mtu is not allowed", func() {
diff --git a/go/lib/topology/json/json_test.go b/go/lib/topology/json/json_test.go
index 3e94d7295..b88965abe 100644
--- a/go/lib/topology/json/json_test.go
+++ b/go/lib/topology/json/json_test.go
@@ -1,4 +1,5 @@
 // Copyright 2019 ETH Zurich
+// 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.
@@ -25,6 +26,7 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/scrypto/trc"
 	jsontopo "github.com/scionproto/scion/go/lib/topology/json"
 	"github.com/scionproto/scion/go/lib/topology/overlay"
 )
@@ -39,7 +41,7 @@ func TestLoadRawFromFile(t *testing.T) {
 		TimestampHuman: "May  6 00:00:00 CET 1975",
 		IA:             "6-ff00:0:362",
 		MTU:            1472,
-		Core:           true,
+		Attributes:     []trc.Attribute{trc.Authoritative, trc.Core, trc.Issuing, trc.Voting},
 		Overlay:        overlay.UDPIPv46Name,
 		BorderRouters: map[string]*jsontopo.BRInfo{
 			"borderrouter6-f00:0:362-1": {
diff --git a/go/lib/topology/json/testdata/topology.json b/go/lib/topology/json/testdata/topology.json
index 9f3a44273..e634db372 100644
--- a/go/lib/topology/json/testdata/topology.json
+++ b/go/lib/topology/json/testdata/topology.json
@@ -5,7 +5,12 @@
     "ISD_AS": "6-ff00:0:362",
     "Overlay": "UDP/IPv4+6",
     "MTU": 1472,
-    "Core": true,
+    "Attributes": [
+        "authoritative",
+        "core",
+        "issuing",
+        "voting"
+    ],
     "BorderRouters": {
         "borderrouter6-f00:0:362-1": {
             "InternalAddrs": {
diff --git a/go/lib/topology/testdata/basic.json b/go/lib/topology/testdata/basic.json
index d063c18c3..d952553b7 100644
--- a/go/lib/topology/testdata/basic.json
+++ b/go/lib/topology/testdata/basic.json
@@ -5,7 +5,7 @@
     "ISD_AS": "1-ff00:0:311",
     "MTU": 1472,
     "Overlay": "UDP/IPv4+6",
-    "Core": false,
+    "Attributes": [],
     "BorderRouters": {
         "br1-ff00:0:311-1": {
             "InternalAddrs": {
diff --git a/go/lib/topology/testdata/core.json b/go/lib/topology/testdata/core.json
index 0812870b8..f45dcb452 100644
--- a/go/lib/topology/testdata/core.json
+++ b/go/lib/topology/testdata/core.json
@@ -3,7 +3,7 @@
     "TimestampHuman": "May  6 00:00:00 CET 1975",
     "ISD_AS": "6-ff00:0:362",
     "MTU": 1472,
-    "Core": true,
+    "Attributes": ["authoritative", "core", "issuing", "voting"],
     "Overlay": "UDP/IPv4+6",
     "BorderRouters": {
         "borderrouter6-ff00:0:362-1": {
diff --git a/go/lib/topology/topology_test.go b/go/lib/topology/topology_test.go
index 0e073a508..337494993 100644
--- a/go/lib/topology/topology_test.go
+++ b/go/lib/topology/topology_test.go
@@ -38,7 +38,7 @@ func TestMeta(t *testing.T) {
 	assert.Equal(t, time.Hour, c.TTL, "Field 'TTL'")
 	assert.Equal(t, addr.IA{I: 1, A: 0xff0000000311}, c.IA, "Field 'ISD_AS'")
 	assert.Equal(t, 1472, c.MTU, "Field 'MTU'")
-	assert.False(t, c.Core, "Field 'Core'")
+	assert.Empty(t, c.Attributes, "Field 'Attributes'")
 }
 
 func Test_Active(t *testing.T) {
diff --git a/python/test/lib/topology_test.py b/python/test/lib/topology_test.py
index 8b8aadd56..6cc9b83b8 100644
--- a/python/test/lib/topology_test.py
+++ b/python/test/lib/topology_test.py
@@ -146,7 +146,6 @@ class TestTopologyParseDict(object):
         # Call
         inst.parse_dict(topo_dict)
         # Tests
-        ntools.eq_(inst.is_core_as, True)
         ntools.eq_(inst.isd_as, isd_as.return_value)
         ntools.eq_(inst.mtu, 440)
         inst._parse_srv_dicts.assert_called_once_with(topo_dict)