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:
Nate Potter 2016-12-21 16:04:34 -08:00
parent 6a0ae4e05b
commit d11c1aef9b
10 changed files with 369 additions and 21 deletions

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

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

View File

@ -285,6 +285,45 @@ pod_redfish_link:
pod_status:
description: |
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
required: true
type: string

View 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

View File

@ -24,6 +24,7 @@ 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.racks as v1_racks
import valence.api.v1.storages as v1_storages
import valence.api.v1.systems as v1_systems
import valence.api.v1.version as v1_version
@ -65,6 +66,10 @@ api.add_resource(api_root.Root, '/', endpoint='root')
# V1 Root operations
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
api.add_resource(v1_nodes.Nodes, '/v1/nodes', endpoint='nodes')
api.add_resource(v1_nodes.Node,

38
valence/api/v1/racks.py Normal file
View 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))

View File

@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import logging
import os
@ -81,33 +80,68 @@ def send_request(resource, method="GET", **kwargs):
def filter_chassis(jsonContent, filterCondition):
returnJSONObj = {}
returnMembers = []
members = jsonContent['Members']
for member in members:
resource = member['@odata.id']
resp = send_request(resource)
memberJsonObj = resp.json()
chassisType = memberJsonObj['ChassisType']
member_detail = resp.json()
chassisType = member_detail['ChassisType']
if chassisType == filterCondition:
returnMembers.append(member)
returnJSONObj["Members"] = returnMembers
returnJSONObj["Members@odata.count"] = len(returnMembers)
return returnJSONObj
returnMembers.append(member_detail)
return returnMembers
def racks():
def list_racks(filters={}, show_detail=False):
chassis_url = get_base_resource_url("Chassis")
jsonContent = send_request(chassis_url)
racks = filter_chassis(jsonContent, "Rack")
return json.dumps(racks)
resp = send_request(chassis_url)
json_content = resp.json()
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():
chassis_url = get_base_resource_url("Chassis")
jsonContent = send_request(chassis_url)
pods = filter_chassis(jsonContent, "Pod")
return json.dumps(pods)
def show_rack(rack_id):
return list_racks({"Id": rack_id}, show_detail=True)
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):

View File

@ -33,6 +33,7 @@ class TestRoute(unittest.TestCase):
self.assertEqual(self.api.owns_endpoint('root'), 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('node'), True)
self.assertEqual(self.api.owns_endpoint('nodes_storages'), True)

View File

@ -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"}
]
}
}
]

View File

@ -119,10 +119,18 @@ class TestRedfish(TestCase):
[{"@odata.id": "1"},
{"@odata.id": "2"},
{"@odata.id": "3"}]}
expected = {'Members': [
{u'@odata.id': u'2'},
{u'@odata.id': u'3'}
], 'Members@odata.count': 2}
expected = [
{
"ChassisType": "Rack",
"Name": "Rack 1",
"Id": "2"
},
{
"ChassisType": "Rack",
"Name": "Rack 2",
"Id": "3"
}
]
result = redfish.filter_chassis(chassis, "Rack")
self.assertEqual(expected, result)
@ -576,3 +584,81 @@ class TestRedfish(TestCase):
redfish.node_action("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)