Foundation for different deployment sources

Change-Id: I7c9538e37476d9d3ea5b9cc403419dda95cf77cc
Story: #2002048
Task: #26064
This commit is contained in:
Dmitry Tantsur 2018-09-04 17:19:35 +02:00
parent 1f92d9a5fb
commit a34d0e0951
3 changed files with 103 additions and 16 deletions

View File

@ -26,6 +26,7 @@ from metalsmith import _os_api
from metalsmith import _scheduler
from metalsmith import _utils
from metalsmith import exceptions
from metalsmith import sources
LOG = logging.getLogger(__name__)
@ -178,7 +179,8 @@ class Provisioner(object):
:param node: Node object, UUID or name. Will be reserved first, if
not reserved already. Must be in the "available" state with
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.
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
@ -203,6 +205,9 @@ class Provisioner(object):
"""
if config is None:
config = _config.InstanceConfig()
if isinstance(image, six.string_types):
image = sources.Glance(image)
node = self._check_node_for_deploy(node)
created_ports = []
attached_ports = []
@ -211,14 +216,7 @@ class Provisioner(object):
hostname = self._check_hostname(node, hostname)
root_disk_size = _utils.get_root_disk(root_disk_size, node)
try:
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)
image._validate(self._api)
nics = self._get_nics(nics or [])
@ -235,17 +233,12 @@ class Provisioner(object):
capabilities['boot_option'] = 'netboot' if netboot else 'local'
updates = {'/instance_info/image_source': image.id,
'/instance_info/root_gb': root_disk_size,
updates = {'/instance_info/root_gb': root_disk_size,
'/instance_info/capabilities': capabilities,
'/extra/%s' % _CREATED_PORTS: created_ports,
'/extra/%s' % _ATTACHED_PORTS: attached_ports,
'/instance_info/%s' % _os_api.HOSTNAME_FIELD: hostname}
for prop in ('kernel', 'ramdisk'):
value = getattr(image, '%s_id' % prop, None)
if value:
updates['/instance_info/%s' % prop] = value
updates.update(image._node_updates(self._api))
LOG.debug('Updating node %(node)s with %(updates)s',
{'node': _utils.log_node(node), 'updates': updates})

73
metalsmith/sources.py Normal file
View 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

View File

@ -22,6 +22,7 @@ from metalsmith import _instance
from metalsmith import _os_api
from metalsmith import _provisioner
from metalsmith import exceptions
from metalsmith import sources
class Base(testtools.TestCase):
@ -206,6 +207,26 @@ class TestProvisionNode(Base):
self.assertFalse(self.api.release_node.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):
config = mock.MagicMock(spec=_config.InstanceConfig)
inst = self.pr.provision_node(self.node, 'image',