Merge lp:~andreserl/maas/maas_ipmi_autodetection into lp:maas/trunk

Proposed by Andres Rodriguez on 2012-10-04
Status: Merged
Approved by: Andres Rodriguez on 2012-10-04
Approved revision: 1157
Merged at revision: 1157
Proposed branch: lp:~andreserl/maas/maas_ipmi_autodetection
Merge into: lp:maas/trunk
Diff against target: 242 lines (+166/-1)
1 file modified
etc/maas/commissioning-user-data (+166/-1)
To merge this branch: bzr merge lp:~andreserl/maas/maas_ipmi_autodetection
Reviewer Review Type Date Requested Status
Scott Moser Approve on 2012-10-04
Julian Edwards (community) 2012-10-04 Approve on 2012-10-04
Review via email: mp+127911@code.launchpad.net

Commit Message

IPMI autodetection during commissioning

To post a comment you must log in.
Scott Moser (smoser) wrote :

48 + fargs="--power-type=ipmi --power-parameters=$power_settings"
49 + signal $fargs WORKING "finished [maas-ipmi-autodetect]"

That is wrong.
shell will incorrectly expand that to not do what you want on line 49 (plus, its up for shell expansion).

Correct way is:

signal "--power-type=ipmi" "--power-parameters=${power_settings}" WORKING "finished [maas-ipmi-autodetect]"

Other thing is less severe, but more stlylish.
put the actual stuff you're doing in the 'main' routine.

rather than outside (aptget, loadmodules)...

review: Needs Fixing
Julian Edwards (julian-edwards) wrote :

>Other thing is less severe, but more stlylish.
>put the actual stuff you're doing in the 'main' routine.
>
>rather than outside (aptget, loadmodules)...

Andres is doing it correctly IMO. Refactoring into small, well-defined functions makes debugging easier, testing easier and writing easier.

Alas, there are no tests. How can I convince you guys to write tests?

Scott Moser (smoser) wrote :

I'm not saying there should not be functions. but that they should be called from main.

On Oct 3, 2012, at 8:44 PM, Julian Edwards <email address hidden> wrote:

>> Other thing is less severe, but more stlylish.
>> put the actual stuff you're doing in the 'main' routine.
>>
>> rather than outside (aptget, loadmodules)...
>
> Andres is doing it correctly IMO. Refactoring into small, well-defined functions makes debugging easier, testing easier and writing easier.
>
> Alas, there are no tests. How can I convince you guys to write tests?
> --
> https://code.launchpad.net/~andreserl/maas/maas_ipmi_autodetection/+merge/127911
> You are reviewing the proposed merge of lp:~andreserl/maas/maas_ipmi_autodetection into lp:maas.
>

1157. By Andres Rodriguez on 2012-10-04

Address recommendations by smoser

Julian Edwards (julian-edwards) wrote :

<roaksoax> bigjools: I addressed smoser's suggedstions, could you please :)? https://code.launchpad.net/~andreserl/maas/maas_ipmi_autodetection/+merge/127911
<bigjools> roaksoax: you might as well land it, I'll approve. It's hard to make suggestions at this stage because anything I would change would be quite substantial I think. I don't want to block you.

