Building instance base codes
Change-Id: Ic34268df71625b39e588179e6586ca02466b5c6e
This commit is contained in:
		@@ -248,6 +248,13 @@
 | 
			
		||||
# Deprecated group/name - [DEFAULT]/rpc_zmq_serialization
 | 
			
		||||
#rpc_zmq_serialization = json
 | 
			
		||||
 | 
			
		||||
# This option configures round-robin mode in zmq socket. True
 | 
			
		||||
# means not keeping a queue when server side disconnects.
 | 
			
		||||
# False means to keep queue and messages even if server is
 | 
			
		||||
# disconnected, when the server appears we send all
 | 
			
		||||
# accumulated messages to it. (boolean value)
 | 
			
		||||
#zmq_immediate = false
 | 
			
		||||
 | 
			
		||||
# Size of executor thread pool. (integer value)
 | 
			
		||||
# Deprecated group/name - [DEFAULT]/rpc_thread_pool_size
 | 
			
		||||
#executor_thread_pool_size = 64
 | 
			
		||||
@@ -565,6 +572,82 @@
 | 
			
		||||
#sync_node_resource_interval = 60
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[ironic]
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# From nimble
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# URL for the Ironic API endpoint (string value)
 | 
			
		||||
#api_endpoint = http://ironic.example.org:6385/
 | 
			
		||||
 | 
			
		||||
# Ironic keystone admin username (string value)
 | 
			
		||||
#admin_username = <None>
 | 
			
		||||
 | 
			
		||||
# Ironic keystone admin password (string value)
 | 
			
		||||
#admin_password = <None>
 | 
			
		||||
 | 
			
		||||
# DEPRECATED:
 | 
			
		||||
# Ironic keystone auth token. This option is deprecated and
 | 
			
		||||
# admin_username, admin_password and admin_tenant_name options
 | 
			
		||||
# are used for authorization.
 | 
			
		||||
#  (string value)
 | 
			
		||||
# This option is deprecated for removal.
 | 
			
		||||
# Its value may be silently ignored in the future.
 | 
			
		||||
#admin_auth_token = <None>
 | 
			
		||||
 | 
			
		||||
# Keystone public API endpoint (string value)
 | 
			
		||||
#admin_url = <None>
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Path to the PEM encoded Certificate Authority file to be
 | 
			
		||||
# used when verifying
 | 
			
		||||
# HTTPs connections with the Ironic driver. By default this
 | 
			
		||||
# option is not used.
 | 
			
		||||
#
 | 
			
		||||
# Possible values:
 | 
			
		||||
#
 | 
			
		||||
# * None - Default
 | 
			
		||||
# * Path to the CA file
 | 
			
		||||
#  (string value)
 | 
			
		||||
#cafile = <None>
 | 
			
		||||
 | 
			
		||||
# Ironic keystone tenant name (string value)
 | 
			
		||||
#admin_tenant_name = <None>
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# The number of times to retry when a request conflicts.
 | 
			
		||||
# If set to 0, only try once, no retries.
 | 
			
		||||
#
 | 
			
		||||
# Related options:
 | 
			
		||||
#
 | 
			
		||||
# * api_retry_interval
 | 
			
		||||
#  (integer value)
 | 
			
		||||
# Minimum value: 0
 | 
			
		||||
#api_max_retries = 60
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# The number of seconds to wait before retrying the request.
 | 
			
		||||
#
 | 
			
		||||
# Related options:
 | 
			
		||||
#
 | 
			
		||||
# * api_max_retries
 | 
			
		||||
#  (integer value)
 | 
			
		||||
# Minimum value: 0
 | 
			
		||||
#api_retry_interval = 2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[keystone]
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# From nimble
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# The region used for getting endpoints of OpenStack services.
 | 
			
		||||
# (string value)
 | 
			
		||||
#region_name = <None>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[matchmaker_redis]
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
@@ -606,16 +689,34 @@
 | 
			
		||||
 | 
			
		||||
# Time in ms to wait between connection attempts. (integer
 | 
			
		||||
# value)
 | 
			
		||||
#wait_timeout = 5000
 | 
			
		||||
#wait_timeout = 2000
 | 
			
		||||
 | 
			
		||||
# Time in ms to wait before the transaction is killed.
 | 
			
		||||
# (integer value)
 | 
			
		||||
#check_timeout = 60000
 | 
			
		||||
#check_timeout = 20000
 | 
			
		||||
 | 
			
		||||
# Timeout in ms on blocking socket operations (integer value)
 | 
			
		||||
#socket_timeout = 10000
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[neutron]
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# From nimble
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# URL for connecting to neutron. (string value)
 | 
			
		||||
#url = <None>
 | 
			
		||||
 | 
			
		||||
# Timeout value for connecting to neutron in seconds. (integer
 | 
			
		||||
# value)
 | 
			
		||||
#url_timeout = 30
 | 
			
		||||
 | 
			
		||||
# Client retries in the case of a failed request. (integer
 | 
			
		||||
# value)
 | 
			
		||||
#retries = 3
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[oslo_concurrency]
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
@@ -641,22 +742,8 @@
 | 
			
		||||
# From oslo.messaging
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# address prefix used when sending to a specific server
 | 
			
		||||
# (string value)
 | 
			
		||||
# Deprecated group/name - [amqp1]/server_request_prefix
 | 
			
		||||
#server_request_prefix = exclusive
 | 
			
		||||
 | 
			
		||||
# address prefix used when broadcasting to all servers (string
 | 
			
		||||
# value)
 | 
			
		||||
# Deprecated group/name - [amqp1]/broadcast_prefix
 | 
			
		||||
#broadcast_prefix = broadcast
 | 
			
		||||
 | 
			
		||||
# address prefix when sending to any server in group (string
 | 
			
		||||
# value)
 | 
			
		||||
# Deprecated group/name - [amqp1]/group_request_prefix
 | 
			
		||||
#group_request_prefix = unicast
 | 
			
		||||
 | 
			
		||||
# Name for the AMQP container (string value)
 | 
			
		||||
# Name for the AMQP container. must be globally unique.
 | 
			
		||||
# Defaults to a generated UUID (string value)
 | 
			
		||||
# Deprecated group/name - [amqp1]/container_name
 | 
			
		||||
#container_name = <None>
 | 
			
		||||
 | 
			
		||||
@@ -716,6 +803,122 @@
 | 
			
		||||
# Deprecated group/name - [amqp1]/password
 | 
			
		||||
#password =
 | 
			
		||||
 | 
			
		||||
# Seconds to pause before attempting to re-connect. (integer
 | 
			
		||||
# value)
 | 
			
		||||
# Minimum value: 1
 | 
			
		||||
#connection_retry_interval = 1
 | 
			
		||||
 | 
			
		||||
# Increase the connection_retry_interval by this many seconds
 | 
			
		||||
# after each unsuccessful failover attempt. (integer value)
 | 
			
		||||
# Minimum value: 0
 | 
			
		||||
#connection_retry_backoff = 2
 | 
			
		||||
 | 
			
		||||
# Maximum limit for connection_retry_interval +
 | 
			
		||||
# connection_retry_backoff (integer value)
 | 
			
		||||
# Minimum value: 1
 | 
			
		||||
#connection_retry_interval_max = 30
 | 
			
		||||
 | 
			
		||||
# Time to pause between re-connecting an AMQP 1.0 link that
 | 
			
		||||
# failed due to a recoverable error. (integer value)
 | 
			
		||||
