Foundation for different deployment sources
Change-Id: I7c9538e37476d9d3ea5b9cc403419dda95cf77cc Story: #2002048 Task: #26064
This commit is contained in:
parent
1f92d9a5fb
commit
a34d0e0951
@ -26,6 +26,7 @@ from metalsmith import _os_api
|
|||||||
from metalsmith import _scheduler
|
from metalsmith import _scheduler
|
||||||
from metalsmith import _utils
|
from metalsmith import _utils
|
||||||
from metalsmith import exceptions
|
from metalsmith import exceptions
|
||||||
|
from metalsmith import sources
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -178,7 +179,8 @@ class Provisioner(object):
|
|||||||
:param node: Node object, UUID or name. Will be reserved first, if
|
:param node: Node object, UUID or name. Will be reserved first, if
|
||||||
not reserved already. Must be in the "available" state with
|
not reserved already. Must be in the "available" state with
|
||||||
maintenance mode off.
|
maintenance mode off.
|
||||||
:param image: Image name or UUID to provision.
|
:param image: Image source - one of :mod:`~metalsmith.sources`,
|
||||||
|
`Image` name or UUID.
|
||||||
:param nics: List of virtual NICs to attach to physical ports.
|
:param nics: List of virtual NICs to attach to physical ports.
|
||||||
Each item is a dict with a key describing the type of the NIC:
|
Each item is a dict with a key describing the type of the NIC:
|
||||||
either a port (``{"port": "<port name or ID>"}``) or a network
|
either a port (``{"port": "<port name or ID>"}``) or a network
|
||||||
@ -203,6 +205,9 @@ class Provisioner(object):
|
|||||||
"""
|
"""
|
||||||
if config is None:
|
if config is None:
|
||||||
config = _config.InstanceConfig()
|
config = _config.InstanceConfig()
|
||||||
|
if isinstance(image, six.string_types):
|
||||||
|
image = sources.Glance(image)
|
||||||
|
|
||||||
node = self._check_node_for_deploy(node)
|
node = self._check_node_for_deploy(node)
|
||||||
created_ports = []
|
created_ports = []
|
||||||
attached_ports = []
|
attached_ports = []
|
||||||
@ -211,14 +216,7 @@ class Provisioner(object):
|
|||||||
hostname = self._check_hostname(node, hostname)
|
hostname = self._check_hostname(node, hostname)
|
||||||
root_disk_size = _utils.get_root_disk(root_disk_size, node)
|
root_disk_size = _utils.get_root_disk(root_disk_size, node)
|
||||||
|
|
||||||
try:
|
image._validate(self._api)
|
||||||
image = self._api.get_image(image)
|
|
||||||
except Exception as exc:
|
|
||||||
raise exceptions.InvalidImage(
|
|
||||||
'Cannot find image %(image)s: %(error)s' %
|
|
||||||
{'image': image, 'error': exc})
|
|
||||||
|
|
||||||
LOG.debug('Image: %s', image)
|
|
||||||
|
|
||||||
nics = self._get_nics(nics or [])
|
nics = self._get_nics(nics or [])
|
||||||
|
|
||||||
@ -235,17 +233,12 @@ class Provisioner(object):
|
|||||||
|
|
||||||
capabilities['boot_option'] = 'netboot' if netboot else 'local'
|
capabilities['boot_option'] = 'netboot' if netboot else 'local'
|
||||||
|
|
||||||
updates = {'/instance_info/image_source': image.id,
|
updates = {'/instance_info/root_gb': root_disk_size,
|
||||||
'/instance_info/root_gb': root_disk_size,
|
|
||||||
'/instance_info/capabilities': capabilities,
|
'/instance_info/capabilities': capabilities,
|
||||||
'/extra/%s' % _CREATED_PORTS: created_ports,
|
'/extra/%s' % _CREATED_PORTS: created_ports,
|
||||||
'/extra/%s' % _ATTACHED_PORTS: attached_ports,
|
'/extra/%s' % _ATTACHED_PORTS: attached_ports,
|
||||||
'/instance_info/%s' % _os_api.HOSTNAME_FIELD: hostname}
|
'/instance_info/%s' % _os_api.HOSTNAME_FIELD: hostname}
|
||||||
|
updates.update(image._node_updates(self._api))
|
||||||
for prop in ('kernel', 'ramdisk'):
|
|
||||||
value = getattr(image, '%s_id' % prop, None)
|
|
||||||
if value:
|
|
||||||
updates['/instance_info/%s' % prop] = value
|
|
||||||
|
|
||||||
LOG.debug('Updating node %(node)s with %(updates)s',
|
LOG.debug('Updating node %(node)s with %(updates)s',
|
||||||
{'node': _utils.log_node(node), 'updates': updates})
|
{'node': _utils.log_node(node), 'updates': updates})
|
||||||
|
73
metalsmith/sources.py
Normal file
73
metalsmith/sources.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# Copyright 2018 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Image sources to use when provisioning nodes."""
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from metalsmith import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class _Source(object):
|
||||||
|
|
||||||
|
def _validate(self, api):
|
||||||
|
"""Validate the source."""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _node_updates(self, api):
|
||||||
|
"""Updates required for a node to use this source."""
|
||||||
|
|
||||||
|
|
||||||
|
class Glance(_Source):
|
||||||
|
"""Image from the OpenStack Image service."""
|
||||||
|
|
||||||
|
def __init__(self, image):
|
||||||
|
"""Create a Glance source.
|
||||||
|
|
||||||
|
:param image: `Image` object, ID or name.
|
||||||
|
"""
|
||||||
|
self._image_id = image
|
||||||
|
self._image_obj = None
|
||||||
|
|
||||||
|
def _validate(self, api):
|
||||||
|
if self._image_obj is not None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self._image_obj = api.get_image(self._image_id)
|
||||||
|
except Exception as exc:
|
||||||
|
raise exceptions.InvalidImage(
|
||||||
|
'Cannot find image %(image)s: %(error)s' %
|
||||||
|
{'image': self._image_id, 'error': exc})
|
||||||
|
|
||||||
|
def _node_updates(self, api):
|
||||||
|
self._validate(api)
|
||||||
|
LOG.debug('Image: %s', self._image_obj)
|
||||||
|
|
||||||
|
updates = {
|
||||||
|
'/instance_info/image_source': self._image_obj.id
|
||||||
|
}
|
||||||
|
for prop in ('kernel', 'ramdisk'):
|
||||||
|
value = getattr(self._image_obj, '%s_id' % prop, None)
|
||||||
|
if value:
|
||||||
|
updates['/instance_info/%s' % prop] = value
|
||||||
|
|
||||||
|
return updates
|
@ -22,6 +22,7 @@ from metalsmith import _instance
|
|||||||
from metalsmith import _os_api
|
from metalsmith import _os_api
|
||||||
from metalsmith import _provisioner
|
from metalsmith import _provisioner
|
||||||
from metalsmith import exceptions
|
from metalsmith import exceptions
|
||||||
|
from metalsmith import sources
|
||||||
|
|
||||||
|
|
||||||
class Base(testtools.TestCase):
|
class Base(testtools.TestCase):
|
||||||
@ -206,6 +207,26 @@ class TestProvisionNode(Base):
|
|||||||
self.assertFalse(self.api.release_node.called)
|
self.assertFalse(self.api.release_node.called)
|
||||||
self.assertFalse(self.api.delete_port.called)
|
self.assertFalse(self.api.delete_port.called)
|
||||||
|
|
||||||
|
def test_ok_with_source(self):
|
||||||
|
inst = self.pr.provision_node(self.node, sources.Glance('image'),
|
||||||
|
[{'network': 'network'}])
|
||||||
|
|
||||||
|
self.assertEqual(inst.uuid, self.node.uuid)
|
||||||
|
self.assertEqual(inst.node, self.node)
|
||||||
|
|
||||||
|
self.api.create_port.assert_called_once_with(
|
||||||
|
network_id=self.api.get_network.return_value.id)
|
||||||
|
self.api.attach_port_to_node.assert_called_once_with(
|
||||||
|
self.node.uuid, self.api.create_port.return_value.id)
|
||||||
|
self.api.update_node.assert_called_once_with(self.node, self.updates)
|
||||||
|
self.api.validate_node.assert_called_once_with(self.node,
|
||||||
|
validate_deploy=True)
|
||||||
|
self.api.node_action.assert_called_once_with(self.node, 'active',
|
||||||
|
configdrive=mock.ANY)
|
||||||
|
self.assertFalse(self.wait_mock.called)
|
||||||
|
self.assertFalse(self.api.release_node.called)
|
||||||
|
self.assertFalse(self.api.delete_port.called)
|
||||||
|
|
||||||
def test_with_config(self):
|
def test_with_config(self):
|
||||||
config = mock.MagicMock(spec=_config.InstanceConfig)
|
config = mock.MagicMock(spec=_config.InstanceConfig)
|
||||||
inst = self.pr.provision_node(self.node, 'image',
|
inst = self.pr.provision_node(self.node, 'image',
|
||||||
|
Loading…
Reference in New Issue
Block a user