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.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')

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
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,

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 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]

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