Building instance base codes
Change-Id: Ic34268df71625b39e588179e6586ca02466b5c6e
This commit is contained in:
parent
41d7272a4b
commit
d71cb7b50c
|
@ -248,6 +248,13 @@
|
||||||
# Deprecated group/name - [DEFAULT]/rpc_zmq_serialization
|
# Deprecated group/name - [DEFAULT]/rpc_zmq_serialization
|
||||||
#rpc_zmq_serialization = json
|
#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)
|
# Size of executor thread pool. (integer value)
|
||||||
# Deprecated group/name - [DEFAULT]/rpc_thread_pool_size
|
# Deprecated group/name - [DEFAULT]/rpc_thread_pool_size
|
||||||
#executor_thread_pool_size = 64
|
#executor_thread_pool_size = 64
|
||||||
|
@ -565,6 +572,82 @@
|
||||||
#sync_node_resource_interval = 60
|
#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]
|
[matchmaker_redis]
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -606,16 +689,34 @@
|
||||||
|
|
||||||
# Time in ms to wait between connection attempts. (integer
|
# Time in ms to wait between connection attempts. (integer
|
||||||
# value)
|
# value)
|
||||||
#wait_timeout = 5000
|
#wait_timeout = 2000
|
||||||
|
|
||||||
# Time in ms to wait before the transaction is killed.
|
# Time in ms to wait before the transaction is killed.
|
||||||
# (integer value)
|
# (integer value)
|
||||||
#check_timeout = 60000
|
#check_timeout = 20000
|
||||||
|
|
||||||
# Timeout in ms on blocking socket operations (integer value)
|
# Timeout in ms on blocking socket operations (integer value)
|
||||||
#socket_timeout = 10000
|
#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]
|
[oslo_concurrency]
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -641,22 +742,8 @@
|
||||||
# From oslo.messaging
|
# From oslo.messaging
|
||||||
#
|
#
|
||||||
|
|
||||||
# address prefix used when sending to a specific server
|
# Name for the AMQP container. must be globally unique.
|
||||||
# (string value)
|
# Defaults to a generated UUID (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)
|
|
||||||
# Deprecated group/name - [amqp1]/container_name
|
# Deprecated group/name - [amqp1]/container_name
|
||||||
#container_name = <None>
|
#container_name = <None>
|
||||||
|
|
||||||
|
@ -716,6 +803,122 @@
|
||||||
# Deprecated group/name - [amqp1]/password
|
# Deprecated group/name - [amqp1]/password
|
||||||
#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]
|
[oslo_messaging_notifications]
|
||||||
|
|
||||||
|
@ -781,7 +984,7 @@
|
||||||
#kombu_reconnect_delay = 1.0
|
#kombu_reconnect_delay = 1.0
|
||||||
|
|
||||||
# EXPERIMENTAL: Possible values are: gzip, bz2. If not set
|
# 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)
|
# available in future versions. (string value)
|
||||||
#kombu_compression = <None>
|
#kombu_compression = <None>
|
||||||
|
|
||||||
|
@ -1113,6 +1316,13 @@
|
||||||
# Deprecated group/name - [DEFAULT]/rpc_zmq_serialization
|
# Deprecated group/name - [DEFAULT]/rpc_zmq_serialization
|
||||||
#rpc_zmq_serialization = json
|
#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]
|
[oslo_policy]
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,8 @@ class Instance(base.APIBase):
|
||||||
between the internal object model and the API representation of
|
between the internal object model and the API representation of
|
||||||
a instance.
|
a instance.
|
||||||
"""
|
"""
|
||||||
|
id = wsme.wsattr(wtypes.IntegerType(minimum=1))
|
||||||
|
"""The ID of the instance"""
|
||||||
|
|
||||||
uuid = types.uuid
|
uuid = types.uuid
|
||||||
"""The UUID of the instance"""
|
"""The UUID of the instance"""
|
||||||
|
@ -55,6 +57,15 @@ class Instance(base.APIBase):
|
||||||
availability_zone = wtypes.text
|
availability_zone = wtypes.text
|
||||||
"""The availability zone of the instance"""
|
"""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)
|
links = wsme.wsattr([link.Link], readonly=True)
|
||||||
"""A list containing a self link"""
|
"""A list containing a self link"""
|
||||||
|
|
||||||
|
@ -129,9 +140,11 @@ class InstanceController(rest.RestController):
|
||||||
# Set the HTTP Location Header
|
# Set the HTTP Location Header
|
||||||
pecan.response.location = link.build_url('instance', instance_obj.uuid)
|
pecan.response.location = link.build_url('instance', instance_obj.uuid)
|
||||||
|
|
||||||
new_instance = pecan.request.rpcapi.create_instance(
|
pecan.request.rpcapi.create_instance(pecan.request.context,
|
||||||
pecan.request.context, instance_obj)
|
instance_obj)
|
||||||
return Instance.convert_with_links(new_instance)
|
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)
|
@expose.expose(None, types.uuid, status_code=http_client.NO_CONTENT)
|
||||||
def delete(self, instance_uuid):
|
def delete(self, instance_uuid):
|
||||||
|
@ -141,4 +154,5 @@ class InstanceController(rest.RestController):
|
||||||
"""
|
"""
|
||||||
rpc_instance = objects.Instance.get(pecan.request.context,
|
rpc_instance = objects.Instance.get(pecan.request.context,
|
||||||
instance_uuid)
|
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.")
|
msg_fmt = _("Instance %(instance)s could not be found.")
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceDeployFailure(Invalid):
|
||||||
|
msg_fmt = _("Failed to deploy instance: %(reason)s")
|
||||||
|
|
||||||
|
|
||||||
class NoFreeEngineWorker(TemporaryFailure):
|
class NoFreeEngineWorker(TemporaryFailure):
|
||||||
_msg_fmt = _('Requested action cannot be performed due to lack of free '
|
_msg_fmt = _('Requested action cannot be performed due to lack of free '
|
||||||
'engine workers.')
|
'engine workers.')
|
||||||
|
|
|
@ -1,61 +1,148 @@
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Copyright 2016 Huawei Technologies Co.,LTD.
|
||||||
# you may not use this file except in compliance with the License.
|
# All Rights Reserved.
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
#
|
||||||
# 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
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
#
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
# implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
# See the License for the specific language governing permissions and
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# limitations under the License.
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
from ironicclient import client
|
|
||||||
from oslo_config import cfg
|
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
|
CONF = cfg.CONF
|
||||||
|
|
||||||
# 1.11 is API version, which support 'enroll' state
|
ironic = None
|
||||||
DEFAULT_IRONIC_API_VERSION = '1.11'
|
|
||||||
|
|
||||||
IRONIC_GROUP = 'ironic'
|
# The API version required by the Ironic driver
|
||||||
|
IRONIC_API_VERSION = (1, 21)
|
||||||
IRONIC_SESSION = None
|
|
||||||
LEGACY_MAP = {
|
|
||||||
'auth_url': 'os_auth_url',
|
|
||||||
'username': 'os_username',
|
|
||||||
'password': 'os_password',
|
|
||||||
'tenant_name': 'os_tenant_name'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_client(token=None,
|
class IronicClientWrapper(object):
|
||||||
api_version=DEFAULT_IRONIC_API_VERSION): # pragma: no cover
|
"""Ironic client wrapper class that encapsulates authentication logic."""
|
||||||
"""Get Ironic client instance."""
|
|
||||||
# NOTE: To support standalone ironic without keystone
|
def __init__(self):
|
||||||
if CONF.ironic.auth_strategy == 'noauth':
|
"""Initialise the IronicClientWrapper for use.
|
||||||
args = {'token': 'noauth',
|
|
||||||
'endpoint': CONF.ironic.ironic_url}
|
Initialise IronicClientWrapper by loading ironicclient
|
||||||
else:
|
dynamically so that ironicclient is not a dependency for
|
||||||
global IRONIC_SESSION
|
Nimble.
|
||||||
if not IRONIC_SESSION:
|
"""
|
||||||
IRONIC_SESSION = keystone.get_session(
|
global ironic
|
||||||
IRONIC_GROUP, legacy_mapping=LEGACY_MAP)
|
if ironic is None:
|
||||||
if token is None:
|
ironic = importutils.import_module('ironicclient')
|
||||||
args = {'session': IRONIC_SESSION,
|
# NOTE(deva): work around a lack of symbols in the current version.
|
||||||
'region_name': CONF.ironic.os_region}
|
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:
|
else:
|
||||||
ironic_url = IRONIC_SESSION.get_endpoint(
|
kwargs = {'os_auth_token': auth_token,
|
||||||
service_type=CONF.ironic.os_service_type,
|
'ironic_url': CONF.ironic.api_endpoint}
|
||||||
endpoint_type=CONF.ironic.os_endpoint_type,
|
|
||||||
region_name=CONF.ironic.os_region
|
if CONF.ironic.cafile:
|
||||||
)
|
kwargs['os_cacert'] = CONF.ironic.cafile
|
||||||
args = {'token': token,
|
# Set the old option for compat with old clients
|
||||||
'endpoint': ironic_url}
|
kwargs['ca_file'] = CONF.ironic.cafile
|
||||||
args['os_ironic_api_version'] = api_version
|
|
||||||
args['max_retries'] = CONF.ironic.max_retries
|
# Retries for Conflict exception
|
||||||
args['retry_interval'] = CONF.ironic.retry_interval
|
kwargs['max_retries'] = max_retries
|
||||||
return client.Client(1, **args)
|
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.
|
# under the License.
|
||||||
|
|
||||||
from neutronclient.v2_0 import client as clientv20
|
from neutronclient.v2_0 import client as clientv20
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from nimble.common import keystone
|
from nimble.common import keystone
|
||||||
from nimble.conf import CONF
|
from nimble.conf import CONF
|
||||||
|
|
||||||
DEFAULT_NEUTRON_URL = 'http://%s:9696' % CONF.my_ip
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
_NEUTRON_SESSION = None
|
_NEUTRON_SESSION = None
|
||||||
|
|
||||||
|
@ -30,33 +31,41 @@ def _get_neutron_session():
|
||||||
def get_client(token=None):
|
def get_client(token=None):
|
||||||
params = {'retries': CONF.neutron.retries}
|
params = {'retries': CONF.neutron.retries}
|
||||||
url = CONF.neutron.url
|
url = CONF.neutron.url
|
||||||
if CONF.neutron.auth_strategy == 'noauth':
|
session = _get_neutron_session()
|
||||||
params['endpoint_url'] = url or DEFAULT_NEUTRON_URL
|
if token is None:
|
||||||
params['auth_strategy'] = 'noauth'
|
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({
|
params.update({
|
||||||
'timeout': CONF.neutron.url_timeout or CONF.neutron.timeout,
|
'timeout': CONF.neutron.url_timeout,
|
||||||
'insecure': CONF.neutron.insecure,
|
'insecure': CONF.neutron.insecure,
|
||||||
'ca_cert': CONF.neutron.cafile})
|
'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)
|
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 database
|
||||||
from nimble.conf import default
|
from nimble.conf import default
|
||||||
from nimble.conf import engine
|
from nimble.conf import engine
|
||||||
|
from nimble.conf import ironic
|
||||||
|
from nimble.conf import keystone
|
||||||
|
from nimble.conf import neutron
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
@ -26,3 +29,6 @@ api.register_opts(CONF)
|
||||||
database.register_opts(CONF)
|
database.register_opts(CONF)
|
||||||
default.register_opts(CONF)
|
default.register_opts(CONF)
|
||||||
engine.register_opts(CONF)
|
engine.register_opts(CONF)
|
||||||
|
ironic.register_opts(CONF)
|
||||||
|
keystone.register_opts(CONF)
|
||||||
|
neutron.register_opts(CONF)
|
||||||
|
|
|
@ -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)
|
|
@ -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')
|
|
@ -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.database
|
||||||
import nimble.conf.default
|
import nimble.conf.default
|
||||||
import nimble.conf.engine
|
import nimble.conf.engine
|
||||||
|
import nimble.conf.ironic
|
||||||
|
import nimble.conf.keystone
|
||||||
|
import nimble.conf.neutron
|
||||||
|
|
||||||
_default_opt_lists = [
|
_default_opt_lists = [
|
||||||
nimble.conf.default.api_opts,
|
nimble.conf.default.api_opts,
|
||||||
|
@ -29,6 +32,9 @@ _opts = [
|
||||||
('api', nimble.conf.api.opts),
|
('api', nimble.conf.api.opts),
|
||||||
('database', nimble.conf.database.opts),
|
('database', nimble.conf.database.opts),
|
||||||
('engine', nimble.conf.engine.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('power_state', sa.String(length=255), nullable=True),
|
||||||
sa.Column('task_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('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('launched_at', sa.DateTime(), nullable=True),
|
||||||
sa.Column('terminated_at', sa.DateTime(), nullable=True),
|
sa.Column('terminated_at', sa.DateTime(), nullable=True),
|
||||||
sa.Column('availability_zone', sa.String(length=255), 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)
|
task_state = Column(String(255), nullable=True)
|
||||||
instance_type_id = Column(Integer, nullable=True)
|
instance_type_id = Column(Integer, nullable=True)
|
||||||
availability_zone = Column(String(255), 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)
|
node_uuid = Column(String(36), nullable=True)
|
||||||
extra = Column(Text, nullable=True)
|
extra = Column(Text, nullable=True)
|
||||||
|
|
|
@ -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)
|
|
@ -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
|
from oslo_log import log
|
||||||
import oslo_messaging as messaging
|
import oslo_messaging as messaging
|
||||||
|
from oslo_service import loopingcall
|
||||||
from oslo_service import periodic_task
|
from oslo_service import periodic_task
|
||||||
|
|
||||||
|
from nimble.common import exception
|
||||||
from nimble.common.i18n import _LI
|
from nimble.common.i18n import _LI
|
||||||
|
from nimble.common import neutron
|
||||||
from nimble.conf import CONF
|
from nimble.conf import CONF
|
||||||
|
from nimble.engine.baremetal import ironic
|
||||||
|
from nimble.engine.baremetal import ironic_states
|
||||||
from nimble.engine import base_manager
|
from nimble.engine import base_manager
|
||||||
|
|
||||||
MANAGER_TOPIC = 'nimble.engine_manager'
|
MANAGER_TOPIC = 'nimble.engine_manager'
|
||||||
|
@ -38,9 +43,74 @@ class EngineManager(base_manager.BaseEngineManager):
|
||||||
def _sync_node_resources(self, context):
|
def _sync_node_resources(self, context):
|
||||||
LOG.info(_LI("During sync_node_resources."))
|
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):
|
def create_instance(self, context, instance):
|
||||||
"""Signal to engine service to perform a deployment."""
|
"""Signal to engine service to perform a deployment."""
|
||||||
LOG.debug("During create instance.")
|
LOG.debug("Strating instance...")
|
||||||
instance.task_state = 'deploying'
|
instance.status = 'building'
|
||||||
|
|
||||||
|
# Scheduling...
|
||||||
|
# instance.node_uuid = '8d22309b-b47a-41a7-80e3-e758fae9dedd'
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
|
self._build_networks(context, instance)
|
||||||
|
|
||||||
|
self._build_instance(context, instance)
|
||||||
|
|
||||||
return 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):
|
def create_instance(self, context, instance):
|
||||||
"""Signal to engine service to perform a deployment."""
|
"""Signal to engine service to perform a deployment."""
|
||||||
cctxt = self.client.prepare(topic=self.topic, server=CONF.host)
|
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),
|
'task_state': object_fields.StringField(nullable=True),
|
||||||
'instance_type_id': object_fields.IntegerField(nullable=True),
|
'instance_type_id': object_fields.IntegerField(nullable=True),
|
||||||
'availability_zone': object_fields.StringField(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),
|
'node_uuid': object_fields.UUIDField(nullable=True),
|
||||||
'extra': object_fields.FlexibleDictField(nullable=True),
|
'extra': object_fields.FlexibleDictField(nullable=True),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue