3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
30 from oslo_config import cfg
31 from oslo_log import log
32 from ironic_python_agent import utils
33 from ironic_lib import utils as ironic_utils
34 from ironic_python_agent import errors
35 from oslo_concurrency import processutils
38 LOG = log.getLogger(__name__)
39 dhclient_physIfaces = []
42 if os.path.exists("/usr/sbin/dhclient"):
43 return "/usr/sbin/dhclient"
44 elif os.path.exists("/sbin/dhclient"):
45 return "/sbin/dhclient"
47 raise RuntimeError("Could not find dhclient")
49 def stop_dhclient_process(interface):
50 """Stop a DHCP process before running os-net-config.
52 :param interface: The interface on which to stop dhclient.
54 pid_file = '/var/run/dhclient-%s.pid' % (interface)
56 dhclient = dhclient_path()
57 except RuntimeError as err:
58 LOG.info('Exception when stopping dhclient: %s' % err)
61 if os.path.exists(pid_file):
62 LOG.info('Stopping %s on interface %s' % (dhclient, interface))
63 utils.execute(dhclient, '-r', '-pf', pid_file, interface)
66 except OSError as err:
67 LOG.error('Could not remove dhclient pid file \'%s\': %s' %
70 def _poll_interface(_ifacedata):
71 ifacedata = json.loads(_ifacedata)
72 global dhclient_physIfaces
75 if "network_config" in ifacedata:
76 for netconfdata in ifacedata["network_config"]:
77 if "device" in netconfdata:
78 if "bond" not in netconfdata["device"]:
79 # Is (physical) interface
80 LOG.debug('Physical device %s' % netconfdata["device"])
81 physIfaces.append(netconfdata["device"])
83 elif "members" in netconfdata:
84 # logical interface with member (f.ex bond)
85 for _member in netconfdata["members"]:
87 if _member["type"] == 'interface':
89 LOG.debug('Physical device %s' % _member["name"])
90 physIfaces.append(_member["name"])
91 elif "name" in netconfdata:
92 if "type" in netconfdata and netconfdata["type"] == 'interface':
93 LOG.debug('Physical device %s' % netconfdata["name"])
94 physIfaces.append(netconfdata["name"])
96 LOG.info('Checking for physical device(s) "%s"' % ', '.join(physIfaces))
97 dhclient_physIfaces = list(physIfaces)
101 while len(physIfaces) > 0 and max_wait_secs >= 0:
103 max_wait_secs = max_wait_secs - wait_secs
105 for _device in physIfaces:
106 devicepath = "/sys/class/net/%s/device" % _device
107 LOG.debug('Check path "%s"' % devicepath )
108 if os.path.exists(devicepath):
109 LOG.debug('Device "%s" in known by kernel' % _device)
110 physIfaces.remove(_device)
112 LOG.debug('Device "%s" in not (yet) known by kernel' % _device)
113 missing_devices.append(_device)
115 if len(physIfaces) > 0:
116 LOG.info('Device(s) not (yet?) known by kernel: "%s"' % ', '.join(missing_devices))
117 time.sleep(wait_secs)
120 if len(physIfaces) > 0:
121 msg = 'Timeout, Device(s) missing: "%s"' % ', '.join(physIfaces)
123 raise errors.VirtualMediaBootError(msg)
125 LOG.info('All physical devices found.')
127 for _device in dhclient_physIfaces:
128 stop_dhclient_process(_device)
130 def _configure_static_net(os_net_config):
131 """Configures network using os-net-config utility"""
132 global dhclient_physIfaces
133 LOG.debug("Configuring static network with os-net-config: %s", os_net_config)
135 os.makedirs('/etc/os-net-config/')
136 except OSError as exc:
137 if exc.errno != errno.EEXIST:
140 with open('/etc/os-net-config/config.yaml', 'w') as fp:
141 fp.write(os_net_config)
144 _poll_interface(os_net_config)
145 except Exception as e:
146 LOG.info('Exception while checking for physical interfaces: %s' % str(e) )
149 os.system('/usr/sbin/ip a > /tmp/ifaces_before_initial_netconfig')
150 except Exception as e:
151 LOG.info('Exception while logging runtime ifaces to /tmp/ifaces_before_initial_netconfig: %s' % str(e) )
153 LOG.info('Running os-net-config..')
155 cmd = [ '/usr/bin/os-net-config', '--detailed-exit-codes', '-v', '-c', '/etc/os-net-config/config.yaml']
159 retries = retries - 1
160 netconf_process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
162 output = netconf_process.stdout.readline()
163 if output == '' and netconf_process.poll() is not None:
166 LOG.info(output.strip())
168 rc = netconf_process.poll()
169 LOG.info('os-net-config exit with status %d' % rc)
171 # os-net-config returns:
172 # 0 when nothing changed,
174 # 2 when config was modified (assuming option "--detailed-exit-codes")
176 if rc == 0 or rc == 1:
177 LOG.info('os-net-config modified nothing or execution error. Not what we want..')
178 LOG.info('Attempt removing physical interface ifcfg-files to force os-net-config to reconfigure')
180 for iface in dhclient_physIfaces:
181 ifcfg_file = '/etc/sysconfig/network-scripts/ifcfg-' + str(iface)
183 LOG.info('Removing "%s"' % ifcfg_file)
184 os.system('/usr/bin/rm -f ' + ifcfg_file)
185 except Exception as e:
186 LOG.info('Ignoring exception when removing "%s": %s' % (ifcfg_file, str(e)))
188 time.sleep(wait_secs)
191 LOG.info('os-net-config done.')
194 LOG.info('os-net-config unknown exit code??')
195 time.sleep(wait_secs)
197 # Config should be in place assuming os-net-config above was successfull
198 # As additional step restart network.service
199 LOG.info('Restarting network.service')
201 cmd = ['/usr/bin/systemctl', 'restart', 'network']
202 subprocess.check_call(cmd)
203 except Exception as e:
204 LOG.info('Igoring exception when restarting network service: %s' % str(e))
208 def get_file_size(filename):
209 "Get the file size by seeking at end"
210 fd= os.open(filename, os.O_RDONLY)
212 return os.lseek(fd, 0, os.SEEK_END)
216 def wait_for_cd_device():
217 """ This function waits for /dev/sr0 device to appear """
218 inputiso = '/dev/sr0'
220 while not os.path.exists(inputiso) and wait_count:
221 LOG.debug('Waiting for %s to appear. Time left = %d secs' %(inputiso,wait_count))
226 msg = "Unable to find device %s" %(inputiso)
227 raise errors.VirtualMediaBootError(msg)
229 def check_cd_config():
230 """ This function checks for any extended 64K block in CD.
231 If it is available it will extract the contents for the block.
232 Loop mount the image for reading configuration parameters.
234 inputiso = '/dev/sr0'
235 outputtgz = '/tmp/cdconf.tgz'
236 mode = os.stat(inputiso).st_mode
237 if stat.S_ISBLK(mode):
238 filesize = get_file_size(inputiso)
239 skip = filesize / 2048-32
240 ironic_utils.dd(inputiso, outputtgz, 'bs=2k', 'skip=%d'%skip)
242 # Check if tgz file is valid.
244 utils.execute("/usr/bin/gzip", '-t', outputtgz)
245 except processutils.ProcessExecutionError as err:
246 if 'not in gzip format' in err.stderr:
247 LOG.info('File is not gzip format skipping!!')
250 LOG.info('Configuration file in gzip format proceeding for extraction')
251 tar = tarfile.open(outputtgz)
252 tar.extractall('/tmp/floppy')
255 dir_list = os.listdir('/tmp/floppy')
256 for item in dir_list:
257 if item.find('.img') != -1:
258 os.mkdir('/tmp/floppy/mnt')
259 utils.execute("mount", '-o', 'loop', '/tmp/floppy/%s' %item, '/tmp/floppy/mnt')
262 def _get_vmedia_params():
263 """This method returns the parameters passed through virtual media floppy.
265 :returns: a partial dict of potential agent configuration parameters
266 :raises: VirtualMediaBootError when it cannot find the virtual media device
268 parameters_file = "parameters.txt"
270 vmedia_device_file_lower_case = "/dev/disk/by-label/ir-vfd-dev"
271 vmedia_device_file_upper_case = "/dev/disk/by-label/IR-VFD-DEV"
272 if os.path.exists(vmedia_device_file_lower_case):
273 vmedia_device_file = vmedia_device_file_lower_case
274 elif os.path.exists(vmedia_device_file_upper_case):
275 vmedia_device_file = vmedia_device_file_upper_case
278 # TODO(rameshg87): This block of code is there only for compatibility
279 # reasons (so that newer agent can work with older Ironic). Remove
280 # this after Liberty release.
281 vmedia_device = utils._get_vmedia_device()
282 if not vmedia_device:
283 msg = "Unable to find virtual media device"
284 raise errors.VirtualMediaBootError(msg)
286 vmedia_device_file = os.path.join("/dev", vmedia_device)
288 vmedia_mount_point = tempfile.mkdtemp()
291 stdout, stderr = utils.execute("mount", vmedia_device_file,
293 except processutils.ProcessExecutionError as e:
294 msg = ("Unable to mount virtual media device %(device)s: "
295 "%(error)s" % {'device': vmedia_device_file, 'error': e})
296 raise errors.VirtualMediaBootError(msg)
298 parameters_file_path = os.path.join(vmedia_mount_point,
300 params = _read_params_from_file(parameters_file_path, '\n')
303 stdout, stderr = utils.execute("umount", vmedia_mount_point)
304 except processutils.ProcessExecutionError as e:
308 shutil.rmtree(vmedia_mount_point)
309 except Exception as e:
314 def _read_params_from_file(filepath, seperator=None):
315 """Extract key=value pairs from a file.
317 :param filepath: path to a file containing key=value pairs separated by
318 whitespace or newlines.
319 :returns: a dictionary representing the content of the file
321 with open(filepath) as f:
324 options = cmdline.split(seperator)
326 for option in options:
327 if '=' not in option:
329 k, v = option.split('=', 1)
335 log.register_options(CONF)
336 CONF(args=sys.argv[1:])
337 log.setup(CONF, 'virtmedia-netconfig')
338 LOG.info("Starting virtmedia-netconfig!!")
340 params = _read_params_from_file('/proc/cmdline')
341 # If the node booted over virtual media, the parameters are passed
342 # in a text file within the virtual media floppy.
344 if params.get('boot_method') == 'vmedia':
345 LOG.debug("Erasing old filesystems")
346 utils.execute('/usr/bin/erase-oldfs.sh')
348 LOG.info("This node is booted with vmedia. Checking for available virtual media!!")
351 vmedia_params = _get_vmedia_params()
352 params.update(vmedia_params)
353 LOG.debug("vmedia parameters: %r", vmedia_params)
354 os_net_config = params.get('os_net_config')
355 LOG.info("virtmedia: os_net_config=%s" %os_net_config)
357 _configure_static_net(os_net_config)
361 if __name__ == "__main__":