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
Add instance types #6564
Add instance types #6564
Conversation
a9f76ca
to
4aa7e30
Compare
!!build!! |
!!try!! |
2035d3d
to
1d9b90c
Compare
!!try!! |
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.
Mostly looking good. I think we should get input from @urosj or someone on GUI about cost/currency details.
package provider | ||
|
||
import ( | ||
names "gopkg.in/juju/names.v2" |
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.
drop alias (goimports is doing this to you too?)
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.
yes, I just realized that, mm, Ill have to check what is going on with goimports so I can disable it
|
||
// NewAPI returns an API pointer. | ||
func NewAPI(backend Backend, authorizer facade.Authorizer, envNew environs.NewEnvironFunc) (*API, error) { | ||
if !authorizer.AuthModelManager() { |
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.
The instance types API is intended for users to call.
}, nil | ||
} | ||
|
||
type modelConfigAble 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.
use environs.ConfigGetter?
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.
Does not satisfy what we need here (lacks err)
return params.InstanceTypesResults{}, errors.Trace(err) | ||
} | ||
|
||
for i, c := range cons.Constraints { |
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.
Please add a TODO to cache the results. We need to avoid repeatedly querying the cloud, but cannot poll the cloud for reasons described in the spec.
|
||
var _ = gc.Suite(&providerTypesSuite{}) | ||
|
||
var over9kCPUCores uint64 = 9001 |
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.
:)
@@ -0,0 +1,16 @@ | |||
package cloudsigma |
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.
copyright
@@ -0,0 +1,16 @@ | |||
package dummy |
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.
copyright
(etc.)
package dummy | ||
|
||
import ( | ||
"github.com/juju/errors" |
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.
\n
(etc.)
return instances.InstanceTypesWithCostMetadata{}, errors.Trace(err) | ||
} | ||
for i, t := range iTypes { | ||
iTypes[i].Cost = t.Cost / 1000 |
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.
Nope. I must have been unclear, sorry.
What I meant was to include a divisor in the result struct, not to truncate/drop precision in the cost field. i.e. the results should include:
- the cost (e.g. in deci-cents)
- a divisor (e.g. of 1000) to get to dollars, or whatever the unit is
- the currency/unit (e.g. $USD/h)
e.g. with cost=1234, divisor=1000, currency/unit=$USD/h, the GUI could display "1.234 $USD/h".
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.
gotcha, will do
} | ||
|
||
result := make([]instances.InstanceType, len(resultUnique)) | ||
i := 0 |
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 i, it := range resultUnique {
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.
once again, map. :p
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.
LGTM, but let's get feedback from @urosj before committing to this.
InstanceTypes []InstanceType `json:"instance-types,omitempty"` | ||
CostUnit string `json:"cost-unit,omitempty"` | ||
CostCurrency string `json:"cost-currency,omitempty"` | ||
Divisor uint64 `json:"divisor,omitempty"` |
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.
CostDivisor/cost-divisor please
CostCurrency string | ||
// Divisor indicates a number that must be applied to InstanceType.Cost to obtain | ||
// a number that is in CostUnit. | ||
Divisor uint64 |
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.
CostDivisor
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 think we should document that setting (Cost)Divisor to zero will mean that the cost is treated as already being in specified unit, as the (cost-)divisor field will be omitted from the API response. i.e. the zero value is equivalent to 1.
@@ -0,0 +1,60 @@ | |||
package gce |
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.
copyright
@@ -0,0 +1,23 @@ | |||
package joyent |
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.
copyright
@@ -0,0 +1,15 @@ | |||
package lxd |
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.
copyright
|
||
var _ environs.InstanceTypesFetcher = (*environ)(nil) | ||
|
||
func (e environ) InstanceTypes(c constraints.Value) (instances.InstanceTypesWithCostMetadata, error) { |
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.
do we need this one? I think the openstack impl is sufficient?
@@ -0,0 +1,16 @@ | |||
package vsphere |
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.
copyright
package vsphere | ||
|
||
import ( | ||
"github.com/juju/errors" |
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.
\n
@@ -0,0 +1,15 @@ | |||
package rackspace |
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.
copyright
package rackspace | ||
|
||
import ( | ||
"github.com/juju/errors" |
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.
\n
costUnit := instanceTypes.CostUnit | ||
costCurrency := instanceTypes.CostCurrency | ||
divisor := instanceTypes.CostDivisor | ||
if err != nil { |
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.
Move this next to the call to InstanceTypes ?
|
||
res[i] = params.InstanceTypesResult{ | ||
InstanceTypes: toParamsInstanceTypeResult(allTypes), | ||
CostUnit: costUnit, |
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.
Inline? i.e. use instanceTypes.CostUnit instead of using a temp variable.
|
||
// InstanceTypesFetcher is an interface that allows for instance information from | ||
// a provider to be obtained. | ||
type InstanceTypesFetcher 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.
Is it really worth having a separate interface type for this single method? Why not just make it part of Environ?
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.
Separation of concerns
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.
What's there LGTM, but the apiserver facade changes are missing.
|
||
// ModelInstanceTypesConstraint contains a constraint applied when filtering instance types. | ||
type ModelInstanceTypesConstraint struct { | ||
// Value, if specified, contains the constraints to filter |
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.
Can you please change the name to Constraints, and tag to "constraints", to match the field in CloudInstanceTypesConstraints? That way one is a superset of the other.
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.
Ping. This one is not so small, please don't forget about this
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.
Fixed
InstanceTypes []InstanceType `json:"instance-types,omitempty"` | ||
CostUnit string `json:"cost-unit,omitempty"` | ||
CostCurrency string `json:"cost-currency,omitempty"` | ||
// CostDivisor Will be present only when the Cost is not expressed in CostUnit. |
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.
s/Will/will/
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.
fixed
} | ||
result := make([]instances.InstanceType, len(types)) | ||
i := 0 | ||
for _, iType := range types { |
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.
@perrito666 like rogpeppe, I do the same when going from map to slice. Only because it's more compact and reads better (to me).
}, | ||
} | ||
return &machineType, nil | ||
|
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.
delete empty line
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.
fixed
) | ||
|
||
var _ environs.InstanceTypesFetcher = (*environ)(nil) | ||
var virtType = "kvm" |
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.
const?
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.
if I use a const I cannot pass its addres afterwards (instances.InstanceType.VirtType is a pointer)
a7aea2a
to
1d46062
Compare
"github.com/juju/juju/environs" | ||
"github.com/juju/juju/state" | ||
"github.com/juju/juju/state/stateenvirons" | ||
names "gopkg.in/juju/names.v2" |
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.
move me, drop alias
package cloud | ||
|
||
import ( | ||
"github.com/juju/errors" |
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.
move me
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.
fixed
// EnvironConfigGetter implements environs.EnvironConfigGetter | ||
// in terms of a *state.State. | ||
type cloudEnvironConfigGetter struct { | ||
*state.State |
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.
You only need state.CloudAccessor and the GetModel function. Backend (apiserver/cloud/backend.go) already embeds state.CloudAccessor, so you just need to add the GetModel method, and you can use Backend instead of State.
You could otherwise just ignore the ModelTag, and use the existing Backend.ControllerModel method.
I think it might be simpler though if you just pass the CloudSpec (or perhaps Environ?) into the common.InstanceTypes function? i.e. have the facades pull apart the args, get the appropriate model, cloud, and region, and construct a CloudSpec from them. You may as well then get the Environ then too, and avoid passing the backend in just to get ModelConfig?
if cons.Constraints != nil { | ||
value = *cons.Constraints | ||
} | ||
backend := cloudEnvironConfigGetter{ |
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.
Please parse the cons.CloudTag here, and check that it is the same as the controller model's cloud. We may relax this later, but it's better to be strict to avoid having to deal with sloppily written clients later.
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.
Fixed
|
||
// InstanceTypes returns instance type information for the cloud and region | ||
// in which the current model is deployed. | ||
func (api *CloudAPI) InstanceTypes(cons params.CloudInstanceTypesConstraints) (params.InstanceTypesResults, error) { |
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.
we need a test or two for this
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.
Fixed
itCons := common.NewInstanceTypeConstraints(backend, value, modelTag) | ||
it, err := common.InstanceTypes(itCons) | ||
if err != nil { | ||
return params.InstanceTypesResults{}, errors.Trace(err) |
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.
An error due to one of them failing should not cause all of them to fail.
Either assume that common.InstanceTypes never fills in the Error field, and if it returns an error, have the caller (Cloud or MachineManager facade) fill it in; or change common.InstanceTypes to not have an error result.
environs.EnvironConfigGetter | ||
ModelTag() names.ModelTag | ||
Close() error | ||
GetModel(names.ModelTag) (*state.Model, error) |
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.
please not state.Model, use an 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.
This is mirroring state, there is nothing I can do.
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.
Don't be so defeatist ;)
Look at ControllerModel in apiserver/cloud/backend.go for an example of how to do this. Anyway, I think this will be moot if you pass the Environ in?
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 realized that seconds after writing that and fixed it with ashim, and am now writing the tests
// InstanceTypes returns a list of the available instance types in the provider according | ||
// to the passed constraints. | ||
func InstanceTypes(cons instanceTypeConstraints) (params.InstanceTypesResult, error) { | ||
m, err := cons.backend.GetModel(cons.modelTag) |
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.
The fact that we're calling GetModel multiple times for what's always going to be the same model is a bit of a smell I think. As mentioned before, I think we should pass the CloudSpec (perhaps Environ?) in.
If this were split out, then it would be reasonable to fail the entire API call if we fail to get the CloudSpec in the case of the MachineManager facade. For the Cloud facade, it would be reasonable to fail the entire API call if we fail to get the Model; and then fail individual results if we can't get the CloudSpec (e.g. if an invalid CloudTag or region is specified).
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.
Looking better, thanks.
// information. | ||
type InstanceTypeBackend interface { | ||
environs.EnvironConfigGetter | ||
Close() error |
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.
Unused, drop me. I don't think it's appropriate to hand off ownership/responsibility to InstanceTypes anyway.
Config() (*config.Config, error) | ||
} | ||
|
||
// NewModelEnvironConfigGetter can create |
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.
Can you please move this, modelEnvironConfigGetter, and InstanceTypeBackend to a separate file? It's only tangentially related to instance types. Also, InstanceTypeBackend should be renamed to be something to do with model/environ config.
It might be simpler just to have this as a struct of two functions? e.g.
type EnvironConfigGetterFuncs struct {
ModelConfigFunc func() (*config.Config, error)
CloudSpecFunc func(names.ModelTag) (environs.CloudSpec, error)
}
func (f EnvironConfigGetterFuncs) ModelConfig() (*config.Config, error) {
return f.ModelConfigFunc()
}
...
Just a thought, feel free to ignore that.
The EnvironConfigGetter interface is kinda weird. Both methods should probably be taking a ModelTag...
|
||
// InstanceTypes returns a list of the available instance types in the provider according | ||
// to the passed constraints. | ||
func InstanceTypes(cons instanceTypeConstraints) (params.InstanceTypesResult, error) { |
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 pass around an unexported 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.
Fixed
// InstanceTypes returns instance type information for the cloud and region | ||
// in which the current model is deployed. | ||
func (mm *MachineManagerAPI) InstanceTypes(cons params.ModelInstanceTypesConstraints) (params.InstanceTypesResults, error) { | ||
st := mm.st.(*state.State) |
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.
Please do the same for this method as you did for the other one, and use interfaces (stateInterface).
jujutesting.Stub | ||
|
||
results map[constraints.Value]instances.InstanceTypesWithCostMetadata | ||
} | ||
|
||
func (m *mockEnviron) InstanceTypes(c constraints.Value) (instances.InstanceTypesWithCostMetadata, error) { | ||
fmt.Println(c) |
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.
d
return m, nil | ||
} | ||
|
||
func (s stateShim) Cloud(cloudName string) (cloud.Cloud, error) { |
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 Cloud* methods aren't needed? stateShim embeds State, and State already has those methods
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.
LGTM once remaining comments are addressed (those from this review and ones from before, unless we've already talked about them.)
"github.com/juju/juju/state/stateenvirons" | ||
) | ||
|
||
// EnvironConfigGetter implements environs.EnvironConfigGetter |
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.
cloudEnvironConfigGetter
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.
fixed
return params.InstanceTypesResults{}, errors.Trace(err) | ||
} | ||
modelTag := c.ModelTag() | ||
m, err := api.backend.GetModel(modelTag) |
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.
you end up with the same model as returned by ControllerModel
just do m, err := api.backend.ControllerModel()
then you can get rid of the ModelTag method from Backend
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.
fixed
"github.com/juju/juju/environs/config" | ||
) | ||
|
||
// EnvironConfigGetterFuncs holds implements environs.EnvironConfigGetter |
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.
s/holds //
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.
fixed
|
||
// ModelInstanceTypesConstraint contains a constraint applied when filtering instance types. | ||
type ModelInstanceTypesConstraint struct { | ||
// Value, if specified, contains the constraints to filter |
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.
Ping. This one is not so small, please don't forget about this
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.
Looks good and works well. Just a couple of minor issues. Did you talk about opening the Cloud call up to anonymous users?
if m.Cloud() != cloudTag.Id() { | ||
result[i] = params.InstanceTypesResult{Error: common.ServerError(errors.NotValidf("asking %s cloud information to %s cloud", cloudTag.Id(), m.Cloud()))} | ||
continue | ||
} |
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'd check also that cons.CloudRegion is not empty here. otherwise I guess the subsequent error would be ugly and not really informative. This case should also be tested.
) (environs.Environ, error) { | ||
return &env, nil | ||
} | ||
r, err := machinemanager.InstanceTypes(&api, fakeEnvironGet, cons) |
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.
Should we also test the case no constraints are passed? In this case I'd expect an empty result.
5ee6359
to
48377ee
Compare
@@ -75,6 +75,10 @@ func instanceTypes(api *CloudAPI, | |||
result[i] = params.InstanceTypesResult{Error: common.ServerError(errors.NotValidf("asking %s cloud information to %s cloud", cloudTag.Id(), m.Cloud()))} | |||
continue | |||
} | |||
if cons.CloudRegion == "" { |
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.
Not all clouds have regions. I'm not sure if the validation belongs here or not, but if we do validate, we need to check that the cloud supports regions before requiring that it's empty.
Yes we have talked about it, but as we agreed we'll defer this bit. It's not feasible to make that change for 2.1. |
Added InstanceTypes API endpoint that allows to learn the available instance types in the model region/zone to make better deployment decisions.
f7ce73c
to
6dcfaf0
Compare
!!try!! |
|
Status: merge request accepted. Url: http://juju-ci.vapour.ws:8080/job/github-merge-juju |
Added InstanceTypes API endpoint that allows to learn
the available instance types in the model region/zone
to make better deployment decisions.
Tests are missing for 3 out of 4 providers implemented but I am keen to get the rest of the code reviewed as this needs to be merged soon.
This follows: https://docs.google.com/document/d/1m6iNWOMYyGwHbDbl8-u_VSZozeZQklxUOgM5t_0wbMQ/edit#heading=h.kc1rayquamxb
###QA
Run Tests.
Create an API client call that points to the controller and Perform the call for the different providers to obtain lists of available types. (This is intended to be used by the GUI)