Merge "implement pod_manager controller for BP:multi-podmanager"

This commit is contained in:
Jenkins 2017-01-27 17:53:21 +00:00 committed by Gerrit Code Review
commit 7a52ec90d1
8 changed files with 440 additions and 7 deletions

View File

@ -23,10 +23,10 @@ from valence.api import app as flaskapp
import valence.api.root as api_root import valence.api.root as api_root
import valence.api.v1.flavors as v1_flavors import valence.api.v1.flavors as v1_flavors
import valence.api.v1.nodes as v1_nodes 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.storages as v1_storages
import valence.api.v1.systems as v1_systems import valence.api.v1.systems as v1_systems
import valence.api.v1.version as v1_version import valence.api.v1.version as v1_version
from valence.common import exception from valence.common import exception
from valence.common import utils from valence.common import utils
@ -59,7 +59,6 @@ api = ValenceService(app)
"""API V1.0 Operations""" """API V1.0 Operations"""
# API Root operation # API Root operation
api.add_resource(api_root.Root, '/', endpoint='root') 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, api.add_resource(v1_storages.Storages,
'/v1/storages/<string:storageid>', endpoint='storage') '/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 # Proxy to PODM
api.add_resource(api_root.PODMProxy, '/<path:url>', endpoint='podmproxy') api.add_resource(api_root.PODMProxy, '/<path:url>', endpoint='podmproxy')

View 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)

View 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"

View File

@ -86,8 +86,24 @@ class RedfishException(ValenceError):
self.detail = message_detail self.detail = message_detail
class NotFound(Exception): class NotFound(ValenceError):
status = http_client.NOT_FOUND
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, def _error(error_code, http_status, error_title, error_detail,

View 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

View File

@ -18,8 +18,10 @@ import logging
import os import os
import requests import requests
from requests import auth
from six.moves import http_client from six.moves import http_client
from valence.common import constants
from valence.common import exception from valence.common import exception
from valence.common import utils from valence.common import utils
from valence import config as cfg from valence import config as cfg
@ -106,6 +108,18 @@ def pods():
return json.dumps(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): def urls2list(url):
# This will extract the url values from @odata.id inside Members # This will extract the url values from @odata.id inside Members
resp = send_request(url) resp = send_request(url)
@ -272,7 +286,7 @@ def get_systembyid(systemid):
def get_nodebyid(nodeid): def get_nodebyid(nodeid):
node = nodes_list({"Id": nodeid}) node = nodes_list({"Id": nodeid})
if not node: if not node:
raise exception.NotFound() raise exception.NotFound(detail='Node: %s not found' % nodeid)
return node[0] return node[0]

View 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)

View File

@ -13,9 +13,12 @@
from unittest import TestCase from unittest import TestCase
import mock import mock
import requests
from requests import auth
from requests.compat import urljoin from requests.compat import urljoin
from six.moves import http_client from six.moves import http_client
from valence.common import constants
from valence.common import exception from valence.common import exception
from valence import config as cfg from valence import config as cfg
from valence.redfish import redfish from valence.redfish import redfish
@ -193,9 +196,9 @@ class TestRedfish(TestCase):
mock_make_response): mock_make_response):
mock_get_url.return_value = '/redfish/v1/Nodes' mock_get_url.return_value = '/redfish/v1/Nodes'
delete_result = fakes.fake_delete_composednode_ok() 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) http_client.NO_CONTENT)
mock_request.return_value = fake_delete_resopnse mock_request.return_value = fake_delete_response
redfish.delete_composednode(101) redfish.delete_composednode(101)
mock_request.assert_called_with('/redfish/v1/Nodes/101', 'DELETE') mock_request.assert_called_with('/redfish/v1/Nodes/101', 'DELETE')
expected_content = { expected_content = {
@ -218,3 +221,42 @@ class TestRedfish(TestCase):
self.assertRaises(exception.RedfishException, self.assertRaises(exception.RedfishException,
redfish.delete_composednode, 101) redfish.delete_composednode, 101)
self.assertFalse(mock_make_response.called) 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'))