Skip to content

Commit

Permalink
Add networking routetable and routes
Browse files Browse the repository at this point in the history
This fixes Azure#2284.
  • Loading branch information
matthchr committed May 20, 2022
1 parent a704299 commit 0ebfdbc
Show file tree
Hide file tree
Showing 48 changed files with 7,716 additions and 250 deletions.
240 changes: 240 additions & 0 deletions v2/api/microsoft.network/versions_matrix.md

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions v2/api/network/customizations/route_table_extension_types_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

169 changes: 169 additions & 0 deletions v2/api/network/customizations/route_table_extensions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package customizations

import (
"context"
"encoding/json"
"reflect"

"github.com/go-logr/logr"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/conversion"

network "github.com/Azure/azure-service-operator/v2/api/network/v1beta20201101storage"
. "github.com/Azure/azure-service-operator/v2/internal/logging"
"github.com/Azure/azure-service-operator/v2/internal/resolver"
"github.com/Azure/azure-service-operator/v2/internal/util/kubeclient"
"github.com/Azure/azure-service-operator/v2/pkg/genruntime"
"github.com/Azure/azure-service-operator/v2/pkg/genruntime/extensions"
)

// Attention: A lot of code in this file is very similar to the logic in virtual_network_extensions.go.
// The two should be kept in sync as much as possible.

var _ extensions.ARMResourceModifier = &RouteTableExtension{}

func (extension *RouteTableExtension) ModifyARMResource(
ctx context.Context,
armObj genruntime.ARMResource,
obj genruntime.ARMMetaObject,
kubeClient kubeclient.Client,
resolver *resolver.Resolver,
log logr.Logger) (genruntime.ARMResource, error) {

typedObj, ok := obj.(*network.RouteTable)
if !ok {
return nil, errors.Errorf("cannot run on unknown resource type %T", obj)
}

// Type assert that we are the hub type. This should fail to compile if
// the hub type has been changed but this extension has not been updated to match
var _ conversion.Hub = typedObj

routeGVK := getRouteGVK(obj)

routes := &network.RouteTablesRouteList{}
matchingFields := client.MatchingFields{".metadata.ownerReferences[0]": string(obj.GetUID())} // TODO: Need to add this index
err := kubeClient.List(ctx, routes, matchingFields)
if err != nil {
return nil, errors.Wrapf(err, "failed listing routes owned by RouteTable %s/%s", obj.GetNamespace(), obj.GetName())
}

var armRoutes []genruntime.ARMResourceSpec
for _, route := range routes.Items {
route := route

var transformed genruntime.ARMResourceSpec
transformed, err = transformToARM(ctx, &route, routeGVK, kubeClient, resolver)
if err != nil {
return nil, errors.Wrapf(err, "failed to transform route %s/%s", route.GetNamespace(), route.GetName())
}
armRoutes = append(armRoutes, transformed)
}

log.V(Info).Info("Found routes to include on RouteTable", "count", len(armRoutes), "names", genruntime.ARMSpecNames(armRoutes))

err = fuzzySetRoutes(armObj.Spec(), armRoutes)
if err != nil {
return nil, errors.Wrapf(err, "failed to set routes")
}

return armObj, nil
}

func getRouteGVK(routeTable genruntime.ARMMetaObject) schema.GroupVersionKind {
gvk := genruntime.GetOriginalGVK(routeTable)
gvk.Kind = reflect.TypeOf(network.RouteTablesRoute{}).Name() // "RouteTableRoute"

return gvk
}

// TODO: When we move to Swagger as the source of truth, the type for routetable.properties.routes and routtableroutes.properties
// TODO: may be the same, so we can do away with the JSON serialization part of this assignment.
// fuzzySetRoutes assigns a collection of routes to the routes property of the route table. Since there are
// many possible ARM API versions and we don't know which one we're using, we cannot do this statically.
// To make matters even more horrible, the type used in the routetable.properties.routes is not the same
// type as used for routes.properties (although structurally they are basically the same). To overcome this
// we JSON serialize the route and deserialize it into the routetableroutes.properties.routes field.
func fuzzySetRoutes(routeTable genruntime.ARMResourceSpec, routes []genruntime.ARMResourceSpec) (err error) {
defer func() {
if x := recover(); x != nil {
err = errors.Errorf("caught panic: %s", x)
}
}()

// Here be dragons
routeTableValue := reflect.ValueOf(routeTable)
routeTableValue = reflect.Indirect(routeTableValue)

if (routeTableValue == reflect.Value{}) {
return errors.Errorf("cannot assign to nil route table")
}

propertiesField := routeTableValue.FieldByName("Properties")
if (propertiesField == reflect.Value{}) {
return errors.Errorf("couldn't find properties field on route table")
}

propertiesValue := reflect.Indirect(propertiesField)
if (propertiesValue == reflect.Value{}) {
// If the properties field is nil, we must construct an entirely new properties and assign it here
temp := reflect.New(propertiesField.Type().Elem())
propertiesField.Set(temp)
propertiesValue = reflect.Indirect(temp)
}

routesField := propertiesValue.FieldByName("Routes")
if (routesField == reflect.Value{}) {
return errors.Errorf("couldn't find routes field on route table")
}

if routesField.Type().Kind() != reflect.Slice {
return errors.Errorf("routes field on route table was not of kind Slice")
}

elemType := routesField.Type().Elem()
routesSlice := reflect.MakeSlice(routesField.Type(), 0, 0)

for _, route := range routes {
embeddedRoute := reflect.New(elemType)
err = fuzzySetRoute(route, embeddedRoute)
if err != nil {
return err
}

routesSlice = reflect.Append(routesSlice, reflect.Indirect(embeddedRoute))
}

// Now do the assignment
routesField.Set(routesSlice)

return err
}

func fuzzySetRoute(route genruntime.ARMResourceSpec, embeddedRoute reflect.Value) error {
routeJSON, err := json.Marshal(route)
if err != nil {
return errors.Wrapf(err, "failed to marshal route json")
}

err = json.Unmarshal(routeJSON, embeddedRoute.Interface())
if err != nil {
return errors.Wrapf(err, "failed to unmarshal route JSON")
}

// Safety check that these are actually the same:
// We can't use reflect.DeepEqual because the types are not the same.
embeddedRouteJSON, err := json.Marshal(embeddedRoute.Interface())
if err != nil {
return errors.Wrap(err, "unable to check that embedded route is the same as route")
}
if string(embeddedRouteJSON) != string(routeJSON) {
return errors.Errorf("embeddedRouteJSON (%s) != routeJSON (%s)", string(embeddedRouteJSON), string(routeJSON))
}

return nil
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 21 additions & 17 deletions v2/api/network/customizations/virtual_network_extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import (
"github.com/Azure/azure-service-operator/v2/pkg/genruntime/extensions"
)

// Attention: A lot of code in this file is very similar to the logic in route_table_extensions.go.
// The two should be kept in sync as much as possible.

var _ extensions.ARMResourceModifier = &VirtualNetworkExtension{}

func (extension *VirtualNetworkExtension) ModifyARMResource(
Expand Down Expand Up @@ -62,7 +65,7 @@ func (extension *VirtualNetworkExtension) ModifyARMResource(
armSubnets = append(armSubnets, transformed)
}

log.V(Info).Info("Found subnets to include on VNET", "count", len(armSubnets), "names", getSubnetNames(armSubnets))
log.V(Info).Info("Found subnets to include on VNET", "count", len(armSubnets), "names", genruntime.ARMSpecNames(armSubnets))

err = fuzzySetSubnets(armObj.Spec(), armSubnets)
if err != nil {
Expand All @@ -72,15 +75,6 @@ func (extension *VirtualNetworkExtension) ModifyARMResource(
return armObj, nil
}

func getSubnetNames(subnets []genruntime.ARMResourceSpec) []string {
var result []string
for _, s := range subnets {
result = append(result, s.GetName())
}

return result
}

func getSubnetGVK(vnet genruntime.ARMMetaObject) schema.GroupVersionKind {
gvk := genruntime.GetOriginalGVK(vnet)
gvk.Kind = reflect.TypeOf(network.VirtualNetworksSubnet{}).Name() // "VirtualNetworksSubnet"
Expand Down Expand Up @@ -115,12 +109,12 @@ func transformToARM(
return nil, errors.Wrapf(err, "transforming resource %s to ARM", obj.GetName())
}

typedArmSpec, ok := armSpec.(genruntime.ARMResourceSpec)
typedARMSpec, ok := armSpec.(genruntime.ARMResourceSpec)
if !ok {
return nil, errors.Errorf("casting armSpec of type %T to genruntime.ARMResourceSpec", armSpec)
}

return typedArmSpec, nil
return typedARMSpec, nil
}

// TODO: When we move to Swagger as the source of truth, the type for vnet.properties.subnets and subnet.properties
Expand All @@ -130,7 +124,13 @@ func transformToARM(
// To make matters even more horrible, the type used in the vnet.properties.subnets property is not the same
// type as used for subnet.properties (although structurally they are basically the same). To overcome this
// we JSON serialize the subnet and deserialize it into the vnet.properties.subnets field.
func fuzzySetSubnets(vnet genruntime.ARMResourceSpec, subnets []genruntime.ARMResourceSpec) error {
func fuzzySetSubnets(vnet genruntime.ARMResourceSpec, subnets []genruntime.ARMResourceSpec) (err error) {
defer func() {
if x := recover(); x != nil {
err = errors.Errorf("caught panic: %s", x)
}
}()

// Here be dragons
vnetValue := reflect.ValueOf(vnet)
vnetValue = reflect.Indirect(vnetValue)
Expand All @@ -142,12 +142,16 @@ func fuzzySetSubnets(vnet genruntime.ARMResourceSpec, subnets []genruntime.ARMRe
if (propertiesField == reflect.Value{}) {
return errors.Errorf("couldn't find properties field on vnet")
}
propertiesField = reflect.Indirect(propertiesField)
if (propertiesField == reflect.Value{}) {
return errors.Errorf("cannot assign to nil vnet properties")

propertiesValue := reflect.Indirect(propertiesField)
if (propertiesValue == reflect.Value{}) {
// If the properties field is nil, we must construct an entirely new properties and assign it here
temp := reflect.New(propertiesField.Type().Elem())
propertiesField.Set(temp)
propertiesValue = reflect.Indirect(temp)
}

subnetsField := propertiesField.FieldByName("Subnets")
subnetsField := propertiesValue.FieldByName("Subnets")
if (subnetsField == reflect.Value{}) {
return errors.Errorf("couldn't find subnets field on vnet")
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 0ebfdbc

Please sign in to comment.