Add Rack API
This commit adds API and corresponding controller functions for viewing rack resources. Change-Id: I66fd2f9cfe30b7a87ee4ea16a9f8027c34d1d0e6 Closes-bug: #1633443
This commit is contained in:
parent
6a0ae4e05b
commit
d11c1aef9b
15
api-ref/source/mockup/rack-get-response.json
Normal file
15
api-ref/source/mockup/rack-get-response.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"description": "Rack created by PODM",
|
||||||
|
"id": "1",
|
||||||
|
"manufacturer": "Intel",
|
||||||
|
"model": "RSD_1",
|
||||||
|
"name": "Rack 1",
|
||||||
|
"serial_number": "12345",
|
||||||
|
"systems": [
|
||||||
|
"2cd33e50-0e7a-11e7-8c14-c5fab3f6ca28",
|
||||||
|
"2a911680-0e7a-11e7-8c14-c5fab3f6ca28",
|
||||||
|
"4cadbee1-fe07-11e6-8c14-c5fab3f6ca28"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
19
api-ref/source/mockup/rack-list-response.json
Normal file
19
api-ref/source/mockup/rack-list-response.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"name": "Rack 1",
|
||||||
|
"systems": [
|
||||||
|
"2cd33e50-0e7a-11e7-8c14-c5fab3f6ca28",
|
||||||
|
"2a911680-0e7a-11e7-8c14-c5fab3f6ca28",
|
||||||
|
"4cadbee1-fe07-11e6-8c14-c5fab3f6ca28"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"name": "Rack 2",
|
||||||
|
"systems": [
|
||||||
|
"7ac441b3-a4a1-44f4-8b38-469492cbfb61",
|
||||||
|
"3bf332e4-100c-11e7-93ae-92361f002671"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -285,6 +285,45 @@ pod_redfish_link:
|
|||||||
pod_status:
|
pod_status:
|
||||||
description: |
|
description: |
|
||||||
Pod manager status
|
Pod manager status
|
||||||
|
rack_id:
|
||||||
|
description: |
|
||||||
|
The ID of a hardware rack.
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
rack_name:
|
||||||
|
description: |
|
||||||
|
Name of a hardware rack.
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
rack_systems:
|
||||||
|
description: |
|
||||||
|
Compute systems contained by a rack.
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
rack_manufacturer:
|
||||||
|
description: |
|
||||||
|
The manufacturer for a rack.
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
rack_model:
|
||||||
|
description: |
|
||||||
|
The model for a rack.
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
rack_description:
|
||||||
|
description: |
|
||||||
|
The description of a rack.
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
rack_serial_number:
|
||||||
|
description: |
|
||||||
|
The serial number of a rack.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
75
api-ref/source/valence-api-v1-racks.inc
Normal file
75
api-ref/source/valence-api-v1-racks.inc
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
.. -*- rst -*-
|
||||||
|
|
||||||
|
====
|
||||||
|
Rack
|
||||||
|
====
|
||||||
|
|
||||||
|
List, Searching of hardware racks through the ``/v1/racks`` resource.
|
||||||
|
|
||||||
|
List Racks
|
||||||
|
==========
|
||||||
|
|
||||||
|
.. rest_method:: GET /v1/racks/
|
||||||
|
|
||||||
|
Return a list of Racks.
|
||||||
|
Some filtering is possible by passing in flags with the request.
|
||||||
|
By default, this query will return racks with id, name, location and
|
||||||
|
contained compute systems.
|
||||||
|
|
||||||
|
Normal response codes: 200
|
||||||
|
Error response codes: badRequest(400), unauthorized(401), forbidden(403)
|
||||||
|
|
||||||
|
Request
|
||||||
|
-------
|
||||||
|
|
||||||
|
Response
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
|
- id: rack_id
|
||||||
|
- name: rack_name
|
||||||
|
- systems: rack_systems
|
||||||
|
|
||||||
|
**Example list of Racks:**
|
||||||
|
|
||||||
|
.. literalinclude:: mockup/rack-list-response.json
|
||||||
|
:language: javascript
|
||||||
|
|
||||||
|
|
||||||
|
Display Rack Details
|
||||||
|
====================
|
||||||
|
|
||||||
|
.. rest_method:: GET /v1/racks/{rack_id}
|
||||||
|
|
||||||
|
Shows details for a Rack.
|
||||||
|
This will return the full representation of the resources.
|
||||||
|
|
||||||
|
Normal response codes: 200
|
||||||
|
|
||||||
|
Error response codes: badRequest(400), unauthorized(401), forbidden(403)
|
||||||
|
|
||||||
|
Request
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
|
- id: rack_id
|
||||||
|
|
||||||
|
Response
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
|
- id: rack_id
|
||||||
|
- name: rack_name
|
||||||
|
- systems: rack_systems
|
||||||
|
- manufacturer: rack_manufacturer
|
||||||
|
- model: rack_model
|
||||||
|
- description: rack_description
|
||||||
|
- serial_number: rack_serial_number
|
||||||
|
|
||||||
|
**Example JSON representation of a Rack:**
|
||||||
|
|
||||||
|
.. literalinclude:: mockup/rack-get-response.json
|
||||||
|
:language: javascript
|
@ -24,6 +24,7 @@ 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.podmanagers as v1_podmanagers
|
||||||
|
import valence.api.v1.racks as v1_racks
|
||||||
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
|
||||||
@ -65,6 +66,10 @@ api.add_resource(api_root.Root, '/', endpoint='root')
|
|||||||
# V1 Root operations
|
# V1 Root operations
|
||||||
api.add_resource(v1_version.V1, '/v1', endpoint='v1')
|
api.add_resource(v1_version.V1, '/v1', endpoint='v1')
|
||||||
|
|
||||||
|
# Rack operations
|
||||||
|
api.add_resource(v1_racks.RackList, '/v1/racks', endpoint='racks')
|
||||||
|
api.add_resource(v1_racks.Rack, '/v1/racks/<string:rack_id>', endpoint='rack')
|
||||||
|
|
||||||
# Node(s) operations
|
# Node(s) operations
|
||||||
api.add_resource(v1_nodes.Nodes, '/v1/nodes', endpoint='nodes')
|
api.add_resource(v1_nodes.Nodes, '/v1/nodes', endpoint='nodes')
|
||||||
api.add_resource(v1_nodes.Node,
|
api.add_resource(v1_nodes.Node,
|
||||||
|
38
valence/api/v1/racks.py
Normal file
38
valence/api/v1/racks.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# 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 flask import request
|
||||||
|
import flask_restful
|
||||||
|
from six.moves import http_client
|
||||||
|
|
||||||
|
from valence.common import utils
|
||||||
|
from valence.redfish import redfish
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RackList(flask_restful.Resource):
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
return utils.make_response(
|
||||||
|
http_client.OK, redfish.list_racks(request.get_json()))
|
||||||
|
|
||||||
|
|
||||||
|
class Rack(flask_restful.Resource):
|
||||||
|
|
||||||
|
def get(self, rack_id):
|
||||||
|
return utils.make_response(
|
||||||
|
http_client.OK, redfish.show_rack(rack_id))
|
@ -13,7 +13,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@ -81,33 +80,68 @@ def send_request(resource, method="GET", **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def filter_chassis(jsonContent, filterCondition):
|
def filter_chassis(jsonContent, filterCondition):
|
||||||
returnJSONObj = {}
|
|
||||||
returnMembers = []
|
returnMembers = []
|
||||||
members = jsonContent['Members']
|
members = jsonContent['Members']
|
||||||
for member in members:
|
for member in members:
|
||||||
resource = member['@odata.id']
|
resource = member['@odata.id']
|
||||||
resp = send_request(resource)
|
resp = send_request(resource)
|
||||||
memberJsonObj = resp.json()
|
member_detail = resp.json()
|
||||||
chassisType = memberJsonObj['ChassisType']
|
chassisType = member_detail['ChassisType']
|
||||||
if chassisType == filterCondition:
|
if chassisType == filterCondition:
|
||||||
returnMembers.append(member)
|
returnMembers.append(member_detail)
|
||||||
returnJSONObj["Members"] = returnMembers
|
return returnMembers
|
||||||
returnJSONObj["Members@odata.count"] = len(returnMembers)
|
|
||||||
return returnJSONObj
|
|
||||||
|
|
||||||
|
|
||||||
def racks():
|
def list_racks(filters={}, show_detail=False):
|
||||||
chassis_url = get_base_resource_url("Chassis")
|
chassis_url = get_base_resource_url("Chassis")
|
||||||
jsonContent = send_request(chassis_url)
|
resp = send_request(chassis_url)
|
||||||
racks = filter_chassis(jsonContent, "Rack")
|
json_content = resp.json()
|
||||||
return json.dumps(racks)
|
raw_racks = filter_chassis(json_content, "Rack")
|
||||||
|
racks = []
|
||||||
|
filterPassed = True
|
||||||
|
|
||||||
|
for rack in raw_racks:
|
||||||
|
|
||||||
|
if any(filters):
|
||||||
|
filterPassed = utils.match_conditions(rack, filters)
|
||||||
|
if not filterPassed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
rack_info = {}
|
||||||
|
|
||||||
|
rack_id = rack["Id"]
|
||||||
|
rack_name = rack["Name"]
|
||||||
|
rack_systems = get_systems_in_chassis(rack)
|
||||||
|
rack_info.update({"id": rack_id, "name": rack_name,
|
||||||
|
"systems": rack_systems})
|
||||||
|
if show_detail:
|
||||||
|
manufacturer = rack["Manufacturer"]
|
||||||
|
model = rack["Model"]
|
||||||
|
description = rack["Description"]
|
||||||
|
serial_number = rack["SerialNumber"]
|
||||||
|
rack_info.update({"manufacturer": manufacturer,
|
||||||
|
"model": model,
|
||||||
|
"description": description,
|
||||||
|
"serial_number": serial_number})
|
||||||
|
racks.append(rack_info)
|
||||||
|
return racks
|
||||||
|
|
||||||
|
|
||||||
def pods():
|
def show_rack(rack_id):
|
||||||
chassis_url = get_base_resource_url("Chassis")
|
return list_racks({"Id": rack_id}, show_detail=True)
|
||||||
jsonContent = send_request(chassis_url)
|
|
||||||
pods = filter_chassis(jsonContent, "Pod")
|
|
||||||
return json.dumps(pods)
|
def get_systems_in_chassis(chassis, total_systems=[]):
|
||||||
|
for chassis_link in chassis["Links"]["Contains"]:
|
||||||
|
resp = send_request(chassis_link["@odata.id"])
|
||||||
|
chassis = resp.json()
|
||||||
|
total_systems = get_systems_in_chassis(chassis, total_systems)
|
||||||
|
for system_link in chassis["Links"]["ComputerSystems"]:
|
||||||
|
resp = send_request(system_link["@odata.id"])
|
||||||
|
system = resp.json()
|
||||||
|
if system["UUID"] not in total_systems:
|
||||||
|
total_systems.append(system["UUID"])
|
||||||
|
return total_systems
|
||||||
|
|
||||||
|
|
||||||
def pod_status(pod_url, username, password):
|
def pod_status(pod_url, username, password):
|
||||||
|
@ -33,6 +33,7 @@ class TestRoute(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(self.api.owns_endpoint('root'), True)
|
self.assertEqual(self.api.owns_endpoint('root'), True)
|
||||||
self.assertEqual(self.api.owns_endpoint('v1'), True)
|
self.assertEqual(self.api.owns_endpoint('v1'), True)
|
||||||
|
self.assertEqual(self.api.owns_endpoint('racks'), True)
|
||||||
self.assertEqual(self.api.owns_endpoint('nodes'), True)
|
self.assertEqual(self.api.owns_endpoint('nodes'), True)
|
||||||
self.assertEqual(self.api.owns_endpoint('node'), True)
|
self.assertEqual(self.api.owns_endpoint('node'), True)
|
||||||
self.assertEqual(self.api.owns_endpoint('nodes_storages'), True)
|
self.assertEqual(self.api.owns_endpoint('nodes_storages'), True)
|
||||||
|
@ -341,3 +341,39 @@ def fake_assemble_node_failed():
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def fake_rack_list():
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"Description": "Rack created by PODM",
|
||||||
|
"Id": "2",
|
||||||
|
"Manufacturer": "Intel",
|
||||||
|
"Model": "RSD_1",
|
||||||
|
"Name": "Rack 1",
|
||||||
|
"SerialNumber": "12345",
|
||||||
|
"Links": {
|
||||||
|
"Contains": [],
|
||||||
|
"ComputerSystems": [
|
||||||
|
{"@odata.id": "/redfish/v1/Systems/1"},
|
||||||
|
{"@odata.id": "/redfish/v1/Systems/2"},
|
||||||
|
{"@odata.id": "/redfish/v1/Systems/3"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Description": "Rack created by PODM",
|
||||||
|
"Id": "3",
|
||||||
|
"Manufacturer": "Intel",
|
||||||
|
"Model": "RSD_1",
|
||||||
|
"Name": "Rack 2",
|
||||||
|
"SerialNumber": "12346",
|
||||||
|
"Links": {
|
||||||
|
"Contains": [],
|
||||||
|
"ComputerSystems": [
|
||||||
|
{"@odata.id": "/redfish/v1/Systems/4"},
|
||||||
|
{"@odata.id": "/redfish/v1/Systems/5"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
@ -119,10 +119,18 @@ class TestRedfish(TestCase):
|
|||||||
[{"@odata.id": "1"},
|
[{"@odata.id": "1"},
|
||||||
{"@odata.id": "2"},
|
{"@odata.id": "2"},
|
||||||
{"@odata.id": "3"}]}
|
{"@odata.id": "3"}]}
|
||||||
expected = {'Members': [
|
expected = [
|
||||||
{u'@odata.id': u'2'},
|
{
|
||||||
{u'@odata.id': u'3'}
|
"ChassisType": "Rack",
|
||||||
], 'Members@odata.count': 2}
|
"Name": "Rack 1",
|
||||||
|
"Id": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ChassisType": "Rack",
|
||||||
|
"Name": "Rack 2",
|
||||||
|
"Id": "3"
|
||||||
|
}
|
||||||
|
]
|
||||||
result = redfish.filter_chassis(chassis, "Rack")
|
result = redfish.filter_chassis(chassis, "Rack")
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
@ -576,3 +584,81 @@ class TestRedfish(TestCase):
|
|||||||
redfish.node_action("1", {"Reset": {"Type": "On"}})
|
redfish.node_action("1", {"Reset": {"Type": "On"}})
|
||||||
|
|
||||||
mock_reset_node.assert_called_once_with("1", {"Reset": {"Type": "On"}})
|
mock_reset_node.assert_called_once_with("1", {"Reset": {"Type": "On"}})
|
||||||
|
|
||||||
|
@mock.patch('valence.redfish.redfish.get_systems_in_chassis')
|
||||||
|
@mock.patch('valence.redfish.redfish.get_base_resource_url')
|
||||||
|
@mock.patch('valence.redfish.redfish.filter_chassis')
|
||||||
|
@mock.patch('valence.redfish.redfish.send_request')
|
||||||
|
def test_list_racks(self, mock_request, mock_filter, mock_base_url,
|
||||||
|
mock_system_list):
|
||||||
|
mock_base_url.return_value = "/redfish/v1/Chassis"
|
||||||
|
fake_chassis_list = fakes.fake_chassis_list()
|
||||||
|
mock_request.return_value = (
|
||||||
|
fakes.mock_request_get(fake_chassis_list, "200"))
|
||||||
|
mock_filter.return_value = fakes.fake_rack_list()
|
||||||
|
mock_system_list.side_effect = [
|
||||||
|
[
|
||||||
|
"2cd33e50-0e7a-11e7-8c14-c5fab3f6ca28",
|
||||||
|
"2a911680-0e7a-11e7-8c14-c5fab3f6ca28",
|
||||||
|
"4cadbee1-fe07-11e6-8c14-c5fab3f6ca28"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"7ac441b3-a4a1-44f4-8b38-469492cbfb61",
|
||||||
|
"3bf332e4-100c-11e7-93ae-92361f002671"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
expected = [
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"name": "Rack 1",
|
||||||
|
"systems": [
|
||||||
|
"2cd33e50-0e7a-11e7-8c14-c5fab3f6ca28",
|
||||||
|
"2a911680-0e7a-11e7-8c14-c5fab3f6ca28",
|
||||||
|
"4cadbee1-fe07-11e6-8c14-c5fab3f6ca28"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"name": "Rack 2",
|
||||||
|
"systems": [
|
||||||
|
"7ac441b3-a4a1-44f4-8b38-469492cbfb61",
|
||||||
|
"3bf332e4-100c-11e7-93ae-92361f002671"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
result = redfish.list_racks()
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
@mock.patch('valence.redfish.redfish.get_systems_in_chassis')
|
||||||
|
@mock.patch('valence.redfish.redfish.get_base_resource_url')
|
||||||
|
@mock.patch('valence.redfish.redfish.filter_chassis')
|
||||||
|
@mock.patch('valence.redfish.redfish.send_request')
|
||||||
|
def test_show_rack(self, mock_request, mock_filter, mock_base_url,
|
||||||
|
mock_system_list):
|
||||||
|
mock_base_url.return_value = "/redfish/v1/Chassis"
|
||||||
|
fake_chassis_list = fakes.fake_chassis_list()
|
||||||
|
mock_request.return_value = (
|
||||||
|
fakes.mock_request_get(fake_chassis_list, "200"))
|
||||||
|
mock_filter.return_value = fakes.fake_rack_list()
|
||||||
|
mock_system_list.return_value = [
|
||||||
|
"2cd33e50-0e7a-11e7-8c14-c5fab3f6ca28",
|
||||||
|
"2a911680-0e7a-11e7-8c14-c5fab3f6ca28",
|
||||||
|
"4cadbee1-fe07-11e6-8c14-c5fab3f6ca28"
|
||||||
|
]
|
||||||
|
expected = [
|
||||||
|
{
|
||||||
|
"description": "Rack created by PODM",
|
||||||
|
"id": "2",
|
||||||
|
"manufacturer": "Intel",
|
||||||
|
"model": "RSD_1",
|
||||||
|
"name": "Rack 1",
|
||||||
|
"serial_number": "12345",
|
||||||
|
"systems": [
|
||||||
|
"2cd33e50-0e7a-11e7-8c14-c5fab3f6ca28",
|
||||||
|
"2a911680-0e7a-11e7-8c14-c5fab3f6ca28",
|
||||||
|
"4cadbee1-fe07-11e6-8c14-c5fab3f6ca28"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
result = redfish.show_rack("2")
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
Loading…
Reference in New Issue
Block a user