Merge "[Ironic] Add callback script for deploy with Ironic"

This commit is contained in:
Jenkins 2015-10-14 11:22:16 +00:00 committed by Gerrit Code Review
commit 672841256f
4 changed files with 201 additions and 0 deletions

View File

@ -0,0 +1,83 @@
# Copyright 2015 Mirantis, 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.
import json
import sys
import time
import requests
from fuel_agent.utils import utils
def _process_error(message):
sys.stderr.write(message)
sys.stderr.write('\n')
sys.exit(1)
def main():
"""Script informs Ironic that bootstrap loading is done.
There are three mandatory parameters in kernel command line.
Ironic prepares these two:
'api-url' - URL of Ironic API service,
'deployment_id' - UUID of the node in Ironic.
Passed from PXE boot loader:
'BOOTIF' - MAC address of the boot interface,
http://www.syslinux.org/wiki/index.php/SYSLINUX#APPEND_-
Example: api_url=http://192.168.122.184:6385
deployment_id=eeeeeeee-dddd-cccc-bbbb-aaaaaaaaaaaa
BOOTIF=01-88-99-aa-bb-cc-dd
"""
kernel_params = utils.parse_kernel_cmdline()
api_url = kernel_params.get('api-url')
deployment_id = kernel_params.get('deployment_id')
if api_url is None or deployment_id is None:
_process_error('Mandatory parameter ("api-url" or "deployment_id") is '
'missing.')
bootif = kernel_params.get('BOOTIF')
if bootif is None:
_process_error('Cannot define boot interface, "BOOTIF" parameter is '
'missing.')
# The leading `01-' denotes the device type (Ethernet) and is not a part of
# the MAC address
boot_mac = bootif[3:].replace('-', ':')
for n in range(10):
boot_ip = utils.get_interface_ip(boot_mac)
if boot_ip is not None:
break
time.sleep(10)
else:
_process_error('Cannot find IP address of boot interface.')
data = {"address": boot_ip,
"status": "ready",
"error_message": "no errors"}
passthru = '%(api-url)s/v1/nodes/%(deployment_id)s/vendor_passthru' \
'/pass_deploy_info' % {'api-url': api_url,
'deployment_id': deployment_id}
try:
resp = requests.post(passthru, data=json.dumps(data),
headers={'Content-Type': 'application/json',
'Accept': 'application/json'})
except Exception as e:
_process_error(str(e))
if resp.status_code != 202:
_process_error('Wrong status code %d returned from Ironic API' %
resp.status_code)

View File

@ -28,6 +28,36 @@ from fuel_agent.utils import utils
CONF = cfg.CONF
_LO_DEVICE = """lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state \
UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
"""
_ETH_DEVICE_NO_IP = """eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc \
pfifo_fast state UP group default qlen 1000
link/ether 08:60:6e:6f:7d:a5 brd ff:ff:ff:ff:ff:ff
"""
_ETH_DEVICE_IP = """inet 172.18.204.10/25 brd 172.18.204.127 scope global \
eth0
valid_lft forever preferred_lft forever
inet6 fe80::a60:6eff:fe6f:6da2/64 scope link
valid_lft forever preferred_lft forever
"""
_ETH_DEVICE = _ETH_DEVICE_NO_IP + _ETH_DEVICE_IP
_DOCKER_DEVICE = """docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 \
qdisc noqueue state DOWN group default
link/ether 56:86:7a:fe:97:79 brd ff:ff:ff:ff:ff:ff
inet 172.17.42.1/16 scope global docker0
"""
class ExecuteTestCase(unittest2.TestCase):
"""This class is partly based on the same class in openstack/ironic."""
@ -387,3 +417,61 @@ class TestUdevRulesBlacklisting(unittest2.TestCase):
self.assertFalse(mock_os.remove.called)
self.assertFalse(mock_os.rename.called)
mock_udev.assert_called_once_with()
@mock.patch.object(utils, 'execute')
class GetIPTestCase(unittest2.TestCase):
def setUp(self):
super(GetIPTestCase, self).setUp()
self.mac = '08:60:6e:6f:7d:a5'
self.cmd = ('ip', 'addr', 'show', 'scope', 'global')
def _build_out(self, lines):
out = ''
for num, line in enumerate(lines, start=1):
out += str(num) + ': ' + line
return out
def test_get_interface_ip(self, mock_execute):
lines = _LO_DEVICE, _ETH_DEVICE, _DOCKER_DEVICE
out = self._build_out(lines)
mock_execute.return_value = out, ''
ip = utils.get_interface_ip(self.mac)
self.assertEqual('172.18.204.10', ip)
mock_execute.assert_called_once_with(*self.cmd)
def test_get_interface_no_mac(self, mock_execute):
lines = _LO_DEVICE, _DOCKER_DEVICE
out = self._build_out(lines)
mock_execute.return_value = out, ''
ip = utils.get_interface_ip(self.mac)
self.assertIsNone(ip)
mock_execute.assert_called_once_with(*self.cmd)
def test_get_interface_no_ip(self, mock_execute):
lines = _LO_DEVICE, _ETH_DEVICE_NO_IP, _DOCKER_DEVICE
out = self._build_out(lines)
mock_execute.return_value = out, ''
ip = utils.get_interface_ip(self.mac)
self.assertIsNone(ip)
mock_execute.assert_called_once_with(*self.cmd)
def test_get_interface_no_ip_last(self, mock_execute):
lines = _LO_DEVICE, _ETH_DEVICE_NO_IP
out = self._build_out(lines)
mock_execute.return_value = out, ''
ip = utils.get_interface_ip(self.mac)
self.assertIsNone(ip)
mock_execute.assert_called_once_with(*self.cmd)
class ParseKernelCmdline(unittest2.TestCase):
def test_parse_kernel_cmdline(self):
data = 'foo=bar baz abc=def=123'
with mock.patch('six.moves.builtins.open',
mock.mock_open(read_data=data)) as mock_open:
params = utils.parse_kernel_cmdline()
self.assertEqual('bar', params['foo'])
mock_open.assert_called_once_with('/proc/cmdline', 'rt')

View File

@ -335,3 +335,32 @@ def unblacklist_udev_rules(udev_rules_dir, udev_rename_substr):
def udevadm_settle():
execute('udevadm', 'settle', '--quiet', check_exit_code=[0])
def parse_kernel_cmdline():
"""Parse linux kernel command line"""
with open('/proc/cmdline', 'rt') as f:
cmdline = f.read()
parameters = {}
for p in cmdline.split():
name, _, value = p.partition('=')
parameters[name] = value
return parameters
def get_interface_ip(mac_addr):
"""Get IP address of interface with mac_addr"""
# NOTE(yuriyz): current limitations IPv4 addresses only and one IP per
# interface.
ip_pattern = re.compile('inet ([\d\.]+)/')
out, err = execute('ip', 'addr', 'show', 'scope', 'global')
lines = out.splitlines()
for num, line in enumerate(lines):
if mac_addr in line:
try:
ip_line = lines[num + 1]
except IndexError:
return
match = ip_pattern.search(ip_line)
if match:
return match.group(1)

View File

@ -21,6 +21,7 @@ console_scripts =
fa_copyimage = fuel_agent.cmd.agent:copyimage
fa_bootloader = fuel_agent.cmd.agent:bootloader
fa_build_image = fuel_agent.cmd.agent:build_image
fa_ironic_callback = fuel_agent.cmd.ironic_callback:main
fuel_agent.drivers =
nailgun = fuel_agent.drivers.nailgun:Nailgun