-
Notifications
You must be signed in to change notification settings - Fork 36
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
Conversation
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.
There was a problem hiding this 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
of the ...
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
full stop?
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, 2 reasons
- config.Schema doesn't support Uint
- 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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO?
There was a problem hiding this comment.
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{}) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Smells like typechecking:)
There was a problem hiding this comment.
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": |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
testservice_staticroutes.go
Outdated
return postedStaticRoute | ||
} | ||
|
||
// NewStaticRoute creates a Static Route in the test server |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Full stop
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this 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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, 2 reasons
- config.Schema doesn't support Uint
- 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. |
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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{}) |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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": |
There was a problem hiding this comment.
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.
testservice_staticroutes.go
Outdated
return postedStaticRoute | ||
} | ||
|
||
// NewStaticRoute creates a Static Route in the test server |
There was a problem hiding this comment.
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(), |
There was a problem hiding this comment.
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.
|
Status: merge request accepted. Url: http://juju-ci.vapour.ws:8080/job/github-merge-juju-gomaasapi |
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)
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