review: Approve
Scott Moser (smoser) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'etc/maas/commissioning-user-data'
2--- etc/maas/commissioning-user-data 2012-08-03 16:36:26 +0000
3+++ etc/maas/commissioning-user-data 2012-10-04 03:18:19 +0000
4@@ -9,14 +9,19 @@
5 #### script setup ######
6 TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX")
7 SCRIPTS_D="${TEMP_D}/scripts"
8+IPMI_CONFIG_D="${TEMP_D}/ipmi.d"
9 BIN_D="${TEMP_D}/bin"
10 OUT_D="${TEMP_D}/out"
11 PATH="$BIN_D:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
12 trap cleanup EXIT
13
14-mkdir -p "$BIN_D" "$OUT_D" "$SCRIPTS_D"
15+mkdir -p "$BIN_D" "$OUT_D" "$SCRIPTS_D" "$IPMI_CONFIG_D"
16
17 ### some utility functions ####
18+aptget() {
19+ DEBIAN_FRONTEND=noninteractive apt-get --assume-yes -q "$@" </dev/null
20+}
21+
22 writefile() {
23 cat > "$1"
24 chmod "$2" "$1"
25@@ -29,6 +34,10 @@
26 cat > "${SCRIPTS_D}/$1"
27 chmod "${2:-755}" "${SCRIPTS_D}/$1"
28 }
29+add_ipmi_config() {
30+ cat > "${IPMI_CONFIG_D}/$1"
31+ chmod "${2:-644}" "${IPMI_CONFIG_D}/$1"
32+}
33 cleanup() {
34 [ -n "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
35 }
36@@ -80,6 +89,11 @@
37
38 main() {
39 trap shutdown EXIT
40+ # Install tools and load modules
41+ aptget update
42+ aptget install freeipmi-tools
43+ load_modules
44+
45 # the main function, actually execute stuff that is written below
46 local script total=0 creds=""
47
48@@ -99,6 +113,12 @@
49 # use global name read by signal() and fail
50 CRED_CFG="$creds"
51
52+ # power settings
53+ power_settings=$(maas-ipmi-autodetect --configdir "$IPMI_CONFIG_D")
54+ if [ ! -z "power_settings" ]; then
55+ signal "--power-type=ipmi" "--power-parameters=${power_settings}" WORKING "finished [maas-ipmi-autodetect]"
56+ fi
57+
58 # just get a count of how many scripts there are for progress reporting
59 for script in "${SCRIPTS_D}/"*; do
60 [ -x "$script" -a -f "$script" ] || continue
61@@ -139,12 +159,132 @@
62
63 }
64
65+load_modules() {
66+ modprobe ipmi_msghandler
67+ modprobe ipmi_devintf
68+ modprobe ipmi_si type=kcs ports=0xca2
69+}
70+
71 ### begin writing files ###
72 add_script "01-lshw" <<"END_LSHW"
73 #!/bin/sh
74 lshw -xml
75 END_LSHW
76
77+add_ipmi_config "01-user-privileges.ipmi" <<"END_IPMI_USER_PRIVILEGES"
78+Section User3
79+ Enable_User Yes
80+ Lan_Enable_IPMI_Msgs Yes
81+ Lan_Privilege_Limit Administrator
82+EndSection
83+END_IPMI_USER_PRIVILEGES
84+
85+add_ipmi_config "02-global-config.ipmi" <<"END_IPMI_CONFIG"
86+Section Lan_Channel
87+ Volatile_Access_Mode Always_Available
88+ Volatile_Enable_User_Level_Auth Yes
89+ Volatile_Channel_Privilege_Limit Administrator
90+ Non_Volatile_Access_Mode Always_Available
91+ Non_Volatile_Enable_User_Level_Auth Yes
92+ Non_Volatile_Channel_Privilege_Limit Administrator
93+EndSection
94+END_IPMI_CONFIG
95+
96+add_bin "maas-ipmi-autodetect" <<"END_MAAS_IPMI_AUTODETECT"
97+#!/usr/bin/python
98+import os
99+import commands
100+import re
101+import string
102+import random
103+import time
104+
105+def detect_ipmi():
106+ # TODO: Detection could be improved.
107+ (status, output) = commands.getstatusoutput('ipmi-locate')
108+ show_re = re.compile('(IPMI\ Version:) (\d\.\d)')
109+ res = show_re.search(output)
110+ if res == None:
111+ return (False, "")
112+ return (True, res.group(2))
113+
114+def is_ipmi_dhcp():
115+ (status, output) = commands.getstatusoutput('bmc-config --checkout --key-pair="Lan_Conf:IP_Address_Source"')
116+ show_re = re.compile('IP_Address_Source\s+Use_DHCP')
117+ res = show_re.search(output)
118+ if res == None:
119+ return False
120+ return True
121+
122+def set_ipmi_network_source(source):
123+ (status, output) = commands.getstatusoutput('bmc-config --commit --key-pair="Lan_Conf:IP_Address_Source=%s"' % source)
124+
125+def get_ipmi_ip_address():
126+ (status, output) = commands.getstatusoutput('bmc-config --checkout --key-pair="Lan_Conf:IP_Address"')
127+ show_re = re.compile('([0-9]{1,3}[.]?){4}')
128+ res = show_re.search(output)
129+ return res.group()
130+
131+def commit_ipmi_user_settings(user, password):
132+ (status, output) = commands.getstatusoutput('bmc-config --commit --key-pair="User3:Username=%s"' % user)
133+ (status, output) = commands.getstatusoutput('bmc-config --commit --key-pair="User3:Password=%s"' % password)
134+
135+def commit_ipmi_settings(config):
136+ (status, output) = commands.getstatusoutput('bmc-config --commit --filename %s' % config)
137+
138+def get_maas_power_settings(user, password, ipaddress):
139+ return "%s,%s,%s" % (user, password, ipaddress)
140+
141+def generate_random_password(min=8,max=15):
142+ length=random.randint(min,max)
143+ letters=string.ascii_letters+string.digits
144+ return ''.join([random.choice(letters) for _ in range(length)])
145+
146+def main():
147+
148+ import argparse
149+
150+ parser = argparse.ArgumentParser(
151+ description='send config file to modify IPMI settings with')
152+ parser.add_argument("--configdir", metavar="folder",
153+ help="specify config file", default=None)
154+
155+ args = parser.parse_args()
156+
157+ # Check whether IPMI exists or not.
158+ (status, ipmi_version) = detect_ipmi()
159+ if status != True:
160+ # if False, then failed to detect ipmi
161+ exit(1)
162+
163+ # Check whether IPMI is being set to DHCP
164+ # If it is not DHCP, set it to DHCP.
165+ if not is_ipmi_dhcp():
166+ set_ipmi_network_source("Use_DHCP")
167+ # allow IPMI 60 seconds to obtain an IP address
168+ time.sleep(60)
169+
170+ # create user/pass
171+ IPMI_MAAS_USER="maas"
172+ IPMI_MAAS_PASSWORD=generate_random_password()
173+
174+ # Configure IPMI user/password
175+ commit_ipmi_user_settings(IPMI_MAAS_USER, IPMI_MAAS_PASSWORD)
176+
177+ # Commit other IPMI settings
178+ if args.configdir:
179+ for file in os.listdir(args.configdir):
180+ commit_ipmi_settings(os.path.join(args.configdir, file))
181+
182+ # get the IP address
183+ IPMI_IP_ADDRESS = get_ipmi_ip_address()
184+
185+ print get_maas_power_settings(IPMI_MAAS_USER, IPMI_MAAS_PASSWORD, IPMI_IP_ADDRESS)
186+
187+if __name__ == '__main__':
188+ main()
189+END_MAAS_IPMI_AUTODETECT
190+
191 add_bin "maas-signal" <<"END_MAAS_SIGNAL"
192 #!/usr/bin/python
193
194@@ -157,9 +297,11 @@
195 import time
196 import urllib2
197 import yaml
198+import json
199
200 MD_VERSION = "2012-03-01"
201 VALID_STATUS = ("OK", "FAILED", "WORKING")
202+POWER_TYPES = ("ipmi", "virsh", "ether_wake")
203
204
205 def _encode_field(field_name, data, boundary):
206@@ -292,6 +434,12 @@
207 help="the data source to query", default=None)
208 parser.add_argument("--file", dest='files',
209 help="file to post", action='append', default=[])
210+ parser.add_argument("--post", dest='posts',
211+ help="name=value pairs to post", action='append', default=[])
212+ parser.add_argument("--power-type", dest='power_type',
213+ help="power type.", choices=POWER_TYPES, default=None)
214+ parser.add_argument("--power-parameters", dest='power_parms',
215+ help="power parameters.", default=None)
216
217 parser.add_argument("status",
218 help="status", choices=VALID_STATUS, action='store')
219@@ -317,6 +465,23 @@
220 "status": args.status,
221 "error": args.message}
222
223+ for ent in args.posts:
224+ try:
225+ (key, val) = ent.split("=", 2)
226+ except ValueError:
227+ sys.stderr.write("'%s' had no '='" % ent)
228+ sys.exit(1)
229+ params[key] = val
230+
231+ if args.power_parms is not None:
232+ params["power_type"] = args.power_type
233+ power_parms = dict(
234+ power_user=args.power_parms.split(",")[0],
235+ power_pass=args.power_parms.split(",")[1],
236+ power_address=args.power_parms.split(",")[2]
237+ )
238+ params["power_parameters"] = json.dumps(power_parms)
239+
240 files = {}
241 for fpath in args.files:
242 files[os.path.basename(fpath)] = open(fpath, "r")