Merge "Use node uuid instead of index for /node(s) api"
This commit is contained in:
commit
01569d5974
@ -66,8 +66,10 @@ api.add_resource(api_root.Root, '/', endpoint='root')
|
||||
api.add_resource(v1_version.V1, '/v1', endpoint='v1')
|
||||
|
||||
# Node(s) operations
|
||||
api.add_resource(v1_nodes.NodesList, '/v1/nodes', endpoint='nodes')
|
||||
api.add_resource(v1_nodes.Nodes, '/v1/nodes/<string:nodeid>', endpoint='node')
|
||||
api.add_resource(v1_nodes.Nodes, '/v1/nodes', endpoint='nodes')
|
||||
api.add_resource(v1_nodes.Node,
|
||||
'/v1/nodes/<string:node_uuid>',
|
||||
endpoint='node')
|
||||
api.add_resource(v1_nodes.NodesStorage,
|
||||
'/v1/nodes/<string:nodeid>/storages',
|
||||
endpoint='nodes_storages')
|
||||
|
@ -12,39 +12,36 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from flask import request
|
||||
from flask_restful import abort
|
||||
from flask_restful import Resource
|
||||
from six.moves import http_client
|
||||
|
||||
from valence.common import utils
|
||||
from valence.redfish import redfish
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NodesList(Resource):
|
||||
|
||||
def get(self):
|
||||
return utils.make_response(http_client.OK,
|
||||
redfish.list_nodes())
|
||||
|
||||
def post(self):
|
||||
return utils.make_response(
|
||||
http_client.OK, redfish.compose_node(request.get_json()))
|
||||
from valence.controller import nodes
|
||||
|
||||
|
||||
class Nodes(Resource):
|
||||
|
||||
def get(self, nodeid):
|
||||
return utils.make_response(http_client.OK,
|
||||
redfish.get_node_by_id(nodeid))
|
||||
def get(self):
|
||||
return utils.make_response(
|
||||
http_client.OK, nodes.Node.list_composed_nodes())
|
||||
|
||||
def delete(self, nodeid):
|
||||
return utils.make_response(http_client.OK,
|
||||
redfish.delete_composednode(nodeid))
|
||||
def post(self):
|
||||
return utils.make_response(
|
||||
http_client.OK, nodes.Node.compose_node(request.get_json()))
|
||||
|
||||
|
||||
class Node(Resource):
|
||||
|
||||
def get(self, node_uuid):
|
||||
return utils.make_response(
|
||||
http_client.OK,
|
||||
nodes.Node.get_composed_node_by_uuid(node_uuid))
|
||||
|
||||
def delete(self, node_uuid):
|
||||
return utils.make_response(
|
||||
http_client.OK, nodes.Node.delete_composed_node(node_uuid))
|
||||
|
||||
|
||||
class NodesStorage(Resource):
|
||||
|
@ -21,6 +21,7 @@
|
||||
import logging
|
||||
|
||||
import flask
|
||||
from oslo_utils import uuidutils
|
||||
import six
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -106,3 +107,8 @@ def make_response(status_code, content="", headers=None):
|
||||
raise ValueError("Response headers should be dict object.")
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def generate_uuid():
|
||||
"""Generate uniform format uuid"""
|
||||
return uuidutils.generate_uuid()
|
||||
|
99
valence/controller/nodes.py
Normal file
99
valence/controller/nodes.py
Normal file
@ -0,0 +1,99 @@
|
||||
# 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 six
|
||||
|
||||
from valence.common import utils
|
||||
from valence.db import api as db_api
|
||||
from valence.redfish import redfish
|
||||
|
||||
|
||||
class Node(object):
|
||||
|
||||
@staticmethod
|
||||
def _show_node_brief_info(node_info):
|
||||
return {key: node_info[key] for key in six.iterkeys(node_info)
|
||||
if key in ["uuid", "name", "links"]}
|
||||
|
||||
@classmethod
|
||||
def compose_node(cls, request_body):
|
||||
"""Compose new node
|
||||
|
||||
param request_body: parameter for node composition
|
||||
return: brief info of this new composed node
|
||||
"""
|
||||
|
||||
# Call redfish to compose new node
|
||||
composed_node = redfish.compose_node(request_body)
|
||||
|
||||
composed_node["uuid"] = utils.generate_uuid()
|
||||
|
||||
# Only store the minimum set of composed node info into backend db,
|
||||
# since other fields like power status may be changed and valence is
|
||||
# not aware.
|
||||
node_db = {"uuid": composed_node["uuid"],
|
||||
"name": composed_node["name"],
|
||||
"index": composed_node["index"],
|
||||
"links": composed_node["links"]}
|
||||
db_api.Connection.create_composed_node(node_db)
|
||||
|
||||
return cls._show_node_brief_info(composed_node)
|
||||
|
||||
@classmethod
|
||||
def get_composed_node_by_uuid(cls, node_uuid):
|
||||
"""Get composed node details
|
||||
|
||||
Get the detail of specific composed node. In some cases db data may be
|
||||
inconsistent with podm side, like user directly operate podm, not
|
||||
through valence api. So compare it with node info from redfish, and
|
||||
update db if it's inconsistent.
|
||||
|
||||
param node_uuid: uuid of composed node
|
||||
return: detail of this composed node
|
||||
"""
|
||||
|
||||
node_db = db_api.Connection.get_composed_node_by_uuid(node_uuid)\
|
||||
.as_dict()
|
||||
node_hw = redfish.get_node_by_id(node_db["index"])
|
||||
|
||||
# Add those fields of composed node from db
|
||||
node_hw.update(node_db)
|
||||
|
||||
return node_hw
|
||||
|
||||
@classmethod
|
||||
def delete_composed_node(cls, node_uuid):
|
||||
"""Delete a composed node
|
||||
|
||||
param node_uuid: uuid of composed node
|
||||
return: message of this deletion
|
||||
"""
|
||||
|
||||
# Get node detail from db, and map node uuid to index
|
||||
index = db_api.Connection.get_composed_node_by_uuid(node_uuid).index
|
||||
|
||||
# Call redfish to delete node, and delete corresponding entry in db
|
||||
message = redfish.delete_composed_node(index)
|
||||
db_api.Connection.delete_composed_node(node_uuid)
|
||||
|
||||
return message
|
||||
|
||||
@classmethod
|
||||
def list_composed_nodes(cls):
|
||||
"""List all composed node
|
||||
|
||||
return: brief info of all composed node
|
||||
"""
|
||||
return [cls._show_node_brief_info(node_info.as_dict())
|
||||
for node_info in db_api.Connection.list_composed_nodes()]
|
@ -115,3 +115,47 @@ class Connection(object):
|
||||
:returns: A list of all flavors.
|
||||
"""
|
||||
return cls.dbdriver.list_flavors()
|
||||
|
||||
@classmethod
|
||||
def create_composed_node(cls, values):
|
||||
"""Create a new composed node.
|
||||
|
||||
:values: The properties for this new composed node.
|
||||
:returns: A composed node created.
|
||||
"""
|
||||
return cls.dbdriver.create_composed_node(values)
|
||||
|
||||
@classmethod
|
||||
def get_composed_node_by_uuid(cls, composed_node_uuid):
|
||||
"""Get specific composed node by its uuid
|
||||
|
||||
:param composed_node_uuid: The uuid of composed node.
|
||||
:returns: A composed node with this uuid.
|
||||
"""
|
||||
return cls.dbdriver.get_composed_node_by_uuid(composed_node_uuid)
|
||||
|
||||
@classmethod
|
||||
def delete_composed_node(cls, composed_node_uuid):
|
||||
"""Delete specific composed node by its uuid
|
||||
|
||||
:param composed_node_uuid: The uuid of composed node.
|
||||
"""
|
||||
cls.dbdriver.delete_composed_node(composed_node_uuid)
|
||||
|
||||
@classmethod
|
||||
def update_composed_node(cls, composed_node_uuid, values):
|
||||
"""Update properties of a composed node.
|
||||
|
||||
:param composed_node_uuid: The uuid of composed node.
|
||||
:values: The properties to be updated.
|
||||
:returns: A composed node model after updated.
|
||||
"""
|
||||
return cls.dbdriver.update_composed_node(composed_node_uuid, values)
|
||||
|
||||
@classmethod
|
||||
def list_composed_nodes(cls):
|
||||
"""Get a list of all composed nodes.
|
||||
|
||||
:returns: A list of all composed node.
|
||||
"""
|
||||
return cls.dbdriver.list_composed_nodes()
|
||||
|
@ -20,7 +20,8 @@ from valence.db import models
|
||||
|
||||
etcd_directories = [
|
||||
models.PodManager.path,
|
||||
models.Flavor.path
|
||||
models.Flavor.path,
|
||||
models.ComposedNode.path
|
||||
]
|
||||
|
||||
etcd_client = etcd.Client(config.etcd_host, config.etcd_port)
|
||||
|
@ -39,6 +39,8 @@ def translate_to_models(etcd_resp, model_type):
|
||||
ret = models.PodManager(**data)
|
||||
elif model_type == models.Flavor.path:
|
||||
ret = models.Flavor(**data)
|
||||
elif model_type == models.ComposedNode.path:
|
||||
ret = models.ComposedNode(**data)
|
||||
else:
|
||||
# TODO(lin.a.yang): after exception module got merged, raise
|
||||
# valence specific InvalidParameter exception here
|
||||
@ -149,3 +151,51 @@ class EtcdDriver(object):
|
||||
flavor, models.Flavor.path))
|
||||
|
||||
return flavors
|
||||
|
||||
def create_composed_node(self, values):
|
||||
composed_node = models.ComposedNode(**values)
|
||||
composed_node.save()
|
||||
|
||||
return composed_node
|
||||
|
||||
def get_composed_node_by_uuid(self, composed_node_uuid):
|
||||
try:
|
||||
resp = self.client.read(models.ComposedNode.etcd_path(
|
||||
composed_node_uuid))
|
||||
except etcd.EtcdKeyNotFound:
|
||||
# TODO(lin.a.yang): after exception module got merged, raise
|
||||
# valence specific DBNotFound exception here
|
||||
raise Exception(
|
||||
'Composed node not found {0} in database.'.format(
|
||||
composed_node_uuid))
|
||||
|
||||
return translate_to_models(resp, models.ComposedNode.path)
|
||||
|
||||
def delete_composed_node(self, composed_node_uuid):
|
||||
composed_node = self.get_composed_node_by_uuid(composed_node_uuid)
|
||||
composed_node.delete()
|
||||
|
||||
def update_composed_node(self, composed_node_uuid, values):
|
||||
composed_node = self.get_composed_node_by_uuid(composed_node_uuid)
|
||||
composed_node.update(values)
|
||||
|
||||
return composed_node
|
||||
|
||||
def list_composed_nodes(self):
|
||||
# TODO(lin.a.yang): support filter for listing composed_node
|
||||
|
||||
try:
|
||||
resp = getattr(self.client.read(models.ComposedNode.path),
|
||||
'children', None)
|
||||
except etcd.EtcdKeyNotFound:
|
||||
LOG.error("Path '/nodes' does not exist, seems etcd server "
|
||||
"was not initialized appropriately.")
|
||||
raise
|
||||
|
||||
composed_nodes = []
|
||||
for node in resp:
|
||||
if node.value is not None:
|
||||
composed_nodes.append(translate_to_models(
|
||||
node, models.ComposedNode.path))
|
||||
|
||||
return composed_nodes
|
||||
|
@ -189,3 +189,23 @@ class Flavor(ModelBaseWithTimeStamp):
|
||||
'validate': types.Dict.validate
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ComposedNode(ModelBaseWithTimeStamp):
|
||||
|
||||
path = "/nodes"
|
||||
|
||||
fields = {
|
||||
'uuid': {
|
||||
'validate': types.Text.validate
|
||||
},
|
||||
'name': {
|
||||
'validate': types.Text.validate
|
||||
},
|
||||
'index': {
|
||||
'validate': types.Text.validate
|
||||
},
|
||||
'links': {
|
||||
'validate': types.List(types.Dict).validate
|
||||
}
|
||||
}
|
||||
|
@ -490,7 +490,7 @@ def compose_node(request_body):
|
||||
|
||||
if assemble_resp.status_code != http_client.NO_CONTENT:
|
||||
# Delete node if assemble failed
|
||||
delete_composednode(node_index)
|
||||
delete_composed_node(node_index)
|
||||
raise exception.RedfishException(assemble_resp.json(),
|
||||
status_code=resp.status_code)
|
||||
else:
|
||||
@ -498,10 +498,10 @@ def compose_node(request_body):
|
||||
LOG.debug('Successfully assembled node: ' + node_url)
|
||||
|
||||
# Return new composed node index
|
||||
return get_node_by_id(node_index, show_detail=False)
|
||||
return get_node_by_id(node_index)
|
||||
|
||||
|
||||
def delete_composednode(nodeid):
|
||||
def delete_composed_node(nodeid):
|
||||
nodes_url = get_base_resource_url("Nodes")
|
||||
delete_url = nodes_url + '/' + str(nodeid)
|
||||
resp = send_request(delete_url, "DELETE")
|
||||
|
52
valence/tests/unit/controller/fakes.py
Normal file
52
valence/tests/unit/controller/fakes.py
Normal file
@ -0,0 +1,52 @@
|
||||
# copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
def get_test_composed_node(**kwargs):
|
||||
return {
|
||||
'uuid': kwargs.get('uuid', 'ea8e2a25-2901-438d-8157-de7ffd68d051'),
|
||||
'name': kwargs.get('name', 'fake_name'),
|
||||
'description': kwargs.get('description', 'fake_description'),
|
||||
'boot_source': kwargs.get('boot_source', 'Hdd'),
|
||||
'health_status': kwargs.get('health_status', 'OK'),
|
||||
'index': kwargs.get('index', '1'),
|
||||
'node_power_state': kwargs.get('node_power_state', 'On'),
|
||||
'node_state': kwargs.get('node_state', 'Assembling'),
|
||||
'pooled_group_id': kwargs.get('bookmark_link', 'None'),
|
||||
'target_boot_source': kwargs.get('target_boot_source', 'Hdd'),
|
||||
'links': kwargs.get(
|
||||
'links',
|
||||
[{'href': 'http://127.0.0.1:8181/v1/nodes/'
|
||||
'7be5bc10-dcdf-11e6-bd86-934bc6947c55/',
|
||||
'rel': 'self'},
|
||||
{'href': 'http://127.0.0.1:8181/nodes/'
|
||||
'7be5bc10-dcdf-11e6-bd86-934bc6947c55/',
|
||||
'rel': 'bookmark'}]),
|
||||
'metadata': kwargs.get(
|
||||
'metadata',
|
||||
{'memory': [{'data_width_bit': 0,
|
||||
'speed_mhz': 2400,
|
||||
'total_memory_mb': 8192}],
|
||||
'network': [{'ipv4': [{'address': '192.168.0.10',
|
||||
'gateway': '192.168.0.1',
|
||||
'subnet_mask': '255.255.252.0'}],
|
||||
'mac': 'e9:47:d3:60:64:66',
|
||||
'speed_mbps': 0,
|
||||
'status': 'Enabled',
|
||||
'vlans': [{'status': 'Enabled',
|
||||
'vlanid': 99}]}],
|
||||
'processor': [{'instruction_set': None,
|
||||
'model': None,
|
||||
'speed_mhz': 3700,
|
||||
'total_core': 0}]})
|
||||
}
|
117
valence/tests/unit/controller/test_nodes.py
Normal file
117
valence/tests/unit/controller/test_nodes.py
Normal file
@ -0,0 +1,117 @@
|
||||
# copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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 unittest
|
||||
|
||||
import mock
|
||||
|
||||
from valence.controller import nodes
|
||||
from valence.tests.unit.controller import fakes
|
||||
from valence.tests.unit.db import utils as test_utils
|
||||
|
||||
|
||||
class TestAPINodes(unittest.TestCase):
|
||||
|
||||
def test_show_node_brief_info(self):
|
||||
"""Test only show node brief info"""
|
||||
node_info = fakes.get_test_composed_node()
|
||||
expected = {
|
||||
"name": "fake_name",
|
||||
"uuid": "ea8e2a25-2901-438d-8157-de7ffd68d051",
|
||||
"links": [{'href': 'http://127.0.0.1:8181/v1/nodes/'
|
||||
'7be5bc10-dcdf-11e6-bd86-934bc6947c55/',
|
||||
'rel': 'self'},
|
||||
{'href': 'http://127.0.0.1:8181/nodes/'
|
||||
'7be5bc10-dcdf-11e6-bd86-934bc6947c55/',
|
||||
'rel': 'bookmark'}]
|
||||
}
|
||||
self.assertEqual(expected,
|
||||
nodes.Node._show_node_brief_info(node_info))
|
||||
|
||||
@mock.patch("valence.db.api.Connection.create_composed_node")
|
||||
@mock.patch("valence.common.utils.generate_uuid")
|
||||
@mock.patch("valence.redfish.redfish.compose_node")
|
||||
def test_compose_node(self, mock_redfish_compose_node, mock_generate_uuid,
|
||||
mock_db_create_composed_node):
|
||||
"""Test compose node successfully"""
|
||||
node_hw = fakes.get_test_composed_node()
|
||||
node_db = {"uuid": node_hw["uuid"],
|
||||
"index": node_hw["index"],
|
||||
"name": node_hw["name"],
|
||||
"links": node_hw["links"]}
|
||||
|
||||
mock_redfish_compose_node.return_value = node_hw
|
||||
uuid = 'ea8e2a25-2901-438d-8157-de7ffd68d051'
|
||||
mock_generate_uuid.return_value = uuid
|
||||
|
||||
result = nodes.Node.compose_node({"name": "test"})
|
||||
expected = nodes.Node._show_node_brief_info(node_hw)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
mock_db_create_composed_node.assert_called_once_with(node_db)
|
||||
|
||||
@mock.patch("valence.redfish.redfish.get_node_by_id")
|
||||
@mock.patch("valence.db.api.Connection.get_composed_node_by_uuid")
|
||||
def test_get_composed_node_by_uuid(
|
||||
self, mock_db_get_composed_node, mock_redfish_get_node):
|
||||
"""Test get composed node detail"""
|
||||
node_hw = fakes.get_test_composed_node()
|
||||
node_db = test_utils.get_test_composed_node_db_info()
|
||||
|
||||
mock_db_model = mock.MagicMock()
|
||||
mock_db_model.as_dict.return_value = node_db
|
||||
mock_db_get_composed_node.return_value = mock_db_model
|
||||
|
||||
mock_redfish_get_node.return_value = node_hw
|
||||
|
||||
result = nodes.Node.get_composed_node_by_uuid("fake_uuid")
|
||||
|
||||
expected = copy.deepcopy(node_hw)
|
||||
expected.update(node_db)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
@mock.patch("valence.db.api.Connection.delete_composed_node")
|
||||
@mock.patch("valence.redfish.redfish.delete_composed_node")
|
||||
@mock.patch("valence.db.api.Connection.get_composed_node_by_uuid")
|
||||
def test_delete_composed_node(
|
||||
self, mock_db_get_composed_node, mock_redfish_delete_composed_node,
|
||||
mock_db_delete_composed_node):
|
||||
"""Test delete composed node"""
|
||||
node_db = test_utils.get_test_composed_node_db_info()
|
||||
|
||||
mock_db_model = mock.MagicMock()
|
||||
mock_db_model.index = node_db["index"]
|
||||
mock_db_get_composed_node.return_value = mock_db_model
|
||||
|
||||
nodes.Node.delete_composed_node(node_db["uuid"])
|
||||
|
||||
mock_redfish_delete_composed_node.assert_called_once_with(
|
||||
node_db["index"])
|
||||
mock_db_delete_composed_node.assert_called_once_with(
|
||||
node_db["uuid"])
|
||||
|
||||
@mock.patch("valence.db.api.Connection.list_composed_nodes")
|
||||
def test_list_composed_nodes(self, mock_db_list_composed_nodes):
|
||||
"""Test list all composed nodes"""
|
||||
node_db = test_utils.get_test_composed_node_db_info()
|
||||
|
||||
mock_db_model = mock.MagicMock()
|
||||
mock_db_model.as_dict.return_value = node_db
|
||||
mock_db_list_composed_nodes.return_value = [mock_db_model]
|
||||
|
||||
expected = [nodes.Node._show_node_brief_info(node_db)]
|
||||
|
||||
result = nodes.Node.list_composed_nodes()
|
||||
|
||||
self.assertEqual(expected, result)
|
@ -178,3 +178,83 @@ class TestDBAPI(unittest.TestCase):
|
||||
mock_etcd_write.assert_called_with(
|
||||
'/flavors/' + flavor['uuid'],
|
||||
json.dumps(result.as_dict()))
|
||||
|
||||
@freezegun.freeze_time("2017-01-01")
|
||||
@mock.patch('etcd.Client.write')
|
||||
@mock.patch('etcd.Client.read')
|
||||
def test_create_composed_node(self, mock_etcd_read, mock_etcd_write):
|
||||
node = utils.get_test_composed_node_db_info()
|
||||
fake_utcnow = '2017-01-01 00:00:00 UTC'
|
||||
node['created_at'] = fake_utcnow
|
||||
node['updated_at'] = fake_utcnow
|
||||
|
||||
# Mark this uuid don't exist in etcd db
|
||||
mock_etcd_read.side_effect = etcd.EtcdKeyNotFound
|
||||
|
||||
result = db_api.Connection.create_composed_node(node)
|
||||
self.assertEqual(node, result.as_dict())
|
||||
mock_etcd_read.assert_called_once_with(
|
||||
'/nodes/' + node['uuid'])
|
||||
mock_etcd_write.assert_called_once_with(
|
||||
'/nodes/' + node['uuid'],
|
||||
json.dumps(result.as_dict()))
|
||||
|
||||
@mock.patch('etcd.Client.read')
|
||||
def test_get_composed_node_by_uuid(self, mock_etcd_read):
|
||||
node = utils.get_test_composed_node_db_info()
|
||||
|
||||
mock_etcd_read.return_value = utils.get_etcd_read_result(
|
||||
node['uuid'], json.dumps(node))
|
||||
result = db_api.Connection.get_composed_node_by_uuid(node['uuid'])
|
||||
|
||||
self.assertEqual(node, result.as_dict())
|
||||
mock_etcd_read.assert_called_once_with(
|
||||
'/nodes/' + node['uuid'])
|
||||
|
||||
@mock.patch('etcd.Client.read')
|
||||
def test_get_composed_node_not_found(self, mock_etcd_read):
|
||||
node = utils.get_test_composed_node_db_info()
|
||||
mock_etcd_read.side_effect = etcd.EtcdKeyNotFound
|
||||
|
||||
with self.assertRaises(Exception) as context: # noqa: H202
|
||||
db_api.Connection.get_composed_node_by_uuid(node['uuid'])
|
||||
|
||||
self.assertTrue('Composed node not found {0} in database.'.format(
|
||||
node['uuid']) in str(context.exception))
|
||||
mock_etcd_read.assert_called_once_with(
|
||||
'/nodes/' + node['uuid'])
|
||||
|
||||
@mock.patch('etcd.Client.delete')
|
||||
@mock.patch('etcd.Client.read')
|
||||
def test_delete_composed_node(self, mock_etcd_read, mock_etcd_delete):
|
||||
node = utils.get_test_composed_node_db_info()
|
||||
|
||||
mock_etcd_read.return_value = utils.get_etcd_read_result(
|
||||
node['uuid'], json.dumps(node))
|
||||
db_api.Connection.delete_composed_node(node['uuid'])
|
||||
|
||||
mock_etcd_delete.assert_called_with(
|
||||
'/nodes/' + node['uuid'])
|
||||
|
||||
@freezegun.freeze_time("2017-01-01")
|
||||
@mock.patch('etcd.Client.write')
|
||||
@mock.patch('etcd.Client.read')
|
||||
def test_update_composed_node(self, mock_etcd_read, mock_etcd_write):
|
||||
node = utils.get_test_composed_node_db_info()
|
||||
|
||||
mock_etcd_read.return_value = utils.get_etcd_read_result(
|
||||
node['uuid'], json.dumps(node))
|
||||
|
||||
fake_utcnow = '2017-01-01 00:00:00 UTC'
|
||||
node['updated_at'] = fake_utcnow
|
||||
node.update({'index': '2'})
|
||||
|
||||
result = db_api.Connection.update_composed_node(
|
||||
node['uuid'], {'index': '2'})
|
||||
|
||||
self.assertEqual(node, result.as_dict())
|
||||
mock_etcd_read.assert_called_with(
|
||||
'/nodes/' + node['uuid'])
|
||||
mock_etcd_write.assert_called_with(
|
||||
'/nodes/' + node['uuid'],
|
||||
json.dumps(result.as_dict()))
|
||||
|
@ -75,3 +75,21 @@ def get_test_flavor(**kwargs):
|
||||
'created_at': kwargs.get('created_at', '2016-01-01 00:00:00 UTC'),
|
||||
'updated_at': kwargs.get('updated_at', '2016-01-01 00:00:00 UTC'),
|
||||
}
|
||||
|
||||
|
||||
def get_test_composed_node_db_info(**kwargs):
|
||||
return {
|
||||
'uuid': kwargs.get('uuid', 'ea8e2a25-2901-438d-8157-de7ffd68d051'),
|
||||
'name': kwargs.get('name', 'fake_name'),
|
||||
'index': kwargs.get('index', '1'),
|
||||
'links': kwargs.get(
|
||||
'links',
|
||||
[{'href': 'http://127.0.0.1:8181/v1/nodes/'
|
||||
'7be5bc10-dcdf-11e6-bd86-934bc6947c55/',
|
||||
'rel': 'self'},
|
||||
{'href': 'http://127.0.0.1:8181/nodes/'
|
||||
'7be5bc10-dcdf-11e6-bd86-934bc6947c55/',
|
||||
'rel': 'bookmark'}]),
|
||||
'created_at': kwargs.get('created_at', '2016-01-01 00:00:00 UTC'),
|
||||
'updated_at': kwargs.get('updated_at', '2016-01-01 00:00:00 UTC')
|
||||
}
|
||||
|
@ -255,7 +255,7 @@ class TestRedfish(TestCase):
|
||||
fake_delete_response = fakes.mock_request_get(delete_result,
|
||||
http_client.NO_CONTENT)
|
||||
mock_request.return_value = fake_delete_response
|
||||
result = redfish.delete_composednode(101)
|
||||
result = redfish.delete_composed_node(101)
|
||||
mock_request.assert_called_with('/redfish/v1/Nodes/101', 'DELETE')
|
||||
expected = {
|
||||
"code": "DELETED",
|
||||
@ -276,7 +276,7 @@ class TestRedfish(TestCase):
|
||||
http_client.INTERNAL_SERVER_ERROR)
|
||||
mock_request.return_value = fake_resp
|
||||
self.assertRaises(exception.RedfishException,
|
||||
redfish.delete_composednode, 101)
|
||||
redfish.delete_composed_node, 101)
|
||||
self.assertFalse(mock_make_response.called)
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@ -340,7 +340,7 @@ class TestRedfish(TestCase):
|
||||
self.assertTrue("There are no computer systems available for this "
|
||||
"allocation request." in str(context.exception.detail))
|
||||
|
||||
@mock.patch('valence.redfish.redfish.delete_composednode')
|
||||
@mock.patch('valence.redfish.redfish.delete_composed_node')
|
||||
@mock.patch('valence.redfish.redfish.get_base_resource_url')
|
||||
@mock.patch('valence.redfish.redfish.send_request')
|
||||
def test_assemble_node_failed(self, mock_request, mock_get_url,
|
||||
@ -375,7 +375,7 @@ class TestRedfish(TestCase):
|
||||
mock_delete_node.assert_called_once()
|
||||
|
||||
@mock.patch('valence.redfish.redfish.get_node_by_id')
|
||||
@mock.patch('valence.redfish.redfish.delete_composednode')
|
||||
@mock.patch('valence.redfish.redfish.delete_composed_node')
|
||||
@mock.patch('valence.redfish.redfish.get_base_resource_url')
|
||||
@mock.patch('valence.redfish.redfish.send_request')
|
||||
def test_assemble_node_success(self, mock_request, mock_get_url,
|
||||
|
Loading…
x
Reference in New Issue
Block a user