Skip to content

Commit

Permalink
Subpolicy list (#15)
Browse files Browse the repository at this point in the history
* Create endpoint for retrieving subpolicy list

* Move all string constants into a separate file

* Implement dropdown menu for selecting subpolicies

* Combine defaults fetching endpoint into one

* Create dropdown menu for selection of ensembler

* Use URL to form addresses

* Convert axios calls to use async/await

* Settle rebase issues
  • Loading branch information
slai11 authored Mar 28, 2019
1 parent aa9ebcb commit d7fb784
Show file tree
Hide file tree
Showing 19 changed files with 126 additions and 54 deletions.
9 changes: 9 additions & 0 deletions app/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/datagovsg/nomad-parametric-autoscaler/logging"
"github.com/datagovsg/nomad-parametric-autoscaler/policy"
"github.com/datagovsg/nomad-parametric-autoscaler/resources"
"github.com/datagovsg/nomad-parametric-autoscaler/types"
"github.com/gin-gonic/gin"
)

Expand All @@ -24,6 +25,14 @@ func (ep *endpoints) GetResourceStatus(c *gin.Context) {
c.JSON(200, ep.wp.policy.GetResourceStatus())
}

// GetSubpolicyList returns list of possible subpolicies
func (ep *endpoints) GetPredefinedFeatures(c *gin.Context) {
c.JSON(200, gin.H{
"subpolicies": types.SubpolicyList,
"ensemblers": types.EnsemblerList,
})
}

// UpdatePolicy parses body and builds a new policy to replace
// existing policy
// returns 200 if policy is well-formed and replaces existing successfully
Expand Down
1 change: 1 addition & 0 deletions app/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func NewRouter(ep *endpoints) *gin.Engine {
// Core endpoints
router.GET("/state", ep.GetPolicy)
router.GET("/status", ep.GetResourceStatus)
router.GET("/predefined", ep.GetPredefinedFeatures)
router.POST("/update", ep.UpdatePolicy)

// Helper endpoints
Expand Down
6 changes: 4 additions & 2 deletions policy/ensemble.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package policy

import (
"fmt"

"github.com/datagovsg/nomad-parametric-autoscaler/types"
)

/*
Expand All @@ -18,11 +20,11 @@ type Ensembler interface {
// GetEnsembler matches name and returns an ensembler
func GetEnsembler(name string) (Ensembler, error) {
switch name {
case "Conservative":
case types.EnsembleConservative:
return ConservativeEnsembling{
name: name,
}, nil
case "Average":
case types.EnsembleAverage:
return AverageEnsembling{
name: name,
}, nil
Expand Down
10 changes: 6 additions & 4 deletions policy/ensemble_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,28 @@ package policy

import (
"testing"

"github.com/datagovsg/nomad-parametric-autoscaler/types"
)

func TestEnsemblerFetching(t *testing.T) {
es, err := GetEnsembler("Conservative")
es, err := GetEnsembler(types.EnsembleConservative)

if err != nil {
t.Errorf("Expected keyword `Conservative` to be valid")
}

if es != (ConservativeEnsembling{name: "Conservative"}) {
if es != (ConservativeEnsembling{name: types.EnsembleConservative}) {
t.Errorf("ConservativeEnsembling struct to be fetched")
}

es, err = GetEnsembler("Average")
es, err = GetEnsembler(types.EnsembleAverage)

if err != nil {
t.Errorf("Expected keyword `Average` to be valid")
}

if es != (AverageEnsembling{name: "Average"}) {
if es != (AverageEnsembling{name: types.EnsembleAverage}) {
t.Errorf("AverageEnsembling struct to be fetched")
}

Expand Down
9 changes: 5 additions & 4 deletions policy/subpolicy/subpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/datagovsg/nomad-parametric-autoscaler/resources"
"github.com/datagovsg/nomad-parametric-autoscaler/types"
)

// ScalingMagnitude needs a way better name
Expand All @@ -29,13 +30,13 @@ type GenericSubPolicy struct {
// CreateSpecificSubpolicy checks name of GSP and creates the actual policy
func CreateSpecificSubpolicy(gsp GenericSubPolicy, mr []resources.Resource) (SubPolicy, error) {
switch gsp.Name {
case "CoreRatio":
case types.CoreRatio:
sp, err := NewCoreRatioSubpolicy(gsp.Name, mr, gsp.Metadata)
if err != nil {
return nil, err
}
return sp, nil
case "OfficeHour":
case types.OfficeHour:
sp, err := NewOfficeHourSubPolicy(gsp.Name, mr, gsp.Metadata)
if err != nil {
return nil, err
Expand All @@ -50,9 +51,9 @@ func CreateSpecificSubpolicy(gsp GenericSubPolicy, mr []resources.Resource) (Sub
// of scaling methods
func determineNewDesiredLevel(cur int, sm ScalingMagnitude) int {
switch sm.ChangeType {
case "multiply":
case types.ChangeTypeMultiply:
return int(float64(cur) * sm.ChangeValue)
case "until":
case types.ChangeTypeUntil:
return int(sm.ChangeValue)
default:
return cur
Expand Down
13 changes: 13 additions & 0 deletions types/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package types

const CoreRatio = "CoreRatio"
const OfficeHour = "OfficeHour"

const ChangeTypeMultiply = "multiply"
const ChangeTypeUntil = "until"

const EnsembleConservative = "Conservative"
const EnsembleAverage = "Average"

var SubpolicyList = []string{CoreRatio, OfficeHour}
var EnsemblerList = []string{EnsembleConservative, EnsembleAverage}
35 changes: 22 additions & 13 deletions ui/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,30 @@ class App extends Component {
this.refreshState();
}

refreshState() {
const reqUrl = new URL(
async refreshState() {
const predefinedUrl = new URL(
"/predefined",
window.config.env.REACT_APP_NOPAS_ENDPOINT
);
const stateUrl = new URL(
"/state",
window.config.env.REACT_APP_NOPAS_ENDPOINT
);

axios
.get(reqUrl)
.then(response => {
const newState = serverToUIConversion(response.data);
newState && this.props.refreshState(newState);
})
.catch(function(error) {
alert(error);
});
let firstResponse = await axios.get(predefinedUrl);
if (firstResponse.err) {
alert(firstResponse.err);
return;
}

this.props.updatePossibleDefaultsList(firstResponse.data);
let secondResponse = await axios.get(stateUrl);
if (secondResponse.err) {
alert(secondResponse.err);
} else {
const newState = serverToUIConversion(secondResponse.data);
this.props.refreshState(newState);
}
}

sendUpdate() {
Expand Down Expand Up @@ -75,8 +84,8 @@ class App extends Component {
}

App.propTypes = {
refreshState: PropTypes.func,
state: PropTypes.object
refreshState: PropTypes.func.isRequired,
state: PropTypes.object.isRequired
};

export default App;
7 changes: 7 additions & 0 deletions ui/src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export const UPDATE_SUBPOLICY_RESOURCE = "UPDATE_SUBPOLICY_RESOURCE";
export const UPDATE_SP_META = "UPDATE_SP_META";
export const DELETE_SUBPOLICY = "DELETE_SUBPOLICY";

export const UPDATE_POSSIBLE_DEFAULTS_LIST = "UPDATE_POSSIBLE_DEFAULTS_LIST";

/*
* action creators
*/
Expand Down Expand Up @@ -101,3 +103,8 @@ export const updateMeta = change => ({
type: UPDATE_SP_META,
change: change
});

export const updatePossibleDefaultsList = change => ({
type: UPDATE_POSSIBLE_DEFAULTS_LIST,
change: change
});
11 changes: 9 additions & 2 deletions ui/src/components/PolicySummary.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import PropTypes from "prop-types";
import TextField from "@material-ui/core/TextField";
import { Paper, Card, CardContent } from "../../node_modules/@material-ui/core";

import MenuItem from "@material-ui/core/MenuItem";
import ResourceGroup from "../containers/ResourceGroup";
import SubpolicyGroup from "../containers/SubpolicyGroup";

Expand All @@ -26,12 +26,18 @@ const PolicySummary = props => {
<CardContent>
<TextField
required
select
id="standard-required"
label="Ensembler"
value={props.ensembler}
onChange={props.updateEnsembler}
margin="normal"
/>
>
{props.possibleEnsemblerList &&
props.possibleEnsemblerList.map(pe => (
<MenuItem value={pe}>{pe}</MenuItem>
))}
</TextField>
</CardContent>
</Card>
</Paper>
Expand All @@ -43,6 +49,7 @@ const PolicySummary = props => {

PolicySummary.propTypes = {
frequency: PropTypes.string.isRequired,
possibleEnsemblerList: PropTypes.arrayOf(PropTypes.string).isRequired,
updateCheckingFrequency: PropTypes.func.isRequired,
ensembler: PropTypes.string.isRequired,
updateEnsembler: PropTypes.func.isRequired
Expand Down
12 changes: 10 additions & 2 deletions ui/src/components/StatusSwitch.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@ class StatusSwitch extends React.Component {

handleChange(event) {
if (event.target.checked) {
axios.put(`${window.config.env.REACT_APP_NOPAS_ENDPOINT}/resume`);
const resumeUrl = new URL(
"/resume",
window.config.env.REACT_APP_NOPAS_ENDPOINT
);
axios.put(resumeUrl);
} else {
axios.put(`${window.config.env.REACT_APP_NOPAS_ENDPOINT}/pause`);
const pauseUrl = new URL(
"/pause",
window.config.env.REACT_APP_NOPAS_ENDPOINT
);
axios.put(pauseUrl);
}

this.setState({
Expand Down
13 changes: 10 additions & 3 deletions ui/src/components/Subpolicy.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import TextField from "@material-ui/core/TextField";
import { Card, CardContent, CardHeader } from "@material-ui/core";
import Fab from "@material-ui/core/Fab";
import DeleteIcon from "@material-ui/icons/Delete";

import MenuItem from "@material-ui/core/MenuItem";
import ManagedResources from "../containers/ManagedResources";

const Subpolicy = props => {
const { id, name, resources, metadata } = props;
const { id, name, resources, metadata, possibleSubpolicyList } = props;
const updateField = event => {
props.updateMeta({ id: id, value: event.target.value });
};
Expand All @@ -28,12 +28,18 @@ const Subpolicy = props => {
<CardContent>
<TextField
required
select
id="standard-required"
label="Subpolicy Name"
value={name}
onChange={renameSubpolicy}
margin="normal"
/>
>
{possibleSubpolicyList &&
possibleSubpolicyList.map(ps => (
<MenuItem value={ps}>{ps}</MenuItem>
))}
</TextField>
<ManagedResources id={id} resources={resources} />
<TextField
required
Expand Down Expand Up @@ -63,6 +69,7 @@ Subpolicy.propTypes = {
name: PropTypes.string.isRequired,
resources: PropTypes.arrayOf(PropTypes.string).isRequired,
metadata: PropTypes.string.isRequired,
possibleSubpolicyList: PropTypes.arrayOf(PropTypes.string).isRequired,
updateMeta: PropTypes.func.isRequired,
deleteSubpolicy: PropTypes.func.isRequired,
updateSubpolicyName: PropTypes.func.isRequired
Expand Down
4 changes: 4 additions & 0 deletions ui/src/containers/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { connect } from "react-redux";
import App from "../App";
import { updatePossibleDefaultsList } from "../actions";

const mapStateToProps = state => {
return {
Expand All @@ -11,6 +12,9 @@ const mapDispatchToProps = dispatch => {
return {
refreshState: event => {
dispatch({ type: "UPDATE_STATE", change: event });
},
updatePossibleDefaultsList: event => {
dispatch(updatePossibleDefaultsList(event));
}
};
};
Expand Down
3 changes: 2 additions & 1 deletion ui/src/containers/PolicySummary.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { updateCheckingFrequency, updateEnsembler } from "../actions";
const mapStateToProps = state => {
return {
frequency: state.policy.CheckingFreq,
ensembler: state.policy.Ensembler
ensembler: state.policy.Ensembler,
possibleEnsemblerList: state.defaultsList.ensemblers
};
};

Expand Down
3 changes: 2 additions & 1 deletion ui/src/containers/Subpolicy.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const mapStateToProps = (state, ownProps) => {
return {
name: sp.Name,
metadata: sp.Metadata,
resources: sp.ManagedResources
resources: sp.ManagedResources,
possibleSubpolicyList: state.defaultsList.subpolicies
};
};

Expand Down
23 changes: 19 additions & 4 deletions ui/src/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,18 @@ import {
UPDATE_SP_RESOURCE,
UPDATE_SUBPOLICY_RESOURCE,
UPDATE_SP_META,
DELETE_SUBPOLICY
DELETE_SUBPOLICY,
UPDATE_POSSIBLE_DEFAULTS_LIST
} from "../actions";

// TODO: change to fetch fn that gets from nopas backend
export const possibleDefaults = {
subpolicies: ["CoreRatio"],
ensemblers: ["Conservative"]
}

export const initialState = {
CheckingFreq: "1m",
Ensembler: "conservative",
Ensembler: "Conservative",
Resources: {
uuid1: {
Name: "Sample",
Expand Down Expand Up @@ -296,4 +301,14 @@ export const policy = (state = initialState, action) => {
}
};

export const rootReducer = combineReducers({ policy });
export const defaultsList = (state = possibleDefaults, action) => {
switch (action.type) {
case UPDATE_POSSIBLE_DEFAULTS_LIST:
return action.change;
default:
return state;
}
};


export const rootReducer = combineReducers({ policy, defaultsList });
5 changes: 1 addition & 4 deletions ui/tests/actions/actions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ import {
UPDATE_SUBPOLICY_NAME,
UPDATE_SUBPOLICY_RESOURCE,
UPDATE_SP_META,
DELETE_SUBPOLICY
} from "../../src/actions";

import {
DELETE_SUBPOLICY,
updateNomadParameters,
updateNumericNomadParameters,
updateEnsembler,
Expand Down
Loading

0 comments on commit d7fb784

Please sign in to comment.