Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose the StaticRoutes API #63

Merged
merged 4 commits into from
Feb 27, 2017
Merged

Expose the StaticRoutes API #63

merged 4 commits into from
Feb 27, 2017

Conversation

jameinel
Copy link
Member

@jameinel jameinel commented Feb 23, 2017

This implements support for listing StaticRoutes.
https://docs.ubuntu.com/maas/2.0/en/api#static-route

The only thing it supports is reading all of them. It doesn't support creating them, or reading just a single one of them. However, that's all we concretely need for Juju's use case. (MaaS doesn't let you look up static routes by source/destination subnets, etc. So we always have to read all of them and then find the ones we care about from there.)

This is necessary to implement Static Route support for containers in Juju:
https://bugs.launchpad.net/juju/+bug/1653708

Start by implementing Static Route handler in the test service, in
preparation for updating the actual code base to interact with the
test service.
Controller.StaticRoutes() now works, and we have some
direct tests of parsing the values from a canned MAAS response,
and a test service that can also be populated with routes.
Copy link

@reedobrien reedobrien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems a lot of type checking in code rather than letting the compiler do it. Mostly nits and questions, but seems good to me.

I am not too familiar with maas api and it is >500 line change so you should get another +1.

Also there's no QA steps to verify.

interfaces.go Outdated
// Destination is the subnet that a machine wants to send packets to. We
// want to configure a route to that subnet via GatewayIP
Destination() Subnet
// GatewayIP is the IPAddress of the

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

of the ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

interfaces.go Outdated
// inside Source should use GatewayIP to reach Destination addresses.)
Source() Subnet
// Destination is the subnet that a machine wants to send packets to. We
// want to configure a route to that subnet via GatewayIP

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

full stop?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// Metric is the routing metric that determines whether this route will
// take precedence over similar routes (there may be a route for 10/8, but
// also a more concrete route for 10.0/16 that should take precedence if it
// applies.) Metric should be a non-negative integer.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why isn't it a uint if it should not be negative?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, 2 reasons

  1. config.Schema doesn't support Uint
  2. It allows you to have an "invalid" value inline. I don't know if that will actually be used, but "ID" is also actually a uint on the MAAS side, but is always presented as an 'int', so I followed suit.

interfaces.go Outdated
// want to configure a route to that subnet via GatewayIP
Destination() Subnet
// GatewayIP is the IPAddress of the
GatewayIP() string

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not a net.IPAddr? (after reading through I think I get why we arent' using net.IPAddr, net.IPNet, and friends.) Alas...

staticroute.go Outdated
)

type staticRoute struct {
// Add the controller in when we need to do things with the staticRoute.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copied from Spaces, I can just remove it.

if err != nil {
return nil, errors.Annotatef(err, "static-route base schema check failed")
}
valid := coerced.([]interface{})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we convert []interface to a stringmap and then convert it back to []interface? If source is valid, why not just use it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coerce returns an interface{}, what we have is a list of interfaces []interface{}. We don't have a StringMap, because that's not how Golang works.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. My brain ignored the [] in the type assertion.

return readStaticRouteList(valid, readFunc)
}

// readStaticRouteList expects the values of the sourceList to be string maps.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again wondering why sourceList isn't a a slice of string maps (or an interface that has a StringMaps method which returns one). I'm sure for historical reasons.

}
valid := coerced.(map[string]interface{})
// From here we know that the map returned from the schema coercion
// contains fields of the right type.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Smells like typechecking:)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we have a "map[string]interface{}", but we know the type of all the contents that the schema has defined (valid["resource_uri"] must be a 'string' because it was defined in the schema, and Coerce would have given an error if it was the wrong type.)

err = json.NewEncoder(w).Encode(server.staticRoutes[ID])
}
checkError(err)
case "POST":

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These imply there's something to test for PUT and POST. If there isn't they should prolly just be handled by default -- or be a TODO.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was copying this, but I went ahead and put in at least http.StatusNotImplemented.

return postedStaticRoute
}

// NewStaticRoute creates a Static Route in the test server

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Full stop

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Member Author

@jameinel jameinel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the overall comments-

We are converting from a JSON string into a typed object. I believe we are choosing to do so via schema.Coerce instead of json.Unmarshal because Unmarshal is very loose in its interpretation of your content. I believe that schema.Coerce enforces that fields that are flagged as 'must be there' will cause an error if it isn't there, rather than silently using the zero value, etc.

At the very least, I wouldn't change the mechanism for deserializing json strings for one type vs using what all the other types did.

interfaces.go Outdated
// inside Source should use GatewayIP to reach Destination addresses.)
Source() Subnet
// Destination is the subnet that a machine wants to send packets to. We
// want to configure a route to that subnet via GatewayIP
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

interfaces.go Outdated
// Destination is the subnet that a machine wants to send packets to. We
// want to configure a route to that subnet via GatewayIP
Destination() Subnet
// GatewayIP is the IPAddress of the
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// Metric is the routing metric that determines whether this route will
// take precedence over similar routes (there may be a route for 10/8, but
// also a more concrete route for 10.0/16 that should take precedence if it
// applies.) Metric should be a non-negative integer.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, 2 reasons

  1. config.Schema doesn't support Uint
  2. It allows you to have an "invalid" value inline. I don't know if that will actually be used, but "ID" is also actually a uint on the MAAS side, but is always presented as an 'int', so I followed suit.

staticroute.go Outdated
)

type staticRoute struct {
// Add the controller in when we need to do things with the staticRoute.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copied from Spaces, I can just remove it.

checker := schema.List(schema.StringMap(schema.Any()))
coerced, err := checker.Coerce(source, nil)
if err != nil {
return nil, errors.Annotatef(err, "static-route base schema check failed")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is checking that we have a list of objects (eg [{}, {}, {}], which is supposed to be true regardless of what version of the API we are talking to.

I think this was a deliberate choice from the people implementing the maas parser that you break apart parsing a list of objects into a top level 'parse it into a list' and then intermediate 'parse it into version-specific objects'.

if err != nil {
return nil, errors.Annotatef(err, "static-route base schema check failed")
}
valid := coerced.([]interface{})
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coerce returns an interface{}, what we have is a list of interfaces []interface{}. We don't have a StringMap, because that's not how Golang works.

}
valid := coerced.(map[string]interface{})
// From here we know that the map returned from the schema coercion
// contains fields of the right type.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we have a "map[string]interface{}", but we know the type of all the contents that the schema has defined (valid["resource_uri"] must be a 'string' because it was defined in the schema, and Coerce would have given an error if it was the wrong type.)

err = json.NewEncoder(w).Encode(server.staticRoutes[ID])
}
checkError(err)
case "POST":
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was copying this, but I went ahead and put in at least http.StatusNotImplemented.

return postedStaticRoute
}

// NewStaticRoute creates a Static Route in the test server
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

"source": schema.StringMap(schema.Any()),
"destination": schema.StringMap(schema.Any()),
"gateway_ip": schema.String(),
"metric": schema.ForceInt(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a ForceUint now.

@jameinel
Copy link
Member Author

$$merge$$

@jujubot
Copy link
Contributor

jujubot commented Feb 27, 2017

Status: merge request accepted. Url: http://juju-ci.vapour.ws:8080/job/github-merge-juju-gomaasapi

@jujubot jujubot merged commit cfbc096 into juju:master Feb 27, 2017
jujubot added a commit to juju/juju that referenced this pull request Feb 27, 2017
2.1.1 static routes for containers

## Description of change

This change allows Juju to propagate Static Route information that MAAS declares into containers. It depends on this patch to gomaasapi: juju/gomaasapi#63. (I updated dependencies.tsv for my version of the code, but we should update dependencies to match the final merge.) The other changes to dependencies.tsv are just reordering because of how the file was generated.

We use the Static Routes API https://docs.ubuntu.com/maas/2.0/en/api#static-route to find what routes are needed, and apply them based on their Source subnet.
We add a post-up and a pre-down rule that uses 'ip route add'. This differs slightly from MAAS and Curtin that use "route add", but it means we can use CIDR instead of netmask.
http://bazaar.launchpad.net/~curtin-dev/curtin/trunk/view/head:/curtin/net/__init__.py#L380

## QA steps

Using MAAS create a static route for one of your subnets (you can do so via the web ui by looking at the source subnet). You'll need to create destination subnets for it to route to. Afterwards, deploy a node, and you should see that "ip route show" lists that static route. You should then be able to deploy a container onto that machine using the same space. It should have the same route. Without this patch (stock juju 2.1.0) it should not.

## Documentation changes

Potentially we should document static routes with containers, but it feels a bit like people using static routes just expect it to work.

## Bug reference

[lp:1653708](https://bugs.launchpad.net/juju/+bug/1653708)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants