在Ironic创建过程中注入数据

Ironic简介

Ironic是Openstack的物理机即服务的控制组件,可以作为Nova的后端driver,用于物理机的生命周期管理。

Ironic的物理机创建流程

创建命令经过处理后到达了nova.virt.ironic.driver.py中的spawn函数:

1
2
3
4
self.ironicclient.call("node.set_provision_state", node_uuid,
ironic_states.ACTIVE,
configdrive=configdrive_value,
extra_data=extra_data)

通过ironicclient接入ironic的api,ironic.api.controllers.v1.node.py中的provision函数,所给的目标状态为ironic_states.ACTIVE

1
2
3
4
if target == ir_states.ACTIVE:
pecan.request.rpcapi.do_node_deploy(pecan.request.context,
rpc_node.uuid, False,
configdrive, topic, extra_data)

再通过rpc调用到conductor中的do_node_deploy函数,ironic.conductor.manager.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def do_node_deploy(task, conductor_id, configdrive=None, extra_data=None):
"""Prepare the environment and deploy a node."""
node = task.node

def handle_failure(e, task, logmsg, errmsg):
# NOTE(deva): there is no need to clear conductor_affinity
task.process_event('fail')
args = {'node': task.node.uuid, 'err': e}
LOG.warning(logmsg, args)
node.last_error = errmsg % e

try:
try:
if configdrive:
_store_configdrive(node, configdrive)
except exception.SwiftOperationError as e:
with excutils.save_and_reraise_exception():
handle_failure(e, task,
_LW('Error while uploading the configdrive for '
'%(node)s to Swift'),
_('Failed to upload the configdrive to Swift. '
'Error: %s'))

try:
task.driver.deploy.prepare(task)
except Exception as e:
with excutils.save_and_reraise_exception():
handle_failure(e, task,
_LW('Error while preparing to deploy to node %(node)s: '
'%(err)s'),
_("Failed to prepare to deploy. Error: %s"))

try:
new_state = task.driver.deploy.deploy(task, extra_data)
except Exception as e:
with excutils.save_and_reraise_exception():
handle_failure(e, task,
_LW('Error in deploy of node %(node)s: %(err)s'),
_("Failed to deploy. Error: %s"))

# Update conductor_affinity to reference this conductor's ID
# since there may be local persistent state
node.conductor_affinity = conductor_id

# NOTE(deva): Some drivers may return states.DEPLOYWAIT
# eg. if they are waiting for a callback
if new_state == states.DEPLOYDONE:
task.process_event('done')
LOG.info(_LI('Successfully deployed node %(node)s with '
'instance %(instance)s.'),
{'node': node.uuid, 'instance': node.instance_uuid})
elif new_state == states.DEPLOYWAIT:
task.process_event('wait')
else:
LOG.error(_LE('Unexpected state %(state)s returned while '
'deploying node %(node)s.'),
{'state': new_state, 'node': node.uuid})
finally:
node.save()

在这个函数中,可以看到ironic先做了task.driver.deploy.prepare(task),相当于准备工作;接下来new_state = task.driver.deploy.deploy(task, extra_data),开始部署。其中的task.driver即为ironic服务所配置的diver类型,以pxePXEAndIPMIToolDriver为例,参考ironic.drivers.pxe.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class PXEAndIPMIToolDriver(base.BaseDriver):
"""PXE + IPMITool driver.

This driver implements the `core` functionality, combining
:class:`ironic.drivers.ipmi.IPMI` for power on/off and reboot with
:class:`ironic.driver.pxe.PXE` for image deployment. Implementations are in
those respective classes; this class is merely the glue between them.
"""

def __init__(self):
self.power = ipmitool.IPMIPower()
self.console = ipmitool.IPMIShellinaboxConsole()
self.deploy = pxe.PXEDeploy()
self.management = ipmitool.IPMIManagement()
self.vendor = pxe.VendorPassthru()
self.inspect = discoverd.DiscoverdInspect.create_if_enabled(
'PXEAndIPMIToolDriver')

task.driver.deploy即为pxe.PXEDeploy()task.driver.deploy.prepare即为ironic.drivers.modules.pxe.py文件中PXEDeploy类的prepare函数,task.driver.deploy.deploy为同文件中PXEDeploy类的deploy函数。

prepare函数主要用于生成pxe的一些配置文件与配置项。

deploy函数用于镜像、网络等的部署。

密码注入

因为在deploy的时候,ironic会将系统镜从glance下载到/var/lib/ironic/images目录下,所以在此时注入密码是最合适的。实际上ironic本身提供了密码注入功能,是通过configdrive方式实现的。但是这个功能并不常用,所以自定义了通过libguestfs来注入密码的功能。

回看PXEDeploy中的deploy函数,iscsi_deploy.cache_instance_image这个步骤即完成了对glance中镜像的下载缓存,所以密码注入步骤需要紧跟其后。

密码注入函数的大概写法可以参考nova中的inject_data函数,例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def _inject_data(task, image_uuid, image_path, admin_pass=None):

try:
os_type, inject_files, partition = deploy_utils._generate_inject_data(task, image_uuid, admin_pass)

if not os_type:
raise

if 'windows' in os_type:
LOG.info("in windows inject")
key = net = metadata = admin_pass = None
deploy_utils.inject_data(image_path,
key, net, metadata, admin_pass, files=inject_files, partition=partition,
use_cow=False)

elif 'linux' in os_type:
LOG.info("in linux inject")
key = net = metadata = None
deploy_utils.inject_data(image_path,
key, net, metadata, admin_pass, files=inject_files, partition=partition,
use_cow=False, mandatory=('files',))

except Exception as e:
with excutils.save_and_reraise_exception():
LOG.error(_LE('Error injecting data into image '
'%(img_id)s (%(e)s)'),
{'img_id': image_uuid, 'e': e})

同时,在nova的配置中需要打开enable_instance_password选项才能使用随机密码。