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 f3f1729
Show file tree
Hide file tree
Showing 48 changed files with 7,855 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.

174 changes: 174 additions & 0 deletions v2/api/network/customizations/route_table_extensions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// 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) {
if len(routes) == 0 {
// Nothing to do
return nil
}

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.

43 changes: 26 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,18 @@ 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) {
if len(subnets) == 0 {
// Nothing to do
return nil
}

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 +147,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.

Loading

0 comments on commit f3f1729

Please sign in to comment.