bd998017d5
Create host inventory services (api, conductor and agent) and python-inventoryclient. The inventory service collects the host resources and provides a REST API and client to expose the host resources. Create plugin for integration with system configuration (sysinv) service. This is the initial inventory service infratructure commit. Puppet configuration, SM integration and host integration with sysinv(systemconfig) changes are pending and planned to be delivered in future commits. Tests Performed: Verify the changes are inert on config_controller installation and provisioning. Puppet and spec changes are required in order to create keystone, database and activate inventory services. Unit tests performed (when puppet configuration for keystone, database is applied): Trigger host configure_check, configure signals into systemconfig(sysinv). Verify python-inventoryclient and api service: Disks and related storage resources are pending. inventory host-cpu-list/show inventory host-device-list/show/modify inventory host-ethernetport-list/show inventory host-lldp-neighbor-list inventory host-lldp-agent-list/show inventory host-memory-list/show inventory host-node-list/show inventory host-port-list/show Tox Unit tests: inventory: pep8 python-inventoryclient: py27, pep8, cover, pylint Change-Id: I744ac0de098608c55b9356abf180cc36601cfb8d Story: 2002950 Task: 22952 Signed-off-by: John Kung <john.kung@windriver.com>
262 lines
9.3 KiB
Python
262 lines
9.3 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
#
|
|
# Copyright 2013 UnitedStack Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# 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.
|
|
#
|
|
# Copyright (c) 2013-2016 Wind River Systems, Inc.
|
|
#
|
|
|
|
|
|
import six
|
|
|
|
import pecan
|
|
from pecan import rest
|
|
|
|
from wsme import types as wtypes
|
|
import wsmeext.pecan as wsme_pecan
|
|
|
|
from inventory.api.controllers.v1 import base
|
|
from inventory.api.controllers.v1 import collection
|
|
from inventory.api.controllers.v1 import cpu
|
|
from inventory.api.controllers.v1 import link
|
|
from inventory.api.controllers.v1 import memory
|
|
from inventory.api.controllers.v1 import port
|
|
from inventory.api.controllers.v1 import types
|
|
from inventory.api.controllers.v1 import utils
|
|
from inventory.common import exception
|
|
from inventory.common.i18n import _
|
|
from inventory import objects
|
|
|
|
from oslo_log import log
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class NodePatchType(types.JsonPatchType):
|
|
|
|
@staticmethod
|
|
def mandatory_attrs():
|
|
return ['/address', '/host_uuid']
|
|
|
|
|
|
class Node(base.APIBase):
|
|
"""API representation of a host node.
|
|
|
|
This class enforces type checking and value constraints, and converts
|
|
between the internal object model and the API representation of
|
|
an node.
|
|
"""
|
|
|
|
uuid = types.uuid
|
|
"Unique UUID for this node"
|
|
|
|
numa_node = int
|
|
"numa node zone for this node"
|
|
|
|
capabilities = {wtypes.text: utils.ValidTypes(wtypes.text,
|
|
six.integer_types)}
|
|
"This node's meta data"
|
|
|
|
host_id = int
|
|
"The hostid that this node belongs to"
|
|
|
|
host_uuid = types.uuid
|
|
"The UUID of the host this node belongs to"
|
|
|
|
links = [link.Link]
|
|
"A list containing a self link and associated node links"
|
|
|
|
icpus = [link.Link]
|
|
"Links to the collection of cpus on this node"
|
|
|
|
imemorys = [link.Link]
|
|
"Links to the collection of memorys on this node"
|
|
|
|
ports = [link.Link]
|
|
"Links to the collection of ports on this node"
|
|
|
|
def __init__(self, **kwargs):
|
|
self.fields = objects.Node.fields.keys()
|
|
for k in self.fields:
|
|
setattr(self, k, kwargs.get(k))
|
|
|
|
@classmethod
|
|
def convert_with_links(cls, rpc_node, expand=True):
|
|
minimum_fields = ['uuid', 'numa_node', 'capabilities',
|
|
'host_uuid', 'host_id',
|
|
'created_at'] if not expand else None
|
|
fields = minimum_fields if not expand else None
|
|
|
|
node = Node.from_rpc_object(rpc_node, fields)
|
|
|
|
# never expose the host_id attribute
|
|
node.host_id = wtypes.Unset
|
|
|
|
node.links = [link.Link.make_link('self', pecan.request.host_url,
|
|
'nodes', node.uuid),
|
|
link.Link.make_link('bookmark',
|
|
pecan.request.host_url,
|
|
'nodes', node.uuid,
|
|
bookmark=True)
|
|
]
|
|
if expand:
|
|
node.icpus = [link.Link.make_link('self',
|
|
pecan.request.host_url,
|
|
'nodes',
|
|
node.uuid + "/cpus"),
|
|
link.Link.make_link('bookmark',
|
|
pecan.request.host_url,
|
|
'nodes',
|
|
node.uuid + "/cpus",
|
|
bookmark=True)
|
|
]
|
|
|
|
node.imemorys = [link.Link.make_link('self',
|
|
pecan.request.host_url,
|
|
'nodes',
|
|
node.uuid + "/memorys"),
|
|
link.Link.make_link('bookmark',
|
|
pecan.request.host_url,
|
|
'nodes',
|
|
node.uuid + "/memorys",
|
|
bookmark=True)
|
|
]
|
|
|
|
node.ports = [link.Link.make_link('self',
|
|
pecan.request.host_url,
|
|
'nodes',
|
|
node.uuid + "/ports"),
|
|
link.Link.make_link('bookmark',
|
|
pecan.request.host_url,
|
|
'nodes',
|
|
node.uuid + "/ports",
|
|
bookmark=True)
|
|
]
|
|
|
|
return node
|
|
|
|
|
|
class NodeCollection(collection.Collection):
|
|
"""API representation of a collection of nodes."""
|
|
|
|
nodes = [Node]
|
|
"A list containing node objects"
|
|
|
|
def __init__(self, **kwargs):
|
|
self._type = 'nodes'
|
|
|
|
@classmethod
|
|
def convert_with_links(cls, rpc_nodes, limit, url=None,
|
|
expand=False, **kwargs):
|
|
collection = NodeCollection()
|
|
collection.nodes = [Node.convert_with_links(p, expand)
|
|
for p in rpc_nodes]
|
|
collection.next = collection.get_next(limit, url=url, **kwargs)
|
|
return collection
|
|
|
|
|
|
LOCK_NAME = 'NodeController'
|
|
|
|
|
|
class NodeController(rest.RestController):
|
|
"""REST controller for nodes."""
|
|
|
|
icpus = cpu.CPUController(from_node=True)
|
|
"Expose cpus as a sub-element of nodes"
|
|
|
|
imemorys = memory.MemoryController(from_node=True)
|
|
"Expose memorys as a sub-element of nodes"
|
|
|
|
ports = port.PortController(from_node=True)
|
|
"Expose ports as a sub-element of nodes"
|
|
|
|
_custom_actions = {
|
|
'detail': ['GET'],
|
|
}
|
|
|
|
def __init__(self, from_hosts=False):
|
|
self._from_hosts = from_hosts
|
|
|
|
def _get_nodes_collection(self, host_uuid, marker, limit, sort_key,
|
|
sort_dir, expand=False, resource_url=None):
|
|
if self._from_hosts and not host_uuid:
|
|
raise exception.InvalidParameterValue(_(
|
|
"Host id not specified."))
|
|
|
|
limit = utils.validate_limit(limit)
|
|
sort_dir = utils.validate_sort_dir(sort_dir)
|
|
|
|
marker_obj = None
|
|
if marker:
|
|
marker_obj = objects.Node.get_by_uuid(pecan.request.context,
|
|
marker)
|
|
|
|
if host_uuid:
|
|
nodes = objects.Node.get_by_host(pecan.request.context,
|
|
host_uuid,
|
|
limit,
|
|
marker=marker_obj,
|
|
sort_key=sort_key,
|
|
sort_dir=sort_dir)
|
|
else:
|
|
nodes = objects.Node.list(pecan.request.context,
|
|
limit,
|
|
marker=marker_obj,
|
|
sort_key=sort_key,
|
|
sort_dir=sort_dir)
|
|
|
|
return NodeCollection.convert_with_links(nodes, limit,
|
|
url=resource_url,
|
|
expand=expand,
|
|
sort_key=sort_key,
|
|
sort_dir=sort_dir)
|
|
|
|
@wsme_pecan.wsexpose(NodeCollection,
|
|
types.uuid, types.uuid, int, wtypes.text, wtypes.text)
|
|
def get_all(self, host_uuid=None, marker=None, limit=None,
|
|
sort_key='id', sort_dir='asc'):
|
|
"""Retrieve a list of nodes."""
|
|
|
|
return self._get_nodes_collection(host_uuid, marker, limit,
|
|
sort_key, sort_dir)
|
|
|
|
@wsme_pecan.wsexpose(NodeCollection, types.uuid, types.uuid, int,
|
|
wtypes.text, wtypes.text)
|
|
def detail(self, host_uuid=None, marker=None, limit=None,
|
|
sort_key='id', sort_dir='asc'):
|
|
"""Retrieve a list of nodes with detail."""
|
|
# NOTE(lucasagomes): /detail should only work agaist collections
|
|
parent = pecan.request.path.split('/')[:-1][-1]
|
|
if parent != "nodes":
|
|
raise exception.HTTPNotFound
|
|
|
|
expand = True
|
|
resource_url = '/'.join(['nodes', 'detail'])
|
|
return self._get_nodes_collection(host_uuid,
|
|
marker, limit,
|
|
sort_key, sort_dir,
|
|
expand, resource_url)
|
|
|
|
@wsme_pecan.wsexpose(Node, types.uuid)
|
|
def get_one(self, node_uuid):
|
|
"""Retrieve information about the given node."""
|
|
|
|
if self._from_hosts:
|
|
raise exception.OperationNotPermitted
|
|
|
|
rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid)
|
|
return Node.convert_with_links(rpc_node)
|