Add flavor fetching via Nova API

This patch support for fetching Nova flavors via its REST API.  We're
including all of the necessary pieces including reducer, selector, etc.

Change-Id: I9535fa8254039ef564d62b653934267b2380ce7c
Depends-On: Ic6df58947bb2cb1e183b5c88ed8d287191e5ee07
This commit is contained in:
Honza Pokorny 2018-02-09 12:05:05 -04:00 committed by Jiri Tomasek
parent 1b1c0b8471
commit 3b91169acd
9 changed files with 272 additions and 1 deletions

View File

@ -0,0 +1,59 @@
/**
* Copyright 2018 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { normalize } from 'normalizr';
import { flavorSchema } from '../normalizrSchemas/flavors';
import { handleErrors } from './ErrorActions';
import NovaApiService from '../services/NovaApiService';
import FlavorsConstants from '../constants/FlavorsConstants';
export default {
fetchFlavorsPending() {
return {
type: FlavorsConstants.FETCH_FLAVORS_PENDING
};
},
fetchFlavorsSuccess(flavors) {
return {
type: FlavorsConstants.FETCH_FLAVORS_SUCCESS,
payload: flavors
};
},
fetchFlavorsFailed() {
return {
type: FlavorsConstants.FETCH_FLAVORS_FAILED
};
},
fetchFlavors() {
return dispatch => {
dispatch(this.fetchFlavorsPending());
dispatch(NovaApiService.getFlavors())
.then(response => {
const flavors = normalize(response.flavors, [flavorSchema]).entities
.flavors;
dispatch(this.fetchFlavorsSuccess(flavors));
})
.catch(error => {
dispatch(handleErrors(error, 'Flavors could not be loaded.'));
dispatch(this.fetchFlavorsFailed());
});
};
}
};

View File

@ -0,0 +1,23 @@
/**
* Copyright 2018 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import keyMirror from 'keymirror';
export default keyMirror({
FETCH_FLAVORS_PENDING: null,
FETCH_FLAVORS_FAILED: null,
FETCH_FLAVORS_SUCCESS: null
});

View File

@ -0,0 +1,28 @@
/**
* Copyright 2018 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { Map, Record } from 'immutable';
export const FlavorsState = Record({
isFetching: false,
isLoaded: false,
flavors: Map()
});
export const Flavor = Record({
name: undefined,
id: undefined
});

View File

@ -0,0 +1,23 @@
/**
* Copyright 2018 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { schema } from 'normalizr';
export const flavorSchema = new schema.Entity(
'flavors',
{},
{ idAttribute: 'id' }
);

View File

@ -20,6 +20,7 @@ import { reducer as formReducer } from 'redux-form';
import appConfig from './appConfig';
import environmentConfigurationReducer from './environmentConfigurationReducer';
import filtersReducer from './filtersReducer';
import flavorsReducer from './flavorsReducer';
import i18nReducer from './i18nReducer';
import loggerReducer from './loggerReducer';
import loginReducer from './loginReducer';
@ -39,6 +40,7 @@ const appReducer = combineReducers({
environmentConfiguration: environmentConfigurationReducer,
executions: workflowExecutionsReducer,
filters: filtersReducer,
flavors: flavorsReducer,
i18n: i18nReducer,
logger: loggerReducer,
login: loginReducer,

View File

@ -0,0 +1,43 @@
/**
* Copyright 2018 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { fromJS } from 'immutable';
import FlavorsConstants from '../constants/FlavorsConstants';
import { FlavorsState, Flavor } from '../immutableRecords/flavors';
const initialState = new FlavorsState();
export default function flavorsReducer(state = initialState, action) {
switch (action.type) {
case FlavorsConstants.FETCH_FLAVORS_PENDING:
return state.set('isFetching', true);
case FlavorsConstants.FETCH_FLAVORS_FAILED:
return state.set('isFetching', false);
case FlavorsConstants.FETCH_FLAVORS_SUCCESS: {
const flavors = fromJS(action.payload).map(flavor => new Flavor(flavor));
return state
.set('flavors', flavors)
.set('isLoaded', true)
.set('isFetching', false);
}
default:
return state;
}
}

View File

@ -0,0 +1,23 @@
/**
* Copyright 2018 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { createSelector } from 'reselect';
const flavors = state => state.flavors;
export const getFlavors = createSelector([flavors], flavors =>
flavors.flavors.sortBy(f => f.name)
);

View File

@ -0,0 +1,63 @@
/**
* Copyright 2018 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import axios from 'axios';
import when from 'when';
import { AuthenticationError, NovaApiError, ConnectionError } from './errors';
import { getAuthTokenId, getServiceUrl } from '../selectors/auth';
class NovaApiService {
defaultRequest(path, additionalAttributes) {
return (dispatch, getState) =>
axios(
Object.assign(
{
baseURL: getServiceUrl(getState(), 'nova'),
url: path,
method: 'GET',
headers: {
'X-Auth-Token': getAuthTokenId(getState())
}
},
additionalAttributes
)
);
}
getFlavors() {
return dispatch =>
dispatch(this.defaultRequest('/flavors'))
.then(response => response.data)
.catch(handleErrors);
}
}
const handleErrors = e => {
if (e.response && e.response.status === 401) {
return when.reject(new AuthenticationError(e));
} else if (e.response) {
return when.reject(new NovaApiError(e));
} else if (e.request) {
return when.reject(
new ConnectionError('Connection to Nova API could not be established', e)
);
} else {
return when.reject(e);
}
};
export default new NovaApiService();

View File

@ -84,6 +84,12 @@ class KeystoneApiError extends BaseAxiosError {
}
}
class NovaApiError extends BaseAxiosError {
constructor(e) {
super('NovaApiError', e.response.data.error.message, e);
}
}
export {
BaseAxiosError,
AuthenticationError,
@ -94,5 +100,6 @@ export {
IronicApiError,
IronicInspectorApiError,
HeatApiError,
KeystoneApiError
KeystoneApiError,
NovaApiError
};