-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathr630-idrac-get-mac
executable file
·222 lines (183 loc) · 7.78 KB
/
r630-idrac-get-mac
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/usr/bin/env python
# -*- coding: utf-8 -*-#
# @(#)r630-idrac-get-mac
#
#
# Copyright (C) 2015, GC3, University of Zurich. All rights reserved.
#
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
__docformat__ = 'reStructuredText'
__author__ = 'Antonio Messina <[email protected]>'
import sys
import argparse
import xml.etree.ElementTree as ET
from collections import namedtuple
from fnmatch import fnmatch
import os
import sys
import yaml
import re
HW = namedtuple('HW', ['sp_mac', 'host_macs', 'host_mac'])
def walk_data_dir(path, pattern):
"""Get a directory and returns a list of paths that matches `glob`"""
if os.path.isfile(path):
if fnmatch(path, pattern):
return [path]
else:
return []
paths = []
for root, dirs, fnames in os.walk(path):
paths.extend(os.path.join(root, fname)
for fname in fnames if fnmatch(fname, pattern))
for d in dirs:
paths.extend(walk_data_dir(os.path.join(root, d), pattern))
return paths
def parse_files(paths, pattern):
"""
Parse all files in `paths` matching the `pattern` *glob* expression.
Returns a 3-named-tuple containing::
- hosts: a dictionary with keys the name of the hosts, and values the yaml parsed data
- parsed_files: list of files correctly parsed
- error_files: list of files that were not correctly parsed
"""
out = namedtuple('ParsedInventory', ['hosts', 'parsed_files', 'error_files'])
out.hosts = {}
all_files = reduce(lambda x, y: x+y,
[walk_data_dir(path, pattern) for path in paths],
[])
out.error_files = []
out.parsed_files = []
for fname in all_files:
with open(fname) as fd:
try:
out.hosts[fname] = yaml.load(fd)
out.parsed_files.append(fname)
except Exception:
out.error_files.append(fname)
return out
def dump_hosts(hosts):
"""
accept a dictionary like the `hosts` member returned by parse_all_files: {filename: dict} and write the `dict` object to `filename`
"""
for fname, host in hosts.items():
with open(fname, 'w') as fd:
yaml.dump(host, fd, default_flow_style=False)
def update_inventory(hw, inventory_dir, hostname):
hosts = {}
sp = parse_files([inventory_dir], 'sp-%s.yaml' % hostname).hosts
# SPs only have one interface
if not sp.values()[0] or 'interfaces' not in sp.values()[0]:
raise Exception("Invalid format for file %s" % sp.keys()[0])
sp.values()[0]['interfaces'][0]['mac'] = hw.sp_mac
hosts.update(sp)
# Same but for the host
host = parse_files([inventory_dir], '%s.yaml' % hostname).hosts
if not host.values()[0] or 'interfaces' not in host.values()[0]:
raise Exception("Invalid format for file %s" % host.keys()[0])
# HOSTs only have one interface
ifaces = host.values()[0]['interfaces']
eth0 = [iface for iface in ifaces if iface.get('iface') == 'eth0']
if not eth0:
eth0 = ifaces[0]
else:
eth0 = eth0[0]
eth0['mac'] = hw.host_mac
hosts.update(host)
return hosts
def parse_hwinventory_file(fname, cfg):
# parse and update inventory files
host_macs = sp_mac = None
tree = ET.parse(fname)
root = tree.getroot()
sp_mac = root.find(".//Component[@Classname='DCIM_iDRACCardView']"
"/PROPERTY[@NAME='PermanentMACAddress']/VALUE").text
if cfg.nic_index:
host_mac = [i.text.lower() for i in root.findall(
".//Component[@Classname='DCIM_NICView'][@Key='NIC.Integrated.%s']"
"/PROPERTY[@NAME='PermanentMACAddress']/VALUE" % cfg.nic_index)][0]
host_macs = [i.text.lower() for i in root.findall(
".//Component[@Classname='DCIM_NICView']"
"/PROPERTY[@NAME='PermanentMACAddress']/VALUE")]
else:
nics = root.findall(".//Component[@Classname='DCIM_NICView']")
for nic in nics:
if nic.find(".//PROPERTY[@NAME='LinkSpeed']/VALUE").text != '0':
host_macs.append(
nic.find(".//PROPERTY[@NAME='PermanentMACAddress']/VALUE"))
if not host_macs:
host_macs = [i.text.lower() for i in root.findall(
".//Component[@Classname='DCIM_NICView']"
"/PROPERTY[@NAME='PermanentMACAddress']/VALUE")]
host_mac = host_macs[0]
return HW(sp_mac, host_macs, host_mac)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="""
Parse R630 iDRAC hwinventory file, gather MAC addresses and update
yaml inventory file corresponding to the machine.
The file parsed by this tool is the output of the command:
racadm hwinventory export -f <hwinventory.xml>
You need to provide the path to the <hwinventory.xml> file, path to
the invenetory database and the name of the machine with `--name`.
Files that will be updated:
`<name>.yaml`
`sp-<name>.yaml`
By default the script will use the MAC address of the first interface
in UP state, but you can define the correct interface with option
`-x`, for instance:
%s -x 1-2-1 ...
""" % sys.argv[0],
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("-f", "--file",
dest="xmlconf",
required=True,
metavar="PATH",
help="Path to the XML inventory file.")
parser.add_argument("-n", "--name",
dest="hostname",
required=True,
help="""Hostname of the new host. Hostname of the SP
will be in the form `sp-<hostname>`""")
parser.add_argument("-i", "--inventory-dir",
metavar="PATH",
required=True,
help="Path to inventory directory.")
parser.add_argument('-x', '--nic-index', default='1-3-1',
help="""Specify the index of the NIC to be used for
host interface. If not provided, the script will try
to identify which port is in Up state. If more ports
are in UP, the first one will be used. If no interface
is UP, the first one will be used. Default: %(default)s""")
cfg = parser.parse_args()
# Check existence of files
if not os.path.exists(cfg.xmlconf):
parser.error("XML file `%s` not found." % cfg.xmlconf)
for fname in [os.path.join(cfg.inventory_dir, cfg.hostname+'.yaml'),
os.path.join(cfg.inventory_dir, 'sp-'+ cfg.hostname+'.yaml')]:
if not os.path.exists(fname):
parser.error("Inventory file %s not found." % fname)
# parse hwinventory file
try:
hw = parse_hwinventory_file(cfg.xmlconf, cfg)
except Exception as ex:
parser.error("Error parsing XML file %s: %s" % (cfg.xmlconf, ex))
# Update yaml inventory files
try:
hosts = update_inventory(hw, cfg.inventory_dir, cfg.hostname)
except Exception as ex:
parser.error("Error parsing inventory files: %s" % ex)
# Save the hosts
dump_hosts(hosts)