Source code for ironicclient.v1.create_resources

#    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 functools
import json

import jsonschema
import six
import yaml

from ironicclient import exc

_CREATE_SCHEMA = {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "description": "Schema for ironic resources file",
    "type": "object",
    "properties": {
        "chassis": {
            "type": "array",
            "items": {
                "type": "object"
            }
        },
        "nodes": {
            "type": "array",
            "items": {
                "type": "object"
            }
        }
    },
    "additionalProperties": False
}


[docs]def create_resources(client, filenames): """Create resources using their JSON or YAML descriptions. :param client: an instance of ironic client; :param filenames: a list of filenames containing JSON or YAML resources definitions. :raises: ClientException if any operation during files processing/resource creation fails. """ errors = [] resources = [] for resource_file in filenames: try: resource = load_from_file(resource_file) jsonschema.validate(resource, _CREATE_SCHEMA) resources.append(resource) except (exc.ClientException, jsonschema.ValidationError) as e: errors.append(e) if errors: raise exc.ClientException('While validating the resources file(s), the' ' following error(s) were encountered:\n%s' % '\n'.join(six.text_type(e) for e in errors)) for r in resources: errors.extend(create_chassis(client, r.get('chassis', []))) errors.extend(create_nodes(client, r.get('nodes', []))) if errors: raise exc.ClientException('During resources creation, the following ' 'error(s) were encountered:\n%s' % '\n'.join(six.text_type(e) for e in errors))
[docs]def load_from_file(filename): """Deserialize JSON or YAML from file. :param filename: name of the file containing JSON or YAML. :returns: a dictionary deserialized from JSON or YAML. :raises: ClientException if the file can not be loaded or if its contents is not a valid JSON or YAML, or if the file extension is not supported. """ try: with open(filename) as f: if filename.endswith('.yaml'): return yaml.safe_load(f) elif filename.endswith('.json'): return json.load(f) else: # The file is neither .json, nor .yaml, raise an exception raise exc.ClientException( 'Cannot process file "%(file)s" - it must have .json or ' '.yaml extension.' % {'file': filename}) except IOError as e: raise exc.ClientException('Cannot read file "%(file)s" due to ' 'error: %(err)s' % {'err': e, 'file': filename}) except (ValueError, yaml.YAMLError) as e: # json.load raises only ValueError raise exc.ClientException('File "%(file)s" is invalid due to error: ' '%(err)s' % {'err': e, 'file': filename})
[docs]def create_single_handler(resource_type): """Catch errors of the creation of a single resource. This decorator appends an error (which is an instance of some client exception class) to the return value of the create_method, changing the return value from just UUID to (UUID, error), and does some exception handling. :param resource_type: string value, the type of the resource being created, e.g. 'node', used purely for exception messages. """ def outer_wrapper(create_method): @functools.wraps(create_method) def wrapper(client, **params): uuid = None error = None try: uuid = create_method(client, **params) except exc.InvalidAttribute as e: error = exc.InvalidAttribute( 'Cannot create the %(resource)s with attributes ' '%(params)s. One or more attributes are invalid: %(err)s' % {'params': params, 'resource': resource_type, 'err': e} ) except Exception as e: error = exc.ClientException( 'Unable to create the %(resource)s with the specified ' 'attributes: %(params)s. The error is: %(error)s' % {'error': e, 'resource': resource_type, 'params': params}) return uuid, error return wrapper return outer_wrapper
@create_single_handler('node')
[docs]def create_single_node(client, **params): """Call the client to create a node. :param client: ironic client instance. :param params: dictionary to be POSTed to /nodes endpoint, excluding "ports" and "portgroups" keys. :returns: UUID of the created node or None in case of exception, and an exception, if it appears. :raises: InvalidAttribute, if some parameters passed to client's create_method are invalid. :raises: ClientException, if the creation of the node fails. """ params.pop('ports', None) params.pop('portgroups', None) ret = client.node.create(**params) return ret.uuid
@create_single_handler('port')
[docs]def create_single_port(client, **params): """Call the client to create a port. :param client: ironic client instance. :param params: dictionary to be POSTed to /ports endpoint. :returns: UUID of the created port or None in case of exception, and an exception, if it appears. :raises: InvalidAttribute, if some parameters passed to client's create_method are invalid. :raises: ClientException, if the creation of the port fails. """ ret = client.port.create(**params) return ret.uuid
@create_single_handler('port group')
[docs]def create_single_portgroup(client, **params): """Call the client to create a port group. :param client: ironic client instance. :param params: dictionary to be POSTed to /portgroups endpoint, excluding "ports" key. :returns: UUID of the created port group or None in case of exception, and an exception, if it appears. :raises: InvalidAttribute, if some parameters passed to client's create_method are invalid. :raises: ClientException, if the creation of the portgroup fails. """ params.pop('ports', None) ret = client.portgroup.create(**params) return ret.uuid
@create_single_handler('chassis')
[docs]def create_single_chassis(client, **params): """Call the client to create a chassis. :param client: ironic client instance. :param params: dictionary to be POSTed to /chassis endpoint, excluding "nodes" key. :returns: UUID of the created chassis or None in case of exception, and an exception, if it appears. :raises: InvalidAttribute, if some parameters passed to client's create_method are invalid. :raises: ClientException, if the creation of the chassis fails. """ params.pop('nodes', None) ret = client.chassis.create(**params) return ret.uuid
[docs]def create_ports(client, port_list, node_uuid, portgroup_uuid=None): """Create ports from dictionaries. :param client: ironic client instance. :param port_list: list of dictionaries to be POSTed to /ports endpoint. :param node_uuid: UUID of a node the ports should be associated with. :param portgroup_uuid: UUID of a port group the ports should be associated with, if they are its members. :returns: array of exceptions encountered during creation. """ errors = [] for port in port_list: port_node_uuid = port.get('node_uuid') if port_node_uuid and port_node_uuid != node_uuid: errors.append(exc.ClientException( 'Cannot create a port as part of node %(node_uuid)s ' 'because the port %(port)s has a different node UUID ' 'specified.', {'node_uuid': node_uuid, 'port': port})) continue port['node_uuid'] = node_uuid if portgroup_uuid: port_portgroup_uuid = port.get('portgroup_uuid') if port_portgroup_uuid and port_portgroup_uuid != portgroup_uuid: errors.append(exc.ClientException( 'Cannot create a port as part of port group ' '%(portgroup_uuid)s because the port %(port)s has a ' 'different port group UUID specified.', {'portgroup_uuid': portgroup_uuid, 'port': port})) continue port['portgroup_uuid'] = portgroup_uuid port_uuid, error = create_single_port(client, **port) if error: errors.append(error) return errors
[docs]def create_portgroups(client, portgroup_list, node_uuid): """Create port groups from dictionaries. :param client: ironic client instance. :param portgroup_list: list of dictionaries to be POSTed to /portgroups endpoint, if some of them contain "ports" key, its content is POSTed separately to /ports endpoint. :param node_uuid: UUID of a node the port groups should be associated with. :returns: array of exceptions encountered during creation. """ errors = [] for portgroup in portgroup_list: portgroup_node_uuid = portgroup.get('node_uuid') if portgroup_node_uuid and portgroup_node_uuid != node_uuid: errors.append(exc.ClientException( 'Cannot create a port group as part of node %(node_uuid)s ' 'because the port group %(portgroup)s has a different node ' 'UUID specified.', {'node_uuid': node_uuid, 'portgroup': portgroup})) continue portgroup['node_uuid'] = node_uuid portgroup_uuid, error = create_single_portgroup(client, **portgroup) if error: errors.append(error) ports = portgroup.get('ports') # Port group UUID == None means that port group creation failed, don't # create the ports inside it if ports is not None and portgroup_uuid is not None: errors.extend(create_ports(client, ports, node_uuid, portgroup_uuid=portgroup_uuid)) return errors
[docs]def create_nodes(client, node_list, chassis_uuid=None): """Create nodes from dictionaries. :param client: ironic client instance. :param node_list: list of dictionaries to be POSTed to /nodes endpoint, if some of them contain "ports" key, its content is POSTed separately to /ports endpoint. :param chassis_uuid: UUID of a chassis the nodes should be associated with. :returns: array of exceptions encountered during creation. """ errors = [] for node in node_list: if chassis_uuid is not None: node_chassis_uuid = node.get('chassis_uuid') if node_chassis_uuid and node_chassis_uuid != chassis_uuid: errors.append(exc.ClientException( 'Cannot create a node as part of chassis %(chassis_uuid)s ' 'because the node %(node)s has a different chassis UUID ' 'specified.' % {'chassis_uuid': chassis_uuid, 'node': node})) continue node['chassis_uuid'] = chassis_uuid node_uuid, error = create_single_node(client, **node) if error: errors.append(error) ports = node.get('ports') portgroups = node.get('portgroups') # Node UUID == None means that node creation failed, don't # create the port(group)s inside it if node_uuid is not None: if portgroups is not None: errors.extend( create_portgroups(client, portgroups, node_uuid)) if ports is not None: errors.extend(create_ports(client, ports, node_uuid)) return errors
[docs]def create_chassis(client, chassis_list): """Create chassis from dictionaries. :param client: ironic client instance. :param chassis_list: list of dictionaries to be POSTed to /chassis endpoint, if some of them contain "nodes" key, its content is POSTed separately to /nodes endpoint. :returns: array of exceptions encountered during creation. """ errors = [] for chassis in chassis_list: chassis_uuid, error = create_single_chassis(client, **chassis) if error: errors.append(error) nodes = chassis.get('nodes') # Chassis UUID == None means that chassis creation failed, don't # create the nodes inside it if nodes is not None and chassis_uuid is not None: errors.extend(create_nodes(client, nodes, chassis_uuid=chassis_uuid)) return errors