Merge ~troyanov/maas:backport-redfish-fix-3.2 into maas:3.2

Proposed by Anton Troyanov
Status: Merged
Approved by: Anton Troyanov
Approved revision: d1da01228135186e3b7b673aa9d338d7655404ac
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~troyanov/maas:backport-redfish-fix-3.2
Merge into: maas:3.2
Diff against target: 242 lines (+130/-18)
2 files modified
src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py (+42/-18)
src/metadataserver/builtin_scripts/commissioning_scripts/tests/test_bmc_config.py (+88/-0)
Reviewer Review Type Date Requested Status
Björn Tillenius Approve
Alberto Donato (community) Approve
MAAS Lander Approve
Review via email: mp+424212@code.launchpad.net

Commit message

fix(redfish): backport redfish fixes to 3.2

fix: LP:1977864

(cherry picked from commit 2a809b6738e209e55b27c597f8182afbf8241662)

fix: LP:1977866

(cherry picked from commit c0bf73e7678b79b7de50c3bd3223c54198d0c5e0)

fix: LP:1977942

(cherry picked from commit b67298258b44c20ab0b95bb89eb53e08299c4ca8)

fix: LP:1977951

(cherry picked from commit 51eb53d51c86d1551feaf6ace317d043ee0deba5)

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b backport-redfish-fix-3.2 lp:~troyanov/maas/+git/maas into -b 3.2 lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: d1da01228135186e3b7b673aa9d338d7655404ac

review: Approve
Revision history for this message
Alberto Donato (ack) wrote :

+1, LGTM.

A few nitpicks. As this is a backport, maybe they could be addressed as a separate branch (mainly for master, optionally backported).

review: Approve
Revision history for this message
Björn Tillenius (bjornt) wrote :

Let's land this as it is, and do the fixes in a separate branch.

Please fix the commit message, though, so that it has all the cherry-picked hashes in it.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py b/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py
2index d75b0b5..0f1859b 100755
3--- a/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py
4+++ b/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py
5@@ -78,6 +78,7 @@ from subprocess import (
6 )
7 import sys
8 import time
9+import traceback
10 import urllib
11 import urllib.request
12
13@@ -888,14 +889,27 @@ class Redfish(IPMIBase):
14 """Handle detection and configuration of Redfish device."""
15
16 power_type = "redfish"
17- username = None
18- password = None
19
20- _bmc_ip = None
21+ def __init__(
22+ self,
23+ username=None,
24+ password=None,
25+ ipmi_privilege_level="",
26+ **kwargs,
27+ ):
28+ self.username = username
29+ self.password = password
30+ self._privilege_level = ipmi_privilege_level
31+ self._bmc_config = {}
32
33+ _bmc_ip = None
34 _redfish_ip = None
35 _redfish_port = None
36
37+ class ConfigurationError(Exception):
38+ def __init__(self, msg):
39+ super().__init__(msg)
40+
41 def __str__(self):
42 return "Redfish"
43
44@@ -953,27 +967,32 @@ class Redfish(IPMIBase):
45 }
46 )
47
48- def _configure_network(self, iface, data):
49+ def _configure_network(
50+ self, iface, data, config_path="/etc/netplan/99-redfish.yaml"
51+ ):
52 # Host IP Assignment Type:
53 # Possible values: Unknown, Static, DHCP, AutoConfigure, HostSelected
54 netplan_config = None
55 assignment_type = get_smbios_value(data, "Host IP Assignment Type")
56 if assignment_type in ("Unknown", "AutoConfigure", "HostSelected"):
57- raise ValueError("Unsupported Host IP Assignment Type")
58+ raise self.ConfigurationError(
59+ "Unsupported Host IP Assignment Type"
60+ )
61
62 if assignment_type == "Static":
63 ipv4_addr = get_smbios_value(data, "IPv4 Address")
64 ipv4_mask = get_smbios_value(data, "IPv4 Mask")
65 if not all((ipv4_addr, ipv4_mask)):
66- raise ValueError("Missing Host IPv4 information")
67+ raise self.ConfigurationError("Missing Host IPv4 information")
68
69- ipv4 = ipaddress.IPv4Network((ipv4_addr, ipv4_mask)).with_prefixlen
70+ prefix_length = ipaddress.IPv4Network((0, ipv4_mask)).prefixlen
71+ ipv4 = f"{ipv4_addr}/{prefix_length}"
72 netplan_config = self._generate_netplan_config(iface, False, ipv4)
73
74 if assignment_type == "DHCP":
75 netplan_config = self._generate_netplan_config(iface, True)
76
77- with open("/etc/netplan/99-redfish.yaml", "w") as netplan:
78+ with open(config_path, "w") as netplan:
79 netplan.write(netplan_config)
80
81 # TODO: How to determine if netplan was applied?
82@@ -982,39 +1001,44 @@ class Redfish(IPMIBase):
83 def _detect(self):
84 data = self._get_smbios_data()
85 if data is None:
86- raise ValueError("Missing SMBIOS data")
87+ raise self.ConfigurationError("Missing SMBIOS data")
88
89 vendor_id = get_smbios_value(data, "idVendor")[2:]
90 product_id = get_smbios_value(data, "idProduct")[2:]
91
92 if not all((vendor_id, product_id)):
93- raise ValueError("Missing idVendor and idProduct information")
94+ raise self.ConfigurationError(
95+ "Missing idVendor and idProduct information"
96+ )
97
98 iface = None
99 with open(os.environ["MAAS_RESOURCES_FILE"]) as fd:
100- iface = get_network_interface(fd.read(), vendor_id, product_id)
101+ data = json.load(fd)
102+ iface = get_network_interface(data, vendor_id, product_id)
103 if iface is None:
104- raise ValueError("Missing Redfish Host Interface")
105+ raise self.ConfigurationError("Missing Redfish Host Interface")
106
107 self._configure_network(iface, data)
108
109 self.add_bmc_user()
110
111- self.redfish_ip = get_smbios_value(
112+ self._redfish_ip = get_smbios_value(
113 data, "IPv4 Redfish Service Address"
114 )
115- self.redfish_port = get_smbios_value(data, "Redfish Service Port")
116+ self._redfish_port = get_smbios_value(data, "Redfish Service Port")
117
118- if not all((self.redfish_ip, self.redfish_port)):
119- raise ValueError("Missing Redfish Service information.")
120+ if not all((self._redfish_ip, self._redfish_port)):
121+ raise self.ConfigurationError(
122+ "Missing Redfish Service information."
123+ )
124
125 def detected(self):
126 try:
127 self._detect()
128 return self.get_bmc_ip() is not None
129 # XXX: we should know which exceptions to expect, so we can handle them
130- except ValueError as err:
131- print(f"ERROR: {err}")
132+ except self.ConfigurationError as err:
133+ print(f"ERROR: {err}\n{traceback.format_exc()}")
134 return False
135
136 def _get_bmc_ip(self):
137diff --git a/src/metadataserver/builtin_scripts/commissioning_scripts/tests/test_bmc_config.py b/src/metadataserver/builtin_scripts/commissioning_scripts/tests/test_bmc_config.py
138index 2aaa3c0..63c72b0 100644
139--- a/src/metadataserver/builtin_scripts/commissioning_scripts/tests/test_bmc_config.py
140+++ b/src/metadataserver/builtin_scripts/commissioning_scripts/tests/test_bmc_config.py
141@@ -8,6 +8,7 @@ import os
142 import random
143 import re
144 from subprocess import CalledProcessError, DEVNULL, TimeoutExpired
145+import tempfile
146 import textwrap
147 from unittest.mock import call, MagicMock
148 import urllib
149@@ -1417,6 +1418,93 @@ class TestRedfish(MAASTestCase):
150
151 self.assertEqual("127.0.0.1", self.redfish.get_bmc_ip())
152
153+ def test_configure_network(self):
154+ data = textwrap.dedent(
155+ """\
156+ Device Type: USB
157+ idVendor: 0x046b
158+ idProduct: 0xffb0
159+ Protocol ID: 04 (Redfish over IP)
160+ Service UUID: a0635470-debf-0010-9c04-d85ed302b24a
161+ Host IP Assignment Type: Static
162+ Host IP Address Format: IPv4
163+ IPv4 Address: 169.254.95.120
164+ IPv4 Mask: 255.255.0.0
165+ Redfish Service IP Discovery Type: Static
166+ Redfish Service IP Address Format: IPv4
167+ IPv4 Redfish Service Address: 169.254.95.118
168+ IPv4 Redfish Service Mask: 255.255.0.0
169+ Redfish Service Port: 443
170+ Redfish Service Vlan: 0
171+ Redfish Service Hostname:"""
172+ )
173+
174+ expected_config = textwrap.dedent(
175+ """
176+ network:
177+ version: 2
178+ ethernets:
179+ eth0:
180+ addresses: [169.254.95.120/16]
181+ dhcp4: false
182+ """
183+ )
184+
185+ with tempfile.NamedTemporaryFile(
186+ prefix="redfish-netplan", mode="w+"
187+ ) as netconfig:
188+ mock_check_output = self.patch(bmc_config, "check_output")
189+ self.redfish._configure_network("eth0", data, netconfig.name)
190+ self.assertEqual(
191+ yaml.safe_dump((yaml.safe_load(expected_config))),
192+ netconfig.read(),
193+ )
194+
195+ mock_check_output.assert_called_once_with(
196+ ["netplan", "apply"], timeout=bmc_config.COMMAND_TIMEOUT
197+ )
198+
199+ def test_add_bmc_user(self):
200+ username = factory.make_name("username")
201+ password = factory.make_name("password")
202+ self.redfish = bmc_config.Redfish(username, password)
203+ mock_bmc_set = self.patch(self.redfish, "_bmc_set")
204+ mock_bmc_set_keys = self.patch(self.redfish, "_bmc_set_keys")
205+ self.redfish._bmc_config = {
206+ "User1": {},
207+ "User2": {
208+ "Username": "",
209+ "Password": factory.make_name("password"),
210+ "Enable_User": "Yes",
211+ "Lan_Privilege_Limit": "Administrator",
212+ "Lan_Enable_IPMI_Msgs": "Yes",
213+ },
214+ }
215+ self.redfish._privilege_level = "OPERATOR"
216+
217+ self.redfish.add_bmc_user()
218+
219+ self.assertEqual(username, self.redfish.username)
220+ self.assertEqual(password, self.redfish.password)
221+ # Verify bmc_set is only called for values that have changed
222+ mock_bmc_set.assert_has_calls(
223+ (
224+ call("User2", "Username", username),
225+ call("User2", "Password", password),
226+ call("User2", "Lan_Privilege_Limit", "Operator"),
227+ )
228+ )
229+
230+ mock_bmc_set_keys.assert_called_once_with(
231+ "User2",
232+ [
233+ "Lan_Enable_Link_Auth",
234+ "SOL_Payload_Access",
235+ "Serial_Enable_Link_Auth",
236+ ],
237+ "Yes",
238+ )
239+
240
241 class TestGetIPMILocateOutput(MAASTestCase):
242 def test_get_ipmi_locate_output(self):

Subscribers

People subscribed via source and target branches