Merge "implement pod_manager controller for BP:multi-podmanager"
This commit is contained in:
commit
7a52ec90d1
@ -23,10 +23,10 @@ from valence.api import app as flaskapp
|
||||
import valence.api.root as api_root
|
||||
import valence.api.v1.flavors as v1_flavors
|
||||
import valence.api.v1.nodes as v1_nodes
|
||||
import valence.api.v1.podmanagers as v1_podmanagers
|
||||
import valence.api.v1.storages as v1_storages
|
||||
import valence.api.v1.systems as v1_systems
|
||||
import valence.api.v1.version as v1_version
|
||||
|
||||
from valence.common import exception
|
||||
from valence.common import utils
|
||||
|
||||
@ -59,7 +59,6 @@ api = ValenceService(app)
|
||||
|
||||
"""API V1.0 Operations"""
|
||||
|
||||
|
||||
# API Root operation
|
||||
api.add_resource(api_root.Root, '/', endpoint='root')
|
||||
|
||||
@ -88,5 +87,11 @@ api.add_resource(v1_storages.StoragesList, '/v1/storages', endpoint='storages')
|
||||
api.add_resource(v1_storages.Storages,
|
||||
'/v1/storages/<string:storageid>', endpoint='storage')
|
||||
|
||||
# PodManager(s) operations
|
||||
api.add_resource(v1_podmanagers.PodManager,
|
||||
'/v1/pod_managers/<string:podm_uuid>', endpoint='podmanager')
|
||||
api.add_resource(v1_podmanagers.PodManagersList,
|
||||
'/v1/pod_managers', endpoint='podmanagers')
|
||||
|
||||
# Proxy to PODM
|
||||
api.add_resource(api_root.PODMProxy, '/<path:url>', endpoint='podmproxy')
|
||||
|
54
valence/api/v1/podmanagers.py
Normal file
54
valence/api/v1/podmanagers.py
Normal file
@ -0,0 +1,54 @@
|
||||
# Copyright (c) 2016 Intel, 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 logging
|
||||
|
||||
import flask
|
||||
import flask_restful
|
||||
from six.moves import http_client
|
||||
|
||||
from valence.common import exception
|
||||
from valence.common import utils
|
||||
from valence.controller import podmanagers
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PodManagersList(flask_restful.Resource):
|
||||
|
||||
def get(self):
|
||||
return utils.make_response(http_client.OK, podmanagers.get_podm_list())
|
||||
|
||||
def post(self):
|
||||
values = flask.request.get_json()
|
||||
return utils.make_response(http_client.OK,
|
||||
podmanagers.create_podm(values))
|
||||
|
||||
|
||||
class PodManager(flask_restful.Resource):
|
||||
|
||||
def get(self, podm_uuid):
|
||||
return utils.make_response(http_client.OK,
|
||||
podmanagers.get_podm_by_uuid(podm_uuid))
|
||||
|
||||
def patch(self, podm_uuid):
|
||||
values = flask.request.form.to_dict()
|
||||
return utils.make_response(http_client.OK,
|
||||
podmanagers.update_podm(podm_uuid, values))
|
||||
|
||||
def delete(self, podm_uuid):
|
||||
podmanagers.delete_podm_by_uuid(podm_uuid)
|
||||
resp_dict = exception.confirmation(confirm_detail="DELETED")
|
||||
return utils.make_response(http_client.OK, resp_dict)
|
19
valence/common/constants.py
Normal file
19
valence/common/constants.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2016 Intel, 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.
|
||||
|
||||
PODM_AUTH_BASIC_TYPE = 'basic'
|
||||
|
||||
PODM_STATUS_ONLINE = 'Online'
|
||||
PODM_STATUS_OFFLINE = 'Offline'
|
||||
PODM_STATUS_UNKNOWN = "Unknown"
|
@ -86,8 +86,24 @@ class RedfishException(ValenceError):
|
||||
self.detail = message_detail
|
||||
|
||||
|
||||
class NotFound(Exception):
|
||||
status = http_client.NOT_FOUND
|
||||
class NotFound(ValenceError):
|
||||
|
||||
def __init__(self, detail='resource not found', request_id=None):
|
||||
self.request_id = request_id
|
||||
self.status_code = http_client.NOT_FOUND
|
||||
self.code = http_client.NOT_FOUND
|
||||
self.title = "resource not found"
|
||||
self.detail = detail
|
||||
|
||||
|
||||
class BadRequest(ValenceError):
|
||||
|
||||
def __init__(self, detail='bad request', request_id=None):
|
||||
self.request_id = request_id
|
||||
self.status_code = http_client.BAD_REQUEST
|
||||
self.code = http_client.BAD_REQUEST
|
||||
self.title = "bad request"
|
||||
self.detail = detail
|
||||
|
||||
|
||||
def _error(error_code, http_status, error_title, error_detail,
|
||||
|
118
valence/controller/podmanagers.py
Normal file
118
valence/controller/podmanagers.py
Normal file
@ -0,0 +1,118 @@
|
||||
# Copyright (c) 2016 Intel, 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 logging
|
||||
|
||||
from valence.common import constants
|
||||
from valence.common import exception
|
||||
from valence.db import api as db_api
|
||||
from valence.redfish import redfish
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _check_creation(values):
|
||||
"""Checking args when creating a new pod manager
|
||||
|
||||
authentication: should follow the format
|
||||
name: can not be duplicated
|
||||
url: can not be duplicated
|
||||
|
||||
:values: The properties for this new pod manager.
|
||||
:returns: improved values that could be inserted to db
|
||||
"""
|
||||
|
||||
if not ('name' in values and
|
||||
'url' in values and
|
||||
'authentication' in values):
|
||||
raise exception.BadRequest(detail="Incomplete parameters")
|
||||
# check authentication's format and content
|
||||
try:
|
||||
if not (values['authentication'][0]['type'] and
|
||||
values['authentication'][0]['auth_items']):
|
||||
LOG.error("invalid authentication when creating podmanager")
|
||||
raise exception.BadRequest(detail="invalid "
|
||||
"authentication properties")
|
||||
except KeyError:
|
||||
LOG.error("Incomplete parameters when creating podmanager")
|
||||
raise exception.BadRequest(detail="invalid "
|
||||
"authentication properties")
|
||||
|
||||
pod_manager_list = get_podm_list()
|
||||
names = [podm['name'] for podm in pod_manager_list]
|
||||
urls = [podm['url'] for podm in pod_manager_list]
|
||||
if values['name'] in names or values['url'] in urls:
|
||||
raise exception.BadRequest('duplicated name or url !')
|
||||
|
||||
# input status
|
||||
values['status'] = get_podm_status(values['url'], values['authentication'])
|
||||
|
||||
return values
|
||||
|
||||
|
||||
def _check_updation(values):
|
||||
"""Checking args when updating a exist pod manager
|
||||
|
||||
:values: The properties of pod manager to be updated
|
||||
:returns: improved values that could be updated
|
||||
"""
|
||||
|
||||
# uuid, url can not be modified
|
||||
if 'uuid' in values:
|
||||
values.pop('uuid')
|
||||
if 'url' in values:
|
||||
values.pop('url')
|
||||
return values
|
||||
|
||||
|
||||
def get_podm_list():
|
||||
return map(lambda x: x.as_dict(), db_api.Connection.list_podmanager())
|
||||
|
||||
|
||||
def get_podm_by_uuid(uuid):
|
||||
return db_api.Connection.get_podmanager_by_uuid(uuid).as_dict()
|
||||
|
||||
|
||||
def create_podm(values):
|
||||
values = _check_creation(values)
|
||||
return db_api.Connection.create_podmanager(values).as_dict()
|
||||
|
||||
|
||||
def update_podm(uuid, values):
|
||||
values = _check_updation(values)
|
||||
return db_api.Connection.update_podmanager(uuid, values).as_dict()
|
||||
|
||||
|
||||
def delete_podm_by_uuid(uuid):
|
||||
# TODO(hubian) this need to break the links between podm and its Nodes
|
||||
return db_api.Connection.delete_podmanager(uuid)
|
||||
|
||||
|
||||
def get_podm_status(url, authentication):
|
||||
"""get pod manager running status by its url and auth
|
||||
|
||||
:param url: The url of pod manager.
|
||||
:param authentication: array, The auth(s) info of pod manager.
|
||||
|
||||
:returns: status of the pod manager
|
||||
"""
|
||||
for auth in authentication:
|
||||
# TODO(Hubian) Only consider and support basic auth type here.
|
||||
# After decided to support other auth type this would be improved.
|
||||
if auth['type'] == constants.PODM_AUTH_BASIC_TYPE:
|
||||
username = auth['auth_items']['username']
|
||||
password = auth['auth_items']['password']
|
||||
return redfish.pod_status(url, username, password)
|
||||
|
||||
return constants.PODM_STATUS_UNKNOWN
|
@ -18,8 +18,10 @@ import logging
|
||||
import os
|
||||
|
||||
import requests
|
||||
from requests import auth
|
||||
from six.moves import http_client
|
||||
|
||||
from valence.common import constants
|
||||
from valence.common import exception
|
||||
from valence.common import utils
|
||||
from valence import config as cfg
|
||||
@ -106,6 +108,18 @@ def pods():
|
||||
return json.dumps(pods)
|
||||
|
||||
|
||||
def pod_status(pod_url, username, password):
|
||||
try:
|
||||
resp = requests.get(pod_url,
|
||||
auth=auth.HTTPBasicAuth(username, password))
|
||||
if resp.status_code == http_client.OK:
|
||||
return constants.PODM_STATUS_ONLINE
|
||||
else:
|
||||
return constants.PODM_STATUS_OFFLINE
|
||||
except requests.RequestException:
|
||||
return constants.PODM_STATUS_OFFLINE
|
||||
|
||||
|
||||
def urls2list(url):
|
||||
# This will extract the url values from @odata.id inside Members
|
||||
resp = send_request(url)
|
||||
@ -272,7 +286,7 @@ def get_systembyid(systemid):
|
||||
def get_nodebyid(nodeid):
|
||||
node = nodes_list({"Id": nodeid})
|
||||
if not node:
|
||||
raise exception.NotFound()
|
||||
raise exception.NotFound(detail='Node: %s not found' % nodeid)
|
||||
return node[0]
|
||||
|
||||
|
||||
|
165
valence/tests/unit/controller/test_podmanagers.py
Normal file
165
valence/tests/unit/controller/test_podmanagers.py
Normal file
@ -0,0 +1,165 @@
|
||||
# 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 copy
|
||||
import mock
|
||||
import unittest
|
||||
|
||||
from valence.common import constants
|
||||
from valence.common.exception import BadRequest
|
||||
from valence.controller import podmanagers
|
||||
|
||||
|
||||
class TestPodManagers(unittest.TestCase):
|
||||
|
||||
@mock.patch('valence.controller.podmanagers.get_podm_list')
|
||||
@mock.patch('valence.controller.podmanagers.get_podm_status')
|
||||
def test_check_creation(self, mock_get_podm_status, mock_get_podm_list):
|
||||
mock_get_podm_list.return_value = [
|
||||
{"name": "test1",
|
||||
"url": "https://10.0.0.1"},
|
||||
{"name": "test2",
|
||||
"url": "https://10.0.0.2"}
|
||||
]
|
||||
mock_get_podm_status.return_value = constants.PODM_STATUS_ONLINE
|
||||
|
||||
values = {"name": "podm_name",
|
||||
"url": "https://10.240.212.123",
|
||||
"authentication": [
|
||||
{
|
||||
"type": "basic",
|
||||
"auth_items": {
|
||||
"username": "xxxxxxx",
|
||||
"password": "xxxxxxx"
|
||||
}
|
||||
}
|
||||
]}
|
||||
|
||||
result_values = copy.deepcopy(values)
|
||||
result_values['status'] = constants.PODM_STATUS_ONLINE
|
||||
|
||||
self.assertEqual(podmanagers._check_creation(values), result_values)
|
||||
mock_get_podm_status.assert_called_once_with(values['url'],
|
||||
values['authentication'])
|
||||
mock_get_podm_list.assert_called_once_with()
|
||||
|
||||
def test_check_creation_incomplete_parameters(self):
|
||||
incomplete_values = {
|
||||
'name': 'name',
|
||||
'url': 'url'
|
||||
}
|
||||
self.assertRaises(BadRequest,
|
||||
podmanagers._check_creation,
|
||||
incomplete_values)
|
||||
|
||||
def test_check_creation_invalid_authentication(self):
|
||||
invalid_authentication_values = {
|
||||
"name": "podm_name",
|
||||
"url": "https://10.0.0.2",
|
||||
'authentication': {
|
||||
"username": "username",
|
||||
"password": "password"
|
||||
}
|
||||
}
|
||||
self.assertRaises(BadRequest,
|
||||
podmanagers._check_creation,
|
||||
invalid_authentication_values)
|
||||
|
||||
@mock.patch('valence.controller.podmanagers.get_podm_list')
|
||||
def test_check_creation_duplicate_Exception(self, mock_get_podm_list):
|
||||
mock_get_podm_list.return_value = [
|
||||
{"name": "test1",
|
||||
"url": "https://10.0.0.1",
|
||||
'authentication': "authentication"},
|
||||
{"name": "test2",
|
||||
"url": "https://10.0.0.2",
|
||||
'authentication': "authentication"
|
||||
}
|
||||
]
|
||||
|
||||
name_duplicate_values = {"name": "test1",
|
||||
"url": "https://10.240.212.123",
|
||||
'authentication': [
|
||||
{
|
||||
"type": "basic",
|
||||
"auth_items": {
|
||||
"username": "username",
|
||||
"password": "password"
|
||||
}
|
||||
}
|
||||
]}
|
||||
url_duplicate_values = {"name": "podm_name",
|
||||
"url": "https://10.0.0.2",
|
||||
'authentication': [
|
||||
{
|
||||
"type": "basic",
|
||||
"auth_items": {
|
||||
"username": "username",
|
||||
"password": "password"
|
||||
}
|
||||
}
|
||||
]}
|
||||
|
||||
self.assertRaises(BadRequest,
|
||||
podmanagers._check_creation,
|
||||
name_duplicate_values)
|
||||
self.assertRaises(BadRequest,
|
||||
podmanagers._check_creation,
|
||||
url_duplicate_values)
|
||||
self.assertEqual(mock_get_podm_list.call_count, 2)
|
||||
|
||||
def test_check_updation_ignore_url_uuid(self):
|
||||
values = {
|
||||
"uuid": "uuid",
|
||||
"url": "url",
|
||||
"name": "name"
|
||||
}
|
||||
result_values = copy.deepcopy(values)
|
||||
result_values.pop('url')
|
||||
result_values.pop('uuid')
|
||||
|
||||
self.assertEqual(podmanagers._check_updation(values), result_values)
|
||||
|
||||
@mock.patch('valence.redfish.redfish.pod_status')
|
||||
def test_get_podm_status(self, mock_pod_status):
|
||||
mock_pod_status.return_value = constants.PODM_STATUS_ONLINE
|
||||
authentication = [
|
||||
{
|
||||
"type": "basic",
|
||||
"auth_items": {
|
||||
"username": "username",
|
||||
"password": "password"
|
||||
}
|
||||
}
|
||||
]
|
||||
self.assertEqual(podmanagers.get_podm_status('url', authentication),
|
||||
constants.PODM_STATUS_ONLINE)
|
||||
mock_pod_status.asset_called_once_with('url', "username", "password")
|
||||
|
||||
def test_get_podm_status_unknown(self):
|
||||
"""not basic type authentication podm status set value to be unknown"""
|
||||
authentication = [
|
||||
{
|
||||
"type": "CertificateAuthority",
|
||||
"auth_items": {
|
||||
"public_key": "xxxxxxx"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "DynamicCode",
|
||||
"auth_items": {
|
||||
"code": "xxxxxxx"
|
||||
}
|
||||
}
|
||||
]
|
||||
self.assertEqual(podmanagers.get_podm_status('url', authentication),
|
||||
constants.PODM_STATUS_UNKNOWN)
|
@ -13,9 +13,12 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import mock
|
||||
import requests
|
||||
from requests import auth
|
||||
from requests.compat import urljoin
|
||||
from six.moves import http_client
|
||||
|
||||
from valence.common import constants
|
||||
from valence.common import exception
|
||||
from valence import config as cfg
|
||||
from valence.redfish import redfish
|
||||
@ -193,9 +196,9 @@ class TestRedfish(TestCase):
|
||||
mock_make_response):
|
||||
mock_get_url.return_value = '/redfish/v1/Nodes'
|
||||
delete_result = fakes.fake_delete_composednode_ok()
|
||||
fake_delete_resopnse = fakes.mock_request_get(delete_result,
|
||||
fake_delete_response = fakes.mock_request_get(delete_result,
|
||||
http_client.NO_CONTENT)
|
||||
mock_request.return_value = fake_delete_resopnse
|
||||
mock_request.return_value = fake_delete_response
|
||||
redfish.delete_composednode(101)
|
||||
mock_request.assert_called_with('/redfish/v1/Nodes/101', 'DELETE')
|
||||
expected_content = {
|
||||
@ -218,3 +221,42 @@ class TestRedfish(TestCase):
|
||||
self.assertRaises(exception.RedfishException,
|
||||
redfish.delete_composednode, 101)
|
||||
self.assertFalse(mock_make_response.called)
|
||||
|
||||
@mock.patch('requests.get')
|
||||
def test_get_podm_status_Offline_by_wrong_auth(self, mock_get):
|
||||
fake_resp = fakes.mock_request_get({}, 401)
|
||||
mock_get.return_value = fake_resp
|
||||
self.assertEqual(redfish.pod_status('url', 'username', 'password'),
|
||||
constants.PODM_STATUS_OFFLINE)
|
||||
mock_get.asset_called_once_with('url',
|
||||
auth=auth.HTTPBasicAuth('username',
|
||||
'password'))
|
||||
|
||||
@mock.patch('requests.get')
|
||||
def test_get_podm_status_Offline_by_http_exception(self, mock_get):
|
||||
mock_get.side_effect = requests.ConnectionError
|
||||
self.assertEqual(redfish.pod_status('url', 'username', 'password'),
|
||||
constants.PODM_STATUS_OFFLINE)
|
||||
mock_get.asset_called_once_with('url',
|
||||
auth=auth.HTTPBasicAuth('username',
|
||||
'password'))
|
||||
# SSL Error
|
||||
mock_get.side_effect = requests.exceptions.SSLError
|
||||
self.assertEqual(redfish.pod_status('url', 'username', 'password'),
|
||||
constants.PODM_STATUS_OFFLINE)
|
||||
self.assertEqual(mock_get.call_count, 2)
|
||||
# Timeout
|
||||
mock_get.side_effect = requests.Timeout
|
||||
self.assertEqual(redfish.pod_status('url', 'username', 'password'),
|
||||
constants.PODM_STATUS_OFFLINE)
|
||||
self.assertEqual(mock_get.call_count, 3)
|
||||
|
||||
@mock.patch('requests.get')
|
||||
def test_get_podm_status_Online(self, mock_get):
|
||||
fake_resp = fakes.mock_request_get({}, http_client.OK)
|
||||
mock_get.return_value = fake_resp
|
||||
self.assertEqual(redfish.pod_status('url', 'username', 'password'),
|
||||
constants.PODM_STATUS_ONLINE)
|
||||
mock_get.asset_called_once_with('url',
|
||||
auth=auth.HTTPBasicAuth('username',
|
||||
'password'))
|
||||
|
Loading…
x
Reference in New Issue
Block a user