Skip to content

Commit

Permalink
move ClusterResourceSets
Browse files Browse the repository at this point in the history
  • Loading branch information
fabriziopandini committed Jun 24, 2020
1 parent 303f492 commit 771e8ee
Show file tree
Hide file tree
Showing 5 changed files with 378 additions and 6 deletions.
4 changes: 2 additions & 2 deletions cmd/clusterctl/client/cluster/mover.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ func getMoveSequence(graph *objectGraph) *moveSequence {
// NB. it is necessary to filter out nodes not belonging to a cluster because e.g. discovery reads all the secrets,
// but only few of them are related to Clusters/Machines etc.
moveGroup := moveGroup{}
for _, n := range graph.getNodesWithClusterTenants() {
for _, n := range graph.getNodesWithTenants() {
// If the node was already included in the moveSequence, skip it.
if moveSequence.hasNode(n) {
continue
Expand Down Expand Up @@ -360,7 +360,7 @@ func patchCluster(proxy Proxy, cluster *node, patch client.Patch) error {
func (o *objectMover) ensureNamespaces(graph *objectGraph, toProxy Proxy) error {
ensureNamespaceBackoff := newWriteBackoff()
namespaces := sets.NewString()
for _, node := range graph.getNodesWithClusterTenants() {
for _, node := range graph.getNodesWithTenants() {
namespace := node.identity.Namespace

// If the namespace was already processed, skip it.
Expand Down
36 changes: 36 additions & 0 deletions cmd/clusterctl/client/cluster/mover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,42 @@ var moveTests = []struct {
},
},
},
{
name: "A ClusterResourceSet applied to a cluster",
fields: moveTestsFields{
objs: func() []runtime.Object {
objs := []runtime.Object{}
objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...)

objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1").
WithSecret("resource-s1").
WithConfigMap("resource-c1").
ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")).
Objs()...)

return objs
}(),
},
wantMoveGroups: [][]string{
{ //group 1
// Cluster
"cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1",
// ClusterResourceSet
"/v1, Kind=Secret, ns1/ClusterResourceSet-crs1",
},
{ //group 2 (objects with ownerReferences in group 1)
// owned by Clusters
"/v1, Kind=Secret, ns1/cluster1-ca",
"/v1, Kind=Secret, ns1/cluster1-kubeconfig",
"infrastructure.cluster.x-k8s.io/v1alpha3, Kind=DummyInfrastructureCluster, ns1/cluster1",
// owned by ClusterResourceSet
"/v1, Kind=Secret, ns1/resource-s1",
"/v1, Kind=ConfigMap, ns1/resource-c1",
// owned by ClusterResourceSet & Cluster
"/v1, Kind=Secret, ns1/ClusterResourceSetBinding-crs1-cluster1",
},
},
},
}

func Test_getMoveSequence(t *testing.T) {
Expand Down
48 changes: 44 additions & 4 deletions cmd/clusterctl/client/cluster/objectgraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package cluster

import (
"strings"

"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
Expand Down Expand Up @@ -58,6 +60,10 @@ type node struct {
// tenantClusters define the list of Clusters which are tenant for the node, no matter if the node has a direct OwnerReference to the Cluster or if
// the node is linked to a Cluster indirectly in the OwnerReference chain.
tenantClusters map[*node]empty

// tenantCRSs define the list of ClusterResourceSet which are tenant for the node, no matter if the node has a direct OwnerReference to the Cluster or if
// the node is linked to a ClusterResourceSet indirectly in the OwnerReference chain.
tenantCRSs map[*node]empty
}

// markObserved marks the fact that a node was observed as a concrete object.
Expand Down Expand Up @@ -130,6 +136,7 @@ func (o *objectGraph) ownerToVirtualNode(owner metav1.OwnerReference, namespace
owners: make(map[*node]ownerReferenceAttributes),
softOwners: make(map[*node]empty),
tenantClusters: make(map[*node]empty),
tenantCRSs: make(map[*node]empty),
virtual: true,
}

Expand Down Expand Up @@ -158,6 +165,7 @@ func (o *objectGraph) objToNode(obj *unstructured.Unstructured) *node {
owners: make(map[*node]ownerReferenceAttributes),
softOwners: make(map[*node]empty),
tenantClusters: make(map[*node]empty),
tenantCRSs: make(map[*node]empty),
virtual: false,
}

Expand Down Expand Up @@ -254,6 +262,9 @@ func (o *objectGraph) Discovery(namespace string, types []metav1.TypeMeta) error
// Completes the graph by setting for each node the list of Clusters the node belong to.
o.setClusterTenants()

// Completes the graph by setting for each node the list of ClusterResourceSet the node belong to.
o.setCRSTenants()

return nil
}

Expand Down Expand Up @@ -306,11 +317,23 @@ func (o *objectGraph) getNodes() []*node {
return nodes
}

// getNodesWithClusterTenants returns the list of nodes existing in the object graph that belong at least to one Cluster.
func (o *objectGraph) getNodesWithClusterTenants() []*node {
// getCRSs returns the list of ClusterResourceSet existing in the object graph.
func (o *objectGraph) getCRSs() []*node {
clusters := []*node{}
for _, node := range o.uidToNode {
// TODO: Use proper type as soon as 3107 merges; for the time being, we are using a secret with a well know name prefix
if node.identity.APIVersion == "v1" && node.identity.Kind == "Secret" && strings.HasPrefix(node.identity.Name, "ClusterResourceSet-") {
clusters = append(clusters, node)
}
}
return clusters
}

// getNodesWithTenants returns the list of nodes existing in the object graph that belong at least to one Cluster or to a ClusterResourceSet.
func (o *objectGraph) getNodesWithTenants() []*node {
nodes := []*node{}
for _, node := range o.uidToNode {
if len(node.tenantClusters) > 0 {
if len(node.tenantClusters) > 0 || len(node.tenantCRSs) > 0 {
nodes = append(nodes, node)
}
}
Expand Down Expand Up @@ -360,7 +383,7 @@ func (o *objectGraph) setClusterTenants() {
}
}

// setNodeTenant sets a tenant for a node and for its own dependents/sofDependents.
// setNodeTenant sets a cluster tenant for a node and for its own dependents/sofDependents.
func (o *objectGraph) setClusterTenant(node, tenant *node) {
node.tenantClusters[tenant] = empty{}
for _, other := range o.getNodes() {
Expand All @@ -369,3 +392,20 @@ func (o *objectGraph) setClusterTenant(node, tenant *node) {
}
}
}

// setClusterTenants sets the cluster tenants for the clusters itself and all their dependent object tree.
func (o *objectGraph) setCRSTenants() {
for _, crs := range o.getCRSs() {
o.setCRSTenant(crs, crs)
}
}

// setCRSTenant sets a ClusterResourceSet tenant for a node and for its own dependents/sofDependents.
func (o *objectGraph) setCRSTenant(node, tenant *node) {
node.tenantCRSs[tenant] = empty{}
for _, other := range o.getNodes() {
if other.isOwnedBy(node) {
o.setCRSTenant(other, tenant)
}
}
}
124 changes: 124 additions & 0 deletions cmd/clusterctl/client/cluster/objectgraph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,60 @@ var objectGraphsTests = []struct {
},
},
},
{
name: "A ClusterResourceSet applied to a cluster",
args: objectGraphTestArgs{
objs: func() []runtime.Object {
objs := []runtime.Object{}
objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...)

objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1").
WithSecret("resource-s1").
WithConfigMap("resource-c1").
ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")).
Objs()...)

return objs
}(),
},
want: wantGraph{
nodes: map[string]wantGraphItem{
"cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1": {},
"infrastructure.cluster.x-k8s.io/v1alpha3, Kind=DummyInfrastructureCluster, ns1/cluster1": {
owners: []string{
"cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1",
},
},
"/v1, Kind=Secret, ns1/cluster1-ca": {
softOwners: []string{
"cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1", //NB. this secret is not linked to the cluster through owner ref
},
},
"/v1, Kind=Secret, ns1/cluster1-kubeconfig": {
owners: []string{
"cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1",
},
},
"/v1, Kind=Secret, ns1/ClusterResourceSet-crs1": {},
"/v1, Kind=Secret, ns1/ClusterResourceSetBinding-crs1-cluster1": {
owners: []string{
"/v1, Kind=Secret, ns1/ClusterResourceSet-crs1",
"cluster.x-k8s.io/v1alpha3, Kind=Cluster, ns1/cluster1",
},
},
"/v1, Kind=Secret, ns1/resource-s1": {
owners: []string{
"/v1, Kind=Secret, ns1/ClusterResourceSet-crs1",
},
},
"/v1, Kind=ConfigMap, ns1/resource-c1": {
owners: []string{
"/v1, Kind=Secret, ns1/ClusterResourceSet-crs1",
},
},
},
},
},
}

func getDetachedObjectGraphWihObjs(objs []runtime.Object) (*objectGraph, error) {
Expand Down Expand Up @@ -1284,3 +1338,73 @@ func Test_objectGraph_setClusterTenants(t *testing.T) {
})
}
}

func Test_objectGraph_setCRSTenants(t *testing.T) {
type fields struct {
objs []runtime.Object
}
tests := []struct {
name string
fields fields
wantCRSs map[string][]string
}{
{
name: "A ClusterResourceSet applied to a cluster",
fields: fields{
objs: func() []runtime.Object {
objs := []runtime.Object{}
objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...)

objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1").
WithSecret("resource-s1").
WithConfigMap("resource-c1").
ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")).
Objs()...)

return objs
}(),
},
wantCRSs: map[string][]string{ // wantCRDs is a map[ClusterResourceSet.UID] --> list of UIDs
"/v1, Kind=Secret, ns1/ClusterResourceSet-crs1": {
"/v1, Kind=Secret, ns1/ClusterResourceSet-crs1", // the ClusterResourceSet should be tenant of itself
"/v1, Kind=Secret, ns1/ClusterResourceSetBinding-crs1-cluster1", // ClusterResourceSetBinding are owned by ClusterResourceSet
"/v1, Kind=Secret, ns1/resource-s1", // resource are owned by ClusterResourceSet
"/v1, Kind=ConfigMap, ns1/resource-c1", // resource are owned by ClusterResourceSet
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

gb, err := getDetachedObjectGraphWihObjs(tt.fields.objs)
g.Expect(err).NotTo(HaveOccurred())

gb.setCRSTenants()

gotCRSs := gb.getCRSs()
sort.Slice(gotCRSs, func(i, j int) bool {
return gotCRSs[i].identity.UID < gotCRSs[j].identity.UID
})

g.Expect(gotCRSs).To(HaveLen(len(tt.wantCRSs)))

for _, crs := range gotCRSs {
wantTenants, ok := tt.wantCRSs[string(crs.identity.UID)]
g.Expect(ok).To(BeTrue())

gotTenants := []string{}
for _, node := range gb.uidToNode {
for c := range node.tenantCRSs {
if c.identity.UID == crs.identity.UID {
gotTenants = append(gotTenants, string(node.identity.UID))
}
}
}

g.Expect(gotTenants).To(ConsistOf(wantTenants))
}
})
}
}
Loading

0 comments on commit 771e8ee

Please sign in to comment.