# Minimum value: 1
 | 
			
		||||
#link_retry_delay = 10
 | 
			
		||||
 | 
			
		||||
# The deadline for an rpc reply message delivery. Only used
 | 
			
		||||
# when caller does not provide a timeout expiry. (integer
 | 
			
		||||
# value)
 | 
			
		||||
# Minimum value: 5
 | 
			
		||||
#default_reply_timeout = 30
 | 
			
		||||
 | 
			
		||||
# The deadline for an rpc cast or call message delivery. Only
 | 
			
		||||
# used when caller does not provide a timeout expiry. (integer
 | 
			
		||||
# value)
 | 
			
		||||
# Minimum value: 5
 | 
			
		||||
#default_send_timeout = 30
 | 
			
		||||
 | 
			
		||||
# The deadline for a sent notification message delivery. Only
 | 
			
		||||
# used when caller does not provide a timeout expiry. (integer
 | 
			
		||||
# value)
 | 
			
		||||
# Minimum value: 5
 | 
			
		||||
#default_notify_timeout = 30
 | 
			
		||||
 | 
			
		||||
# Indicates the addressing mode used by the driver.
 | 
			
		||||
# Permitted values:
 | 
			
		||||
# 'legacy'   - use legacy non-routable addressing
 | 
			
		||||
# 'routable' - use routable addresses
 | 
			
		||||
# 'dynamic'  - use legacy addresses if the message bus does
 | 
			
		||||
# not support routing otherwise use routable addressing
 | 
			
		||||
# (string value)
 | 
			
		||||
#addressing_mode = dynamic
 | 
			
		||||
 | 
			
		||||
# address prefix used when sending to a specific server
 | 
			
		||||
# (string value)
 | 
			
		||||
# Deprecated group/name - [amqp1]/server_request_prefix
 | 
			
		||||
#server_request_prefix = exclusive
 | 
			
		||||
 | 
			
		||||
# address prefix used when broadcasting to all servers (string
 | 
			
		||||
# value)
 | 
			
		||||
# Deprecated group/name - [amqp1]/broadcast_prefix
 | 
			
		||||
#broadcast_prefix = broadcast
 | 
			
		||||
 | 
			
		||||
# address prefix when sending to any server in group (string
 | 
			
		||||
# value)
 | 
			
		||||
# Deprecated group/name - [amqp1]/group_request_prefix
 | 
			
		||||
#group_request_prefix = unicast
 | 
			
		||||
 | 
			
		||||
# Address prefix for all generated RPC addresses (string
 | 
			
		||||
# value)
 | 
			
		||||
#rpc_address_prefix = openstack.org/om/rpc
 | 
			
		||||
 | 
			
		||||
# Address prefix for all generated Notification addresses
 | 
			
		||||
# (string value)
 | 
			
		||||
#notify_address_prefix = openstack.org/om/notify
 | 
			
		||||
 | 
			
		||||
# Appended to the address prefix when sending a fanout
 | 
			
		||||
# message. Used by the message bus to identify fanout
 | 
			
		||||
# messages. (string value)
 | 
			
		||||
#multicast_address = multicast
 | 
			
		||||
 | 
			
		||||
# Appended to the address prefix when sending to a particular
 | 
			
		||||
# RPC/Notification server. Used by the message bus to identify
 | 
			
		||||
# messages sent to a single destination. (string value)
 | 
			
		||||
#unicast_address = unicast
 | 
			
		||||
 | 
			
		||||
# Appended to the address prefix when sending to a group of
 | 
			
		||||
# consumers. Used by the message bus to identify messages that
 | 
			
		||||
# should be delivered in a round-robin fashion across
 | 
			
		||||
# consumers. (string value)
 | 
			
		||||
#anycast_address = anycast
 | 
			
		||||
 | 
			
		||||
# Exchange name used in notification addresses.
 | 
			
		||||
# Exchange name resolution precedence:
 | 
			
		||||
# Target.exchange if set
 | 
			
		||||
# else default_notification_exchange if set
 | 
			
		||||
# else control_exchange if set
 | 
			
		||||
# else 'notify' (string value)
 | 
			
		||||
#default_notification_exchange = <None>
 | 
			
		||||
 | 
			
		||||
# Exchange name used in RPC addresses.
 | 
			
		||||
# Exchange name resolution precedence:
 | 
			
		||||
# Target.exchange if set
 | 
			
		||||
# else default_rpc_exchange if set
 | 
			
		||||
# else control_exchange if set
 | 
			
		||||
# else 'rpc' (string value)
 | 
			
		||||
#default_rpc_exchange = <None>
 | 
			
		||||
 | 
			
		||||
# Window size for incoming RPC Reply messages. (integer value)
 | 
			
		||||
# Minimum value: 1
 | 
			
		||||
#reply_link_credit = 200
 | 
			
		||||
 | 
			
		||||
# Window size for incoming RPC Request messages (integer
 | 
			
		||||
# value)
 | 
			
		||||
# Minimum value: 1
 | 
			
		||||
#rpc_server_credit = 100
 | 
			
		||||
 | 
			
		||||
# Window size for incoming Notification messages (integer
 | 
			
		||||
# value)
 | 
			
		||||
# Minimum value: 1
 | 
			
		||||
#notify_server_credit = 100
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[oslo_messaging_notifications]
 | 
			
		||||
 | 
			
		||||
@@ -781,7 +984,7 @@
 | 
			
		||||
#kombu_reconnect_delay = 1.0
 | 
			
		||||
 | 
			
		||||
# EXPERIMENTAL: Possible values are: gzip, bz2. If not set
 | 
			
		||||
# compression will not be used. This option may notbe
 | 
			
		||||
# compression will not be used. This option may not be
 | 
			
		||||
# available in future versions. (string value)
 | 
			
		||||
#kombu_compression = <None>
 | 
			
		||||
 | 
			
		||||
@@ -1113,6 +1316,13 @@
 | 
			
		||||
# Deprecated group/name - [DEFAULT]/rpc_zmq_serialization
 | 
			
		||||
#rpc_zmq_serialization = json
 | 
			
		||||
 | 
			
		||||
# This option configures round-robin mode in zmq socket. True
 | 
			
		||||
# means not keeping a queue when server side disconnects.
 | 
			
		||||
# False means to keep queue and messages even if server is
 | 
			
		||||
# disconnected, when the server appears we send all
 | 
			
		||||
# accumulated messages to it. (boolean value)
 | 
			
		||||
#zmq_immediate = false
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[oslo_policy]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,8 @@ class Instance(base.APIBase):
 | 
			
		||||
    between the internal object model and the API representation of
 | 
			
		||||
    a instance.
 | 
			
		||||
    """
 | 
			
		||||
    id = wsme.wsattr(wtypes.IntegerType(minimum=1))
 | 
			
		||||
    """The ID of the instance"""
 | 
			
		||||
 | 
			
		||||
    uuid = types.uuid
 | 
			
		||||
    """The UUID of the instance"""
 | 
			
		||||
@@ -55,6 +57,15 @@ class Instance(base.APIBase):
 | 
			
		||||
    availability_zone = wtypes.text
 | 
			
		||||
    """The availability zone of the instance"""
 | 
			
		||||
 | 
			
		||||
    instance_type_id = wsme.wsattr(wtypes.IntegerType(minimum=1))
 | 
			
		||||
    """The instance type ID of the instance"""
 | 
			
		||||
 | 
			
		||||
    image_uuid = types.uuid
 | 
			
		||||
    """The image UUID of the instance"""
 | 
			
		||||
 | 
			
		||||
    network_uuid = types.uuid
 | 
			
		||||
    """The network UUID of the instance"""
 | 
			
		||||
 | 
			
		||||
    links = wsme.wsattr([link.Link], readonly=True)
 | 
			
		||||
    """A list containing a self link"""
 | 
			
		||||
 | 
			
		||||
@@ -129,9 +140,11 @@ class InstanceController(rest.RestController):
 | 
			
		||||
        # Set the HTTP Location Header
 | 
			
		||||
        pecan.response.location = link.build_url('instance', instance_obj.uuid)
 | 
			
		||||
 | 
			
		||||
        new_instance = pecan.request.rpcapi.create_instance(
 | 
			
		||||
            pecan.request.context, instance_obj)
 | 
			
		||||
        return Instance.convert_with_links(new_instance)
 | 
			
		||||
        pecan.request.rpcapi.create_instance(pecan.request.context,
 | 
			
		||||
                                             instance_obj)
 | 
			
		||||
        instance_obj.status = 'building'
 | 
			
		||||
        instance_obj.save()
 | 
			
		||||
        return Instance.convert_with_links(instance_obj)
 | 
			
		||||
 | 
			
		||||
    @expose.expose(None, types.uuid, status_code=http_client.NO_CONTENT)
 | 
			
		||||
    def delete(self, instance_uuid):
 | 
			
		||||
@@ -141,4 +154,5 @@ class InstanceController(rest.RestController):
 | 
			
		||||
        """
 | 
			
		||||
        rpc_instance = objects.Instance.get(pecan.request.context,
 | 
			
		||||
                                            instance_uuid)
 | 
			
		||||
        rpc_instance.destroy()
 | 
			
		||||
        pecan.request.rpcapi.delete_instance(pecan.request.context,
 | 
			
		||||
                                             rpc_instance)
 | 
			
		||||
 
 | 
			
		||||
@@ -155,6 +155,10 @@ class InstanceNotFound(NotFound):
 | 
			
		||||
    msg_fmt = _("Instance %(instance)s could not be found.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InstanceDeployFailure(Invalid):
 | 
			
		||||
    msg_fmt = _("Failed to deploy instance: %(reason)s")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NoFreeEngineWorker(TemporaryFailure):
 | 
			
		||||
    _msg_fmt = _('Requested action cannot be performed due to lack of free '
 | 
			
		||||
                 'engine workers.')
 | 
			
		||||
 
 | 
			
		||||
@@ -1,61 +1,148 @@
 | 
			
		||||
# 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
 | 
			
		||||
# Copyright 2016 Huawei Technologies Co.,LTD.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#    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
 | 
			
		||||
#
 | 
			
		||||
# 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.
 | 
			
		||||
#         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.
 | 
			
		||||
 | 
			
		||||
from ironicclient import client
 | 
			
		||||
from oslo_config import cfg
 | 
			
		||||
from oslo_log import log as logging
 | 
			
		||||
from oslo_utils import importutils
 | 
			
		||||
 | 
			
		||||
from nimble.common import keystone
 | 
			
		||||
from nimble.common import exception
 | 
			
		||||
from nimble.common.i18n import _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
CONF = cfg.CONF
 | 
			
		||||
 | 
			
		||||
# 1.11 is API version, which support 'enroll' state
 | 
			
		||||
DEFAULT_IRONIC_API_VERSION = '1.11'
 | 
			
		||||
ironic = None
 | 
			
		||||
 | 
			
		||||
IRONIC_GROUP = 'ironic'
 | 
			
		||||
 | 
			
		||||
IRONIC_SESSION = None
 | 
			
		||||
LEGACY_MAP = {
 | 
			
		||||
    'auth_url': 'os_auth_url',
 | 
			
		||||
    'username': 'os_username',
 | 
			
		||||
    'password': 'os_password',
 | 
			
		||||
    'tenant_name': 'os_tenant_name'
 | 
			
		||||
}
 | 
			
		||||
# The API version required by the Ironic driver
 | 
			
		||||
IRONIC_API_VERSION = (1, 21)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_client(token=None,
 | 
			
		||||
               api_version=DEFAULT_IRONIC_API_VERSION):  # pragma: no cover
 | 
			
		||||
    """Get Ironic client instance."""
 | 
			
		||||
    # NOTE: To support standalone ironic without keystone
 | 
			
		||||
    if CONF.ironic.auth_strategy == 'noauth':
 | 
			
		||||
        args = {'token': 'noauth',
 | 
			
		||||
                'endpoint': CONF.ironic.ironic_url}
 | 
			
		||||
    else:
 | 
			
		||||
        global IRONIC_SESSION
 | 
			
		||||
        if not IRONIC_SESSION:
 | 
			
		||||
            IRONIC_SESSION = keystone.get_session(
 | 
			
		||||
                IRONIC_GROUP, legacy_mapping=LEGACY_MAP)
 | 
			
		||||
        if token is None:
 | 
			
		||||
            args = {'session': IRONIC_SESSION,
 | 
			
		||||
                    'region_name': CONF.ironic.os_region}
 | 
			
		||||
class IronicClientWrapper(object):
 | 
			
		||||
    """Ironic client wrapper class that encapsulates authentication logic."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        """Initialise the IronicClientWrapper for use.
 | 
			
		||||
 | 
			
		||||
        Initialise IronicClientWrapper by loading ironicclient
 | 
			
		||||
        dynamically so that ironicclient is not a dependency for
 | 
			
		||||
        Nimble.
 | 
			
		||||
        """
 | 
			
		||||
        global ironic
 | 
			
		||||
        if ironic is None:
 | 
			
		||||
            ironic = importutils.import_module('ironicclient')
 | 
			
		||||
            # NOTE(deva): work around a lack of symbols in the current version.
 | 
			
		||||
            if not hasattr(ironic, 'exc'):
 | 
			
		||||
                ironic.exc = importutils.import_module('ironicclient.exc')
 | 
			
		||||
            if not hasattr(ironic, 'client'):
 | 
			
		||||
                ironic.client = importutils.import_module(
 | 
			
		||||
                    'ironicclient.client')
 | 
			
		||||
        self._cached_client = None
 | 
			
		||||
 | 
			
		||||
    def _invalidate_cached_client(self):
 | 
			
		||||
        """Tell the wrapper to invalidate the cached ironic-client."""
 | 
			
		||||
        self._cached_client = None
 | 
			
		||||
 | 
			
		||||
    def _get_client(self, retry_on_conflict=True):
 | 
			
		||||
        max_retries = CONF.ironic.api_max_retries if retry_on_conflict else 1
 | 
			
		||||
        retry_interval = (CONF.ironic.api_retry_interval
 | 
			
		||||
                          if retry_on_conflict else 0)
 | 
			
		||||
 | 
			
		||||
        # If we've already constructed a valid, authed client, just return
 | 
			
		||||
        # that.
 | 
			
		||||
        if retry_on_conflict and self._cached_client is not None:
 | 
			
		||||
            return self._cached_client
 | 
			
		||||
 | 
			
		||||
        auth_token = CONF.ironic.admin_auth_token
 | 
			
		||||
        if auth_token is None:
 | 
			
		||||
            kwargs = {'os_username': CONF.ironic.admin_username,
 | 
			
		||||
                      'os_password': CONF.ironic.admin_password,
 | 
			
		||||
                      'os_auth_url': CONF.ironic.admin_url,
 | 
			
		||||
                      'os_tenant_name': CONF.ironic.admin_tenant_name,
 | 
			
		||||
                      'os_service_type': 'baremetal',
 | 
			
		||||
                      'os_endpoint_type': 'public',
 | 
			
		||||
                      'ironic_url': CONF.ironic.api_endpoint}
 | 
			
		||||
        else:
 | 
			
		||||
            ironic_url = IRONIC_SESSION.get_endpoint(
 | 
			
		||||
                service_type=CONF.ironic.os_service_type,
 | 
			
		||||
                endpoint_type=CONF.ironic.os_endpoint_type,
 | 
			
		||||
                region_name=CONF.ironic.os_region
 | 
			
		||||
            )
 | 
			
		||||
            args = {'token': token,
 | 
			
		||||
                    'endpoint': ironic_url}
 | 
			
		||||
    args['os_ironic_api_version'] = api_version
 | 
			
		||||
    args['max_retries'] = CONF.ironic.max_retries
 | 
			
		||||
    args['retry_interval'] = CONF.ironic.retry_interval
 | 
			
		||||
    return client.Client(1, **args)
 | 
			
		||||
            kwargs = {'os_auth_token': auth_token,
 | 
			
		||||
                      'ironic_url': CONF.ironic.api_endpoint}
 | 
			
		||||
 | 
			
		||||
        if CONF.ironic.cafile:
 | 
			
		||||
            kwargs['os_cacert'] = CONF.ironic.cafile
 | 
			
		||||
            # Set the old option for compat with old clients
 | 
			
		||||
            kwargs['ca_file'] = CONF.ironic.cafile
 | 
			
		||||
 | 
			
		||||
        # Retries for Conflict exception
 | 
			
		||||
        kwargs['max_retries'] = max_retries
 | 
			
		||||
        kwargs['retry_interval'] = retry_interval
 | 
			
		||||
        kwargs['os_ironic_api_version'] = '%d.%d' % IRONIC_API_VERSION
 | 
			
		||||
        try:
 | 
			
		||||
            cli = ironic.client.get_client(IRONIC_API_VERSION[0], **kwargs)
 | 
			
		||||
            # Cache the client so we don't have to reconstruct and
 | 
			
		||||
            # reauthenticate it every time we need it.
 | 
			
		||||
            if retry_on_conflict:
 | 
			
		||||
                self._cached_client = cli
 | 
			
		||||
 | 
			
		||||
        except ironic.exc.Unauthorized:
 | 
			
		||||
            msg = _("Unable to authenticate Ironic client.")
 | 
			
		||||
            LOG.error(msg)
 | 
			
		||||
            raise exception.NimbleException(msg)
 | 
			
		||||
 | 
			
		||||
        return cli
 | 
			
		||||
 | 
			
		||||
    def _multi_getattr(self, obj, attr):
 | 
			
		||||
        """Support nested attribute path for getattr().
 | 
			
		||||
 | 
			
		||||
        :param obj: Root object.
 | 
			
		||||
        :param attr: Path of final attribute to get. E.g., "a.b.c.d"
 | 
			
		||||
 | 
			
		||||
        :returns: The value of the final named attribute.
 | 
			
		||||
        :raises: AttributeError will be raised if the path is invalid.
 | 
			
		||||
        """
 | 
			
		||||
        for attribute in attr.split("."):
 | 
			
		||||
            obj = getattr(obj, attribute)
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def call(self, method, *args, **kwargs):
 | 
			
		||||
        """Call an Ironic client method and retry on stale token.
 | 
			
		||||
 | 
			
		||||
        :param method: Name of the client method to call as a string.
 | 
			
		||||
        :param args: Client method arguments.
 | 
			
		||||
        :param kwargs: Client method keyword arguments.
 | 
			
		||||
        :param retry_on_conflict: Boolean value. Whether the request should be
 | 
			
		||||
                                  retried in case of a conflict error
 | 
			
		||||
                                  (HTTP 409) or not. If retry_on_conflict is
 | 
			
		||||
                                  False the cached instance of the client
 | 
			
		||||
                                  won't be used. Defaults to True.
 | 
			
		||||
        """
 | 
			
		||||
        retry_on_conflict = kwargs.pop('retry_on_conflict', True)
 | 
			
		||||
 | 
			
		||||
        # NOTE(dtantsur): allow for authentication retry, other retries are
 | 
			
		||||
        # handled by ironicclient starting with 0.8.0
 | 
			
		||||
        for attempt in range(2):
 | 
			
		||||
            client = self._get_client(retry_on_conflict=retry_on_conflict)
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                return self._multi_getattr(client, method)(*args, **kwargs)
 | 
			
		||||
            except ironic.exc.Unauthorized:
 | 
			
		||||
                # In this case, the authorization token of the cached
 | 
			
		||||
                # ironic-client probably expired. So invalidate the cached
 | 
			
		||||
                # client and the next try will start with a fresh one.
 | 
			
		||||
                if not attempt:
 | 
			
		||||
                    self._invalidate_cached_client()
 | 
			
		||||
                    LOG.debug("The Ironic client became unauthorized. "
 | 
			
		||||
                              "Will attempt to reauthorize and try again.")
 | 
			
		||||
                else:
 | 
			
		||||
                    # This code should be unreachable actually
 | 
			
		||||
                    raise
 | 
			
		||||
 
 | 
			
		||||
@@ -11,11 +11,12 @@
 | 
			
		||||
# under the License.
 | 
			
		||||
 | 
			
		||||
from neutronclient.v2_0 import client as clientv20
 | 
			
		||||
from oslo_log import log as logging
 | 
			
		||||
 | 
			
		||||
from nimble.common import keystone
 | 
			
		||||
from nimble.conf import CONF
 | 
			
		||||
 | 
			
		||||
DEFAULT_NEUTRON_URL = 'http://%s:9696' % CONF.my_ip
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
_NEUTRON_SESSION = None
 | 
			
		||||
 | 
			
		||||
@@ -30,33 +31,41 @@ def _get_neutron_session():
 | 
			
		||||
def get_client(token=None):
 | 
			
		||||
    params = {'retries': CONF.neutron.retries}
 | 
			
		||||
    url = CONF.neutron.url
 | 
			
		||||
    if CONF.neutron.auth_strategy == 'noauth':
 | 
			
		||||
        params['endpoint_url'] = url or DEFAULT_NEUTRON_URL
 | 
			
		||||
        params['auth_strategy'] = 'noauth'
 | 
			
		||||
    session = _get_neutron_session()
 | 
			
		||||
    if token is None:
 | 
			
		||||
        params['session'] = session
 | 
			
		||||
        # NOTE(pas-ha) endpoint_override==None will auto-discover
 | 
			
		||||
        # endpoint from Keystone catalog.
 | 
			
		||||
        # Region is needed only in this case.
 | 
			
		||||
        # SSL related options are ignored as they are already embedded
 | 
			
		||||
        # in keystoneauth Session object
 | 
			
		||||
        if url:
 | 
			
		||||
            params['endpoint_override'] = url
 | 
			
		||||
        else:
 | 
			
		||||
            params['region_name'] = CONF.keystone.region_name
 | 
			
		||||
    else:
 | 
			
		||||
        params['token'] = token
 | 
			
		||||
        params['endpoint_url'] = url or keystone.get_service_url(
 | 
			
		||||
            session, service_type='network')
 | 
			
		||||
        params.update({
 | 
			
		||||
            'timeout': CONF.neutron.url_timeout or CONF.neutron.timeout,
 | 
			
		||||
            'timeout': CONF.neutron.url_timeout,
 | 
			
		||||
            'insecure': CONF.neutron.insecure,
 | 
			
		||||
            'ca_cert': CONF.neutron.cafile})
 | 
			
		||||
    else:
 | 
			
		||||
        session = _get_neutron_session()
 | 
			
		||||
        if token is None:
 | 
			
		||||
            params['session'] = session
 | 
			
		||||
            # NOTE(pas-ha) endpoint_override==None will auto-discover
 | 
			
		||||
            # endpoint from Keystone catalog.
 | 
			
		||||
            # Region is needed only in this case.
 | 
			
		||||
            # SSL related options are ignored as they are already embedded
 | 
			
		||||
            # in keystoneauth Session object
 | 
			
		||||
            if url:
 | 
			
		||||
                params['endpoint_override'] = url
 | 
			
		||||
            else:
 | 
			
		||||
                params['region_name'] = CONF.keystone.region_name
 | 
			
		||||
        else:
 | 
			
		||||
            params['token'] = token
 | 
			
		||||
            params['endpoint_url'] = url or keystone.get_service_url(
 | 
			
		||||
                session, service_type='network')
 | 
			
		||||
            params.update({
 | 
			
		||||
                'timeout': CONF.neutron.url_timeout or CONF.neutron.timeout,
 | 
			
		||||
                'insecure': CONF.neutron.insecure,
 | 
			
		||||
                'ca_cert': CONF.neutron.cafile})
 | 
			
		||||
 | 
			
		||||
    return clientv20.Client(**params)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_ports(context, network_uuid, macs):
 | 
			
		||||
    """Create neutron port."""
 | 
			
		||||
 | 
			
		||||
    client = get_client(context.auth_token)
 | 
			
		||||
    body = {
 | 
			
		||||
        'port': {
 | 
			
		||||
            'network_id': network_uuid,
 | 
			
		||||
            'mac_address': macs
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    port = client.create_port(body)
 | 
			
		||||
 | 
			
		||||
    return port
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,9 @@ from nimble.conf import api
 | 
			
		||||
from nimble.conf import database
 | 
			
		||||
from nimble.conf import default
 | 
			
		||||
from nimble.conf import engine
 | 
			
		||||
from nimble.conf import ironic
 | 
			
		||||
from nimble.conf import keystone
 | 
			
		||||
from nimble.conf import neutron
 | 
			
		||||
 | 
			
		||||
CONF = cfg.CONF
 | 
			
		||||
 | 
			
		||||
@@ -26,3 +29,6 @@ api.register_opts(CONF)
 | 
			
		||||
database.register_opts(CONF)
 | 
			
		||||
default.register_opts(CONF)
 | 
			
		||||
engine.register_opts(CONF)
 | 
			
		||||
ironic.register_opts(CONF)
 | 
			
		||||
keystone.register_opts(CONF)
 | 
			
		||||
neutron.register_opts(CONF)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										103
									
								
								nimble/conf/ironic.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								nimble/conf/ironic.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
# Copyright 2015 Intel Corporation
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
from oslo_config import cfg
 | 
			
		||||
 | 
			
		||||
opt_group = cfg.OptGroup(
 | 
			
		||||
    'ironic',
 | 
			
		||||
    title='Ironic Options',
 | 
			
		||||
    help="""
 | 
			
		||||
Configuration options for Ironic driver (Bare Metal).
 | 
			
		||||
If using the Ironic driver following options must be set:
 | 
			
		||||
* admin_url
 | 
			
		||||
* admin_tenant_name
 | 
			
		||||
* admin_username
 | 
			
		||||
* admin_password
 | 
			
		||||
* api_endpoint
 | 
			
		||||
""")
 | 
			
		||||
 | 
			
		||||
opts = [
 | 
			
		||||
    cfg.StrOpt(
 | 
			
		||||
        # TODO(raj_singh): Get this value from keystone service catalog
 | 
			
		||||
        'api_endpoint',
 | 
			
		||||
        sample_default='http://ironic.example.org:6385/',
 | 
			
		||||
        help='URL for the Ironic API endpoint'),
 | 
			
		||||
    cfg.StrOpt(
 | 
			
		||||
        'admin_username',
 | 
			
		||||
        help='Ironic keystone admin username'),
 | 
			
		||||
    cfg.StrOpt(
 | 
			
		||||
        'admin_password',
 | 
			
		||||
        secret=True,
 | 
			
		||||
        help='Ironic keystone admin password'),
 | 
			
		||||
    cfg.StrOpt(
 | 
			
		||||
        'admin_auth_token',
 | 
			
		||||
        secret=True,
 | 
			
		||||
        deprecated_for_removal=True,
 | 
			
		||||
        help="""
 | 
			
		||||
Ironic keystone auth token. This option is deprecated and
 | 
			
		||||
admin_username, admin_password and admin_tenant_name options
 | 
			
		||||
are used for authorization.
 | 
			
		||||
"""),
 | 
			
		||||
    cfg.StrOpt(
 | 
			
		||||
        # TODO(raj_singh): Change this option admin_url->auth_url to make it
 | 
			
		||||
        # consistent with other clients (Neutron, Cinder). It requires lot
 | 
			
		||||
        # of work in Ironic client to make this happen.
 | 
			
		||||
        'admin_url',
 | 
			
		||||
        help='Keystone public API endpoint'),
 | 
			
		||||
    cfg.StrOpt(
 | 
			
		||||
        'cafile',
 | 
			
		||||
        default=None,
 | 
			
		||||
        help="""
 | 
			
		||||
Path to the PEM encoded Certificate Authority file to be used when verifying
 | 
			
		||||
HTTPs connections with the Ironic driver. By default this option is not used.
 | 
			
		||||
 | 
			
		||||
Possible values:
 | 
			
		||||
 | 
			
		||||
* None - Default
 | 
			
		||||
* Path to the CA file
 | 
			
		||||
"""),
 | 
			
		||||
    cfg.StrOpt(
 | 
			
		||||
        'admin_tenant_name',
 | 
			
		||||
        help='Ironic keystone tenant name'),
 | 
			
		||||
    cfg.IntOpt(
 | 
			
		||||
        'api_max_retries',
 | 
			
		||||
        # TODO(raj_singh): Change this default to some sensible number
 | 
			
		||||
        default=60,
 | 
			
		||||
        min=0,
 | 
			
		||||
        help="""
 | 
			
		||||
The number of times to retry when a request conflicts.
 | 
			
		||||
If set to 0, only try once, no retries.
 | 
			
		||||
 | 
			
		||||
Related options:
 | 
			
		||||
 | 
			
		||||
* api_retry_interval
 | 
			
		||||
"""),
 | 
			
		||||
    cfg.IntOpt(
 | 
			
		||||
        'api_retry_interval',
 | 
			
		||||
        default=2,
 | 
			
		||||
        min=0,
 | 
			
		||||
        help="""
 | 
			
		||||
The number of seconds to wait before retrying the request.
 | 
			
		||||
 | 
			
		||||
Related options:
 | 
			
		||||
 | 
			
		||||
* api_max_retries
 | 
			
		||||
"""),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def register_opts(conf):
 | 
			
		||||
    conf.register_group(opt_group)
 | 
			
		||||
    conf.register_opts(opts, group=opt_group)
 | 
			
		||||
							
								
								
									
										26
									
								
								nimble/conf/keystone.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								nimble/conf/keystone.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
#
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
from oslo_config import cfg
 | 
			
		||||
 | 
			
		||||
from nimble.common.i18n import _
 | 
			
		||||
 | 
			
		||||
opts = [
 | 
			
		||||
    cfg.StrOpt('region_name',
 | 
			
		||||
               help=_('The region used for getting endpoints of OpenStack'
 | 
			
		||||
                      ' services.')),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def register_opts(conf):
 | 
			
		||||
    conf.register_opts(opts, group='keystone')
 | 
			
		||||
							
								
								
									
										41
									
								
								nimble/conf/neutron.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								nimble/conf/neutron.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
#
 | 
			
		||||
#    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.
 | 
			
		||||
 | 
			
		||||
from oslo_config import cfg
 | 
			
		||||
 | 
			
		||||
from nimble.common.i18n import _
 | 
			
		||||
from nimble.conf import auth
 | 
			
		||||
 | 
			
		||||
opts = [
 | 
			
		||||
    cfg.StrOpt('url',
 | 
			
		||||
               help=_("URL for connecting to neutron.")),
 | 
			
		||||
    cfg.IntOpt('url_timeout',
 | 
			
		||||
               default=30,
 | 
			
		||||
               help=_('Timeout value for connecting to neutron in seconds.')),
 | 
			
		||||
    cfg.IntOpt('retries',
 | 
			
		||||
               default=3,
 | 
			
		||||
               help=_('Client retries in the case of a failed request.')),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
opt_group = cfg.OptGroup(name='neutron',
 | 
			
		||||
                         title='Options for the neutron service')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def register_opts(conf):
 | 
			
		||||
    conf.register_group(opt_group)
 | 
			
		||||
    conf.register_opts(opts, group=opt_group)
 | 
			
		||||
    auth.register_auth_opts(conf, 'neutron')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def list_opts():
 | 
			
		||||
    return auth.add_auth_opts(opts)
 | 
			
		||||
@@ -16,6 +16,9 @@ import nimble.conf.api
 | 
			
		||||
import nimble.conf.database
 | 
			
		||||
import nimble.conf.default
 | 
			
		||||
import nimble.conf.engine
 | 
			
		||||
import nimble.conf.ironic
 | 
			
		||||
import nimble.conf.keystone
 | 
			
		||||
import nimble.conf.neutron
 | 
			
		||||
 | 
			
		||||
_default_opt_lists = [
 | 
			
		||||
    nimble.conf.default.api_opts,
 | 
			
		||||
@@ -29,6 +32,9 @@ _opts = [
 | 
			
		||||
    ('api', nimble.conf.api.opts),
 | 
			
		||||
    ('database', nimble.conf.database.opts),
 | 
			
		||||
    ('engine', nimble.conf.engine.opts),
 | 
			
		||||
    ('ironic', nimble.conf.ironic.opts),
 | 
			
		||||
    ('keystone', nimble.conf.keystone.opts),
 | 
			
		||||
    ('neutron', nimble.conf.neutron.opts),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -68,6 +68,8 @@ def upgrade():
 | 
			
		||||
        sa.Column('power_state', sa.String(length=255), nullable=True),
 | 
			
		||||
        sa.Column('task_state', sa.String(length=255), nullable=True),
 | 
			
		||||
        sa.Column('instance_type_id', sa.Integer(), nullable=True),
 | 
			
		||||
        sa.Column('image_uuid', sa.String(length=36), nullable=True),
 | 
			
		||||
        sa.Column('network_uuid', sa.String(length=36), nullable=True),
 | 
			
		||||
        sa.Column('launched_at', sa.DateTime(), nullable=True),
 | 
			
		||||
        sa.Column('terminated_at', sa.DateTime(), nullable=True),
 | 
			
		||||
        sa.Column('availability_zone', sa.String(length=255), nullable=True),
 | 
			
		||||
 
 | 
			
		||||
@@ -104,5 +104,7 @@ class Instance(Base):
 | 
			
		||||
    task_state = Column(String(255), nullable=True)
 | 
			
		||||
    instance_type_id = Column(Integer, nullable=True)
 | 
			
		||||
    availability_zone = Column(String(255), nullable=True)
 | 
			
		||||
    image_uuid = Column(String(36), nullable=True)
 | 
			
		||||
    network_uuid = Column(String(36), nullable=True)
 | 
			
		||||
    node_uuid = Column(String(36), nullable=True)
 | 
			
		||||
    extra = Column(Text, nullable=True)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								nimble/engine/baremetal/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								nimble/engine/baremetal/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										83
									
								
								nimble/engine/baremetal/ironic.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								nimble/engine/baremetal/ironic.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
# Copyright 2016 Huawei Technologies Co.,LTD.
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
from nimble.common import ironic
 | 
			
		||||
from nimble.engine.baremetal import ironic_states
 | 
			
		||||
 | 
			
		||||
_NODE_FIELDS = ('uuid', 'power_state', 'target_power_state', 'provision_state',
 | 
			
		||||
                'target_provision_state', 'last_error', 'maintenance',
 | 
			
		||||
                'properties', 'instance_uuid')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_macs_from_node(node_uuid):
 | 
			
		||||
    """List the MAC addresses from a node."""
 | 
			
		||||
    ironicclient = ironic.IronicClientWrapper()
 | 
			
		||||
    ports = ironicclient.call("node.list_ports", node_uuid)
 | 
			
		||||
    return [p.address for p in ports]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def plug_vifs(node_uuid, port_id):
 | 
			
		||||
    ironicclient = ironic.IronicClientWrapper()
 | 
			
		||||
    ports = ironicclient.call("node.list_ports", node_uuid)
 | 
			
		||||
    patch = [{'op': 'add',
 | 
			
		||||
              'path': '/extra/vif_port_id',
 | 
			
		||||
              'value': port_id}]
 | 
			
		||||
    ironicclient.call("port.update", ports[0].uuid, patch)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def set_instance_info(instance):
 | 
			
		||||
    ironicclient = ironic.IronicClientWrapper()
 | 
			
		||||
 | 
			
		||||
    patch = []
 | 
			
		||||
    # Associate the node with an instance
 | 
			
		||||
    patch.append({'path': '/instance_uuid', 'op': 'add',
 | 
			
		||||
                  'value': instance.uuid})
 | 
			
		||||
    # Add the required fields to deploy a node.
 | 
			
		||||
    patch.append({'path': '/instance_info/image_source', 'op': 'add',
 | 
			
		||||
                  'value': instance.image_uuid})
 | 
			
		||||
    patch.append({'path': '/instance_info/root_gb', 'op': 'add',
 | 
			
		||||
                  'value': '10'})
 | 
			
		||||
    patch.append({'path': '/instance_info/swap_mb', 'op': 'add',
 | 
			
		||||
                  'value': '0'})
 | 
			
		||||
    patch.append({'path': '/instance_info/display_name',
 | 
			
		||||
                  'op': 'add', 'value': instance.name})
 | 
			
		||||
    patch.append({'path': '/instance_info/vcpus', 'op': 'add',
 | 
			
		||||
                  'value': '1'})
 | 
			
		||||
    patch.append({'path': '/instance_info/memory_mb', 'op': 'add',
 | 
			
		||||
                  'value': '10240'})
 | 
			
		||||
    patch.append({'path': '/instance_info/local_gb', 'op': 'add',
 | 
			
		||||
                  'value': '10'})
 | 
			
		||||
 | 
			
		||||
    ironicclient.call("node.update", instance.node_uuid, patch)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def do_node_deploy(node_uuid):
 | 
			
		||||
    # trigger the node deploy
 | 
			
		||||
    ironicclient = ironic.IronicClientWrapper()
 | 
			
		||||
    ironicclient.call("node.set_provision_state", node_uuid,
 | 
			
		||||
                      ironic_states.ACTIVE)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_node_by_instance(instance_uuid):
 | 
			
		||||
    ironicclient = ironic.IronicClientWrapper()
 | 
			
		||||
    return ironicclient.call('node.get_by_instance_uuid',
 | 
			
		||||
                             instance_uuid, fields=_NODE_FIELDS)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def destroy_node(node_uuid):
 | 
			
		||||
    # trigger the node destroy
 | 
			
		||||
    ironicclient = ironic.IronicClientWrapper()
 | 
			
		||||
    ironicclient.call("node.set_provision_state", node_uuid,
 | 
			
		||||
                      ironic_states.DELETED)
 | 
			
		||||
							
								
								
									
										156
									
								
								nimble/engine/baremetal/ironic_states.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								nimble/engine/baremetal/ironic_states.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,156 @@
 | 
			
		||||
# Copyright (c) 2012 NTT DOCOMO, INC.
 | 
			
		||||
# Copyright 2010 OpenStack Foundation
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Mapping of bare metal node states.
 | 
			
		||||
 | 
			
		||||
Setting the node `power_state` is handled by the conductor's power
 | 
			
		||||
synchronization thread. Based on the power state retrieved from the driver
 | 
			
		||||
for the node, the state is set to POWER_ON or POWER_OFF, accordingly.
 | 
			
		||||
Should this fail, the `power_state` value is left unchanged, and the node
 | 
			
		||||
is placed into maintenance mode.
 | 
			
		||||
 | 
			
		||||
The `power_state` can also be set manually via the API. A failure to change
 | 
			
		||||
the state leaves the current state unchanged. The node is NOT placed into
 | 
			
		||||
maintenance mode in this case.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#####################
 | 
			
		||||
# Provisioning states
 | 
			
		||||
#####################
 | 
			
		||||
 | 
			
		||||
NOSTATE = None
 | 
			
		||||
""" No state information.
 | 
			
		||||
 | 
			
		||||
This state is used with power_state to represent a lack of knowledge of
 | 
			
		||||
power state, and in target_*_state fields when there is no target.
 | 
			
		||||
 | 
			
		||||
Prior to the Kilo release, Ironic set node.provision_state to NOSTATE
 | 
			
		||||
when the node was available for provisioning. During Kilo cycle, this was
 | 
			
		||||
changed to the AVAILABLE state.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
MANAGEABLE = 'manageable'
 | 
			
		||||
""" Node is in a manageable state.
 | 
			
		||||
This state indicates that Ironic has verified, at least once, that it had
 | 
			
		||||
sufficient information to manage the hardware. While in this state, the node
 | 
			
		||||
is not available for provisioning (it must be in the AVAILABLE state for that).
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
AVAILABLE = 'available'
 | 
			
		||||
""" Node is available for use and scheduling.
 | 
			
		||||
 | 
			
		||||
This state is replacing the NOSTATE state used prior to Kilo.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
ACTIVE = 'active'
 | 
			
		||||
""" Node is successfully deployed and associated with an instance. """
 | 
			
		||||
 | 
			
		||||
DEPLOYWAIT = 'wait call-back'
 | 
			
		||||
""" Node is waiting to be deployed.
 | 
			
		||||
 | 
			
		||||
This will be the node `provision_state` while the node is waiting for
 | 
			
		||||
the driver to finish deployment.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
DEPLOYING = 'deploying'
 | 
			
		||||
""" Node is ready to receive a deploy request, or is currently being deployed.
 | 
			
		||||
 | 
			
		||||
A node will have its `provision_state` set to DEPLOYING briefly before it
 | 
			
		||||
receives its initial deploy request. It will also move to this state from
 | 
			
		||||
DEPLOYWAIT after the callback is triggered and deployment is continued
 | 
			
		||||
(disk partitioning and image copying).
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
DEPLOYFAIL = 'deploy failed'
 | 
			
		||||
""" Node deployment failed. """
 | 
			
		||||
 | 
			
		||||
DEPLOYDONE = 'deploy complete'
 | 
			
		||||
""" Node was successfully deployed.
 | 
			
		||||
 | 
			
		||||
This is mainly a target provision state used during deployment. A successfully
 | 
			
		||||
deployed node should go to ACTIVE status.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
DELETING = 'deleting'
 | 
			
		||||
""" Node is actively being torn down. """
 | 
			
		||||
 | 
			
		||||
DELETED = 'deleted'
 | 
			
		||||
""" Node tear down was successful.
 | 
			
		||||
 | 
			
		||||
In Juno, target_provision_state was set to this value during node tear down.
 | 
			
		||||
In Kilo, this will be a transitory value of provision_state, and never
 | 
			
		||||
represented in target_provision_state.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
CLEANING = 'cleaning'
 | 
			
		||||
""" Node is being automatically cleaned to prepare it for provisioning. """
 | 
			
		||||
 | 
			
		||||
CLEANWAIT = 'clean wait'
 | 
			
		||||
""" Node is waiting for a clean step to be finished.
 | 
			
		||||
 | 
			
		||||
This will be the node's `provision_state` while the node is waiting for
 | 
			
		||||
the driver to finish a cleaning step.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
CLEANFAIL = 'clean failed'
 | 
			
		||||
""" Node failed cleaning. This requires operator intervention to resolve. """
 | 
			
		||||
 | 
			
		||||
ERROR = 'error'
 | 
			
		||||
""" An error occurred during node processing.
 | 
			
		||||
 | 
			
		||||
The `last_error` attribute of the node details should contain an error message.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
REBUILD = 'rebuild'
 | 
			
		||||
""" Node is to be rebuilt.
 | 
			
		||||
This is not used as a state, but rather as a "verb" when changing the node's
 | 
			
		||||
provision_state via the REST API.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
INSPECTING = 'inspecting'
 | 
			
		||||
""" Node is under inspection.
 | 
			
		||||
This is the provision state used when inspection is started. A successfully
 | 
			
		||||
inspected node shall transition to MANAGEABLE status.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
INSPECTFAIL = 'inspect failed'
 | 
			
		||||
""" Node inspection failed. """
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
##############
 | 
			
		||||
# Power states
 | 
			
		||||
##############
 | 
			
		||||
 | 
			
		||||
POWER_ON = 'power on'
 | 
			
		||||
""" Node is powered on. """
 | 
			
		||||
 | 
			
		||||
POWER_OFF = 'power off'
 | 
			
		||||
""" Node is powered off. """
 | 
			
		||||
 | 
			
		||||
REBOOT = 'rebooting'
 | 
			
		||||
""" Node is rebooting. """
 | 
			
		||||
 | 
			
		||||
##################
 | 
			
		||||
# Helper constants
 | 
			
		||||
##################
 | 
			
		||||
 | 
			
		||||
PROVISION_STATE_LIST = (NOSTATE, MANAGEABLE, AVAILABLE, ACTIVE, DEPLOYWAIT,
 | 
			
		||||
                        DEPLOYING, DEPLOYFAIL, DEPLOYDONE, DELETING, DELETED,
 | 
			
		||||
                        CLEANING, CLEANWAIT, CLEANFAIL, ERROR, REBUILD,
 | 
			
		||||
                        INSPECTING, INSPECTFAIL)
 | 
			
		||||
""" A list of all provision states. """
 | 
			
		||||
@@ -15,10 +15,15 @@
 | 
			
		||||
 | 
			
		||||
from oslo_log import log
 | 
			
		||||
import oslo_messaging as messaging
 | 
			
		||||
from oslo_service import loopingcall
 | 
			
		||||
from oslo_service import periodic_task
 | 
			
		||||
 | 
			
		||||
from nimble.common import exception
 | 
			
		||||
from nimble.common.i18n import _LI
 | 
			
		||||
from nimble.common import neutron
 | 
			
		||||
from nimble.conf import CONF
 | 
			
		||||
from nimble.engine.baremetal import ironic
 | 
			
		||||
from nimble.engine.baremetal import ironic_states
 | 
			
		||||
from nimble.engine import base_manager
 | 
			
		||||
 | 
			
		||||
MANAGER_TOPIC = 'nimble.engine_manager'
 | 
			
		||||
@@ -38,9 +43,74 @@ class EngineManager(base_manager.BaseEngineManager):
 | 
			
		||||
    def _sync_node_resources(self, context):
 | 
			
		||||
        LOG.info(_LI("During sync_node_resources."))
 | 
			
		||||
 | 
			
		||||
    def _build_networks(self, context, instance):
 | 
			
		||||
        macs = ironic.get_macs_from_node(instance.node_uuid)
 | 
			
		||||
        port = neutron.create_ports(context, instance.network_uuid, macs[0])
 | 
			
		||||
        ironic.plug_vifs(instance.node_uuid, port['port']['id'])
 | 
			
		||||
 | 
			
		||||
    def _wait_for_active(self, instance):
 | 
			
		||||
        """Wait for the node to be marked as ACTIVE in Ironic."""
 | 
			
		||||
 | 
			
		||||
        node = ironic.get_node_by_instance(instance.uuid)
 | 
			
		||||
        LOG.debug('Current ironic node state is %s', node.provision_state)
 | 
			
		||||
        if node.provision_state == ironic_states.ACTIVE:
 | 
			
		||||
            # job is done
 | 
			
		||||
            LOG.debug("Ironic node %(node)s is now ACTIVE",
 | 
			
		||||
                      dict(node=node.uuid))
 | 
			
		||||
            instance.status = ironic_states.ACTIVE
 | 
			
		||||
            instance.save()
 | 
			
		||||
            raise loopingcall.LoopingCallDone()
 | 
			
		||||
 | 
			
		||||
        if node.target_provision_state in (ironic_states.DELETED,
 | 
			
		||||
                                           ironic_states.AVAILABLE):
 | 
			
		||||
            # ironic is trying to delete it now
 | 
			
		||||
            raise exception.InstanceNotFound(instance_id=instance.uuid)
 | 
			
		||||
 | 
			
		||||
        if node.provision_state in (ironic_states.NOSTATE,
 | 
			
		||||
                                    ironic_states.AVAILABLE):
 | 
			
		||||
            # ironic already deleted it
 | 
			
		||||
            raise exception.InstanceNotFound(instance_id=instance.uuid)
 | 
			
		||||
 | 
			
		||||
        if node.provision_state == ironic_states.DEPLOYFAIL:
 | 
			
		||||
            # ironic failed to deploy
 | 
			
		||||
            msg = (_("Failed to provision instance %(inst)s: %(reason)s")
 | 
			
		||||
                   % {'inst': instance.uuid, 'reason': node.last_error})
 | 
			
		||||
            raise exception.InstanceDeployFailure(msg)
 | 
			
		||||
 | 
			
		||||
    def _build_instance(self, context, instance):
 | 
			
		||||
        ironic.set_instance_info(instance)
 | 
			
		||||
        ironic.do_node_deploy(instance.node_uuid)
 | 
			
		||||
 | 
			
		||||
        timer = loopingcall.FixedIntervalLoopingCall(self._wait_for_active,
 | 
			
		||||
                                                     instance)
 | 
			
		||||
        timer.start(interval=CONF.ironic.api_retry_interval).wait()
 | 
			
		||||
        LOG.info(_LI('Successfully provisioned Ironic node %s'),
 | 
			
		||||
                 instance.node_uuid)
 | 
			
		||||
 | 
			
		||||
    def _destroy_instance(self, context, instance):
 | 
			
		||||
        ironic.destroy_node(instance.node_uuid)
 | 
			
		||||
        LOG.info(_LI('Successfully destroyed Ironic node %s'),
 | 
			
		||||
                 instance.node_uuid)
 | 
			
		||||
 | 
			
		||||
    def create_instance(self, context, instance):
 | 
			
		||||
        """Signal to engine service to perform a deployment."""
 | 
			
		||||
        LOG.debug("During create instance.")
 | 
			
		||||
        instance.task_state = 'deploying'
 | 
			
		||||
        LOG.debug("Strating instance...")
 | 
			
		||||
        instance.status = 'building'
 | 
			
		||||
 | 
			
		||||
        # Scheduling...
 | 
			
		||||
        # instance.node_uuid = '8d22309b-b47a-41a7-80e3-e758fae9dedd'
 | 
			
		||||
        instance.save()
 | 
			
		||||
 | 
			
		||||
        self._build_networks(context, instance)
 | 
			
		||||
 | 
			
		||||
        self._build_instance(context, instance)
 | 
			
		||||
 | 
			
		||||
        return instance
 | 
			
		||||
 | 
			
		||||
    def delete_instance(self, context, instance):
 | 
			
		||||
        """Signal to engine service to delete an instance."""
 | 
			
		||||
        LOG.debug("Deleting instance...")
 | 
			
		||||
 | 
			
		||||
        self._destroy_instance(context, instance)
 | 
			
		||||
 | 
			
		||||
        instance.destroy()
 | 
			
		||||
 
 | 
			
		||||
@@ -52,4 +52,9 @@ class EngineAPI(object):
 | 
			
		||||
    def create_instance(self, context, instance):
 | 
			
		||||
        """Signal to engine service to perform a deployment."""
 | 
			
		||||
        cctxt = self.client.prepare(topic=self.topic, server=CONF.host)
 | 
			
		||||
        return cctxt.call(context, 'create_instance', instance=instance)
 | 
			
		||||
        return cctxt.cast(context, 'create_instance', instance=instance)
 | 
			
		||||
 | 
			
		||||
    def delete_instance(self, context, instance):
 | 
			
		||||
        """Signal to engine service to delete an instance."""
 | 
			
		||||
        cctxt = self.client.prepare(topic=self.topic, server=CONF.host)
 | 
			
		||||
        return cctxt.call(context, 'delete_instance', instance=instance)
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,8 @@ class Instance(base.NimbleObject, object_base.VersionedObjectDictCompat):
 | 
			
		||||
        'task_state': object_fields.StringField(nullable=True),
 | 
			
		||||
        'instance_type_id': object_fields.IntegerField(nullable=True),
 | 
			
		||||
        'availability_zone': object_fields.StringField(nullable=True),
 | 
			
		||||
        'image_uuid': object_fields.UUIDField(nullable=True),
 | 
			
		||||
        'network_uuid': object_fields.UUIDField(nullable=True),
 | 
			
		||||
        'node_uuid': object_fields.UUIDField(nullable=True),
 | 
			
		||||
        'extra': object_fields.FlexibleDictField(nullable=True),
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user