update the commit message to address the different behavior (we now write json for 1).
is it possible to put cloud-init-dhclient-hook outside of /usr/bin ? we could put it in /usr/lib/cloud-init and hten just name it 'dhclient-hook' . i say this because its not really intende to be used as an executable. other suggestions in line. this is looking good though, thanks Brent and Lars. Diff comments: > === added file 'cloudinit/cmd/dhclient_hook.py' > --- cloudinit/cmd/dhclient_hook.py 1970-01-01 00:00:00 +0000 > +++ cloudinit/cmd/dhclient_hook.py 2016-07-13 22:57:56 +0000 > @@ -0,0 +1,51 @@ > +import os > +import json > +from cloudinit import stages > +from cloudinit import log as logging > + > + > +class LogDhclient(): > + > + LOG = logging.getLogger(__name__) > + > + def __init__(self): > + self.hooks_dir = self._get_hooks_dir() > + > + @staticmethod > + def _get_hooks_dir(): > + i = stages.Init() > + return os.path.join(i.paths.get_runpath(), 'dhclient.hooks') > + > + def check_hooks_dir(self): > + if not os.path.exists(self.hooks_dir): > + os.makedirs(self.hooks_dir) > + else: > + hook_files = [os.path.join(self.hooks_dir, x) > + for x in os.listdir(self.hooks_dir)] > + for hook_file in hook_files: > + os.remove(hook_file) > + > + @staticmethod > + def get_vals(info): > + new_info = {} > + for k, v in info.iteritems(): > + if k.startswith("DHCP4_") or k.startswith("new_"): > + key = (k.replace('DHCP4_', '').replace('new_', '')).lower() > + new_info[key] = v > + return new_info > + > + def record(self): > + envs = os.environ > + ifc_name = envs.get("interface", envs.get("DEVICE_IFACE", None)) > + if ifc_name is None: > + return > + ifc_file_name = os.path.join(self.hooks_dir, ifc_name + '.json') > + with open(ifc_file_name, 'w') as outfile: > + json.dump(self.get_vals(envs), outfile, indent=4) return json.dumps(data, indent=1, sort_keys=True, separators=(',', ': ')).encode('utf-8') that is nice and pretty output. ie, please use sort_keys and separators also. > + self.LOG.debug("Wrote dhclient options in %s", ifc_file_name) > + > + > +def main(): > + record = LogDhclient() > + record.check_hooks_dir() > + record.record() > > === modified file 'cloudinit/sources/helpers/azure.py' > --- cloudinit/sources/helpers/azure.py 2016-05-12 20:52:56 +0000 > +++ cloudinit/sources/helpers/azure.py 2016-07-13 22:57:56 +0000 > @@ -213,15 +228,74 @@ > return socket.inet_ntoa(packed_bytes) > > @staticmethod > - def find_endpoint(): > + def _get_value_from_leases_file(lease_file): > + leases = [] > + content = util.load_file(lease_file) > + LOG.debug("content is {}".format(content)) > + for line in content.splitlines(): > + if 'unknown-245' in line: can you give an example here of what that line looks like ? line.strip(''..... that is hard to parse in your head. > + leases.append(line.strip(' ').split(' ', > 2)[-1].strip(';\n"')) > + # Return the "most recent" one in the list > + if len(leases) < 1: > + return None > + else: > + return leases[-1] > + > + @staticmethod > + def _load_dhclient_json(): > + dhcp_options = {} > + hooks_dir = WALinuxAgentShim._get_hooks_dir() > + if not os.path.exists(hooks_dir): > + LOG.debug("%s not found.", hooks_dir) > + return None > + hook_files = [os.path.join(hooks_dir, x) > + for x in os.listdir(hooks_dir)] > + for hook_file in hook_files: > + try: > + _file_dict = json.load(open(hook_file)) > + dhcp_options[os.path.basename( > + hook_file).replace('.json', '')] = _file_dict > + except ValueError: > + LOG.info("%s is not valid JSON data", hook_file) Probably best to raise some exception here so caller can tell the difference between "there just wasnt data in the file" and "the file did not exist" > + return dhcp_options > + > + @staticmethod > + def _get_value_from_dhcpoptions(dhcp_options): > + if dhcp_options is None: > + return None > + # the MS endpoint server is given to us as DHPC option 245 > + _value = None > + for interface in dhcp_options: > + _value = dhcp_options[interface].get('unknown_245', None) > + if _value is not None: > + break > + return _value > + > + @staticmethod > + def find_endpoint(lease_file): > LOG.debug('Finding Azure endpoint...') > - content = util.load_file('/var/lib/dhcp/dhclient.eth0.leases') > value = None > - for line in content.splitlines(): > - if 'unknown-245' in line: > - value = line.strip(' ').split(' ', 2)[-1].strip(';\n"') > - if value is None: > - raise ValueError('No endpoint found in DHCP config.') > + # Option-245 stored in /run/cloud-init/dhclient.hooks/<ifc>.json > + # a dhclient exit hook that calls cloud-init-dhclient-hook > + LOG.debug("Lease file %s found", lease_file) > + dhcp_options = WALinuxAgentShim._load_dhclient_json() > + value = WALinuxAgentShim._get_value_from_dhcpoptions(dhcp_options) > + LOG.debug("value is {}".format(value)) > + if value is None: > + # Fallback and check the leases file if unsuccessful > + LOG.debug("Unable to find endpoint in dhclient logs. " > + " Falling back to check lease files") > + if lease_file is None: > + LOG.warn("No lease file was specified.") > + value = None > + else: > + value = WALinuxAgentShim._get_value_from_leases_file( > + lease_file) > + LOG.debug("second value is {}".format(value)) > + > + if value is None: > + raise ValueError('No endpoint found.') > + > endpoint_ip_address = WALinuxAgentShim.get_ip_from_lease_value(value) > LOG.debug('Azure endpoint found at %s', endpoint_ip_address) > return endpoint_ip_address > @@ -231,10 +305,11 @@ > http_client = > AzureEndpointHttpClient(self.openssl_manager.certificate) > LOG.info('Registering with Azure...') > attempts = 0 > + end_point = self.endpoint > while True: > try: > response = http_client.get( > - > 'http://{0}/machine/?comp=goalstate'.format(self.endpoint)) > + 'http://{0}/machine/?comp=goalstate'.format(end_point)) revert this, as i dont think you need the saved chars for line lenght, do you? and then recvert line end_point= above also. just to get rid of unrelated small changes. > except Exception: > if attempts < 10: > time.sleep(attempts + 1) > > === added file 'tools/NM-cloudinit' > --- tools/NM-cloudinit 1970-01-01 00:00:00 +0000 > +++ tools/NM-cloudinit 2016-07-13 22:57:56 +0000 > @@ -0,0 +1,4 @@ > +#!/bin/bash > +# Writes DHCP lease information into the cloud-init data directory. > +/usr/bin/cloud-init-dhclient-hook Reading NetworkManager(8) i see that Network Manger says | NetworkManager will execute scripts in the | /etc/NetworkManager/dispatcher.d directory or subdirectories in | alphabetical order in response to network events. Each script should be a | regular executable file owned by root. Furthermore, it must not be | writable by group or other, and not setuid. | Each script receives two arguments, the first being the interface name | of the device an operation just happened on, and second the action. For | device actions, the interface is the name of the kernel interface suitable | for IP configuration. Thus it is either VPN_IP_IFACE, DEVICE_IP_IFACE, or | DEVICE_IFACE, as applicable. For the hostname action it is always "none". So lets do the following: a.) change to /bin/sh b.) use 'exec /usr/bin/cloud-init-dhclient-hook' we probably only want to do this on *some* actions probably 'up' or 'dhcp4-change' ? #!/bin/sh case "$1:$2" in up:*) exec /usr/bin/cloud-init-dhclient-hook;; down:*) ;; # do we need to clean up?;; esac > + > > === added file 'tools/cloud-init-log-dhcp' > --- tools/cloud-init-log-dhcp 1970-01-01 00:00:00 +0000 > +++ tools/cloud-init-log-dhcp 2016-07-13 22:57:56 +0000 > @@ -0,0 +1,5 @@ > +#!/bin/bash i believe this file is *sourced* by dhclient when executing hooks, and dhclient (i believe) uses bash to do that. so #!/bin/bash is reasonable here, but we should mention this is *sourced*, so we should not 'exec cloud-init-dhclient-hook' > +# Writes DHCP lease information into the cloud-init data directory. > + > +cloud-init-dhclient-hook > + -- https://code.launchpad.net/~bbaude/cloud-init/azure_dhcp/+merge/298677 Your team cloud init development team is requested to review the proposed merge of lp:~bbaude/cloud-init/azure_dhcp into lp:cloud-init. _______________________________________________ Mailing list: https://launchpad.net/~cloud-init-dev Post to : cloud-init-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~cloud-init-dev More help : https://help.launchpad.net/ListHelp