Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
Expose the StaticRoutes API #63
Conversation
jameinel
added some commits
Feb 23, 2017
reedobrien
approved these changes
Feb 23, 2017
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.
| + // 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 |
| + // 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 |
| + // want to configure a route to that subnet via GatewayIP | ||
| + Destination() Subnet | ||
| + // GatewayIP is the IPAddress of the | ||
| + GatewayIP() string |
reedobrien
Feb 23, 2017
why not a net.IPAddr? (after reading through I think I get why we arent' using net.IPAddr, net.IPNet, and friends.) Alas...
| + // 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. |
jameinel
Feb 23, 2017
Owner
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.
| +) | ||
| + | ||
| +type staticRoute struct { | ||
| + // Add the controller in when we need to do things with the staticRoute. |
| + 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") |
reedobrien
Feb 23, 2017
Is there a non base schema check? Why not just a schema check failure? Or just make source a string map and let the compiler type check... I am sure there's reasons... I just don't know the history.
jameinel
Feb 23, 2017
Owner
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{}) |
reedobrien
Feb 23, 2017
So we convert []interface to a stringmap and then convert it back to []interface? If source is valid, why not just use it?
jameinel
Feb 23, 2017
Owner
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.
| + return readStaticRouteList(valid, readFunc) | ||
| +} | ||
| + | ||
| +// readStaticRouteList expects the values of the sourceList to be string maps. |
reedobrien
Feb 23, 2017
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. |
jameinel
Feb 23, 2017
Owner
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": |
reedobrien
Feb 23, 2017
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.
jameinel
Feb 23, 2017
Owner
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 |
jameinel
reviewed
Feb 23, 2017
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.
| + // 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 |
| + // 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 |
| + // 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. |
jameinel
Feb 23, 2017
Owner
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.
| +) | ||
| + | ||
| +type staticRoute struct { | ||
| + // Add the controller in when we need to do things with the staticRoute. |
| + 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") |
reedobrien
Feb 23, 2017
Is there a non base schema check? Why not just a schema check failure? Or just make source a string map and let the compiler type check... I am sure there's reasons... I just don't know the history.
jameinel
Feb 23, 2017
Owner
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{}) |
reedobrien
Feb 23, 2017
So we convert []interface to a stringmap and then convert it back to []interface? If source is valid, why not just use it?
jameinel
Feb 23, 2017
Owner
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. |
jameinel
Feb 23, 2017
Owner
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": |
reedobrien
Feb 23, 2017
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.
jameinel
Feb 23, 2017
Owner
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 |
jameinel
referenced this pull request
in juju/juju
Feb 24, 2017
Merged
2.1.1 static routes for containers #7034
| + "source": schema.StringMap(schema.Any()), | ||
| + "destination": schema.StringMap(schema.Any()), | ||
| + "gateway_ip": schema.String(), | ||
| + "metric": schema.ForceInt(), |
|
$$merge$$ |
|
Status: merge request accepted. Url: http://juju-ci.vapour.ws:8080/job/github-merge-juju-gomaasapi |
jameinel commentedFeb 23, 2017
•
Edited 1 time
-
jameinel
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