From 1b005e29f2069cb796a78073dc49e791071c6b7a Mon Sep 17 00:00:00 2001 From: Sushant Sharma Date: Thu, 18 Jan 2018 17:50:39 -0800 Subject: [PATCH 1/2] Enable VM move from one Vnet to another in Azure (#2) Added a new module in cloud-init that listens for interface going up-down (that indicates azure moved VM from one network to another), and then trigger a redhcp. --- cloudinit/config/cc_setup_azure_networking.py | 52 ++++++++ cloudinit/sources/helpers/azuredhcpagent.py | 125 ++++++++++++++++++ config/cloud.cfg.tmpl | 3 +- 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 cloudinit/config/cc_setup_azure_networking.py create mode 100644 cloudinit/sources/helpers/azuredhcpagent.py diff --git a/cloudinit/config/cc_setup_azure_networking.py b/cloudinit/config/cc_setup_azure_networking.py new file mode 100644 index 0000000..0e96bbd --- /dev/null +++ b/cloudinit/config/cc_setup_azure_networking.py @@ -0,0 +1,52 @@ +# Copyright (C) Microsoft Corp. +# +# Author: Tamilmani Manoharan +# +# This file is part of cloud-init. See LICENSE file for license information. + +""" +Setup Azure Networking +------------------ +**Summary:** sets up networking required only for azure virtual machines. + +This module handles any specific networking requirements for azure virtual machines (VMs). +In the present implementation, it handles moving a virtual machine from one azure virtual +network to another. + +**Internal name:** ``cc_setup_azure_networking`` + +**Module frequency:** per instance + +**Supported distros:** all +""" + +from cloudinit.settings import PER_ALWAYS +import time +import os +import socket +import struct +import glob +from subprocess import Popen +from cloudinit import importer +from cloudinit import config +from cloudinit import type_utils + +frequency = PER_ALWAYS + +def IsAzure(): + azure_config_exists = False + for n in glob.glob("/etc/cloud/cloud.cfg.d/*azure.cfg"): + if os.path.isfile(n): + azure_config_exists=True + return azure_config_exists + +def handle(name, _cfg, _cloud, log, _args): + if IsAzure(): + configs_dir = os.path.dirname(os.path.abspath(__file__)) + log.debug("config dir %s", configs_dir) + proc = Popen(["python", configs_dir + "/../sources/helpers/azuredhcpagent.py"]) + log.debug("Created netagent process") + + + + \ No newline at end of file diff --git a/cloudinit/sources/helpers/azuredhcpagent.py b/cloudinit/sources/helpers/azuredhcpagent.py new file mode 100644 index 0000000..349b668 --- /dev/null +++ b/cloudinit/sources/helpers/azuredhcpagent.py @@ -0,0 +1,125 @@ +# Author: Tamilmani Manoharan +# +# This file is part of cloud-init. See LICENSE file for license information. + +import sys +import logging +import socket +import os +import struct +import subprocess +from logging.handlers import RotatingFileHandler + +RTMGRP_LINK = 1 +NLMSG_NOOP = 1 +NLMSG_ERROR = 2 +RTM_NEWLINK = 16 +RTM_DELLINK = 17 +IFLA_IFNAME = 3 + +def GetLogger(): + log = logging.getLogger("azuredhcpagent") + log.setLevel(logging.DEBUG) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)s - %(message)s') + + handler = RotatingFileHandler("/var/log/azuredhcpagent.log", maxBytes=10485760, + backupCount=5) + + handler.setFormatter(formatter) + log.addHandler(handler) + return log + +def main(): + + log = GetLogger() + # Create the netlink socket and bind to RTMGRP_LINK, + s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE) + s.bind((os.getpid(), RTMGRP_LINK)) + + while True: + + data = s.recv(65535) + msg_len, msg_type, flags, seq, pid = struct.unpack("=LHHLL", data[:16]) + + if msg_type == NLMSG_NOOP: + log.debug("noop") + continue + elif msg_type == NLMSG_ERROR: + log.debug("error") + break + + # We fundamentally only care about NEWLINK messages in this version. + if msg_type != RTM_NEWLINK: + continue + + data = data[16:] + + family, _, if_type, index, flags, change = struct.unpack("=BBHiII", data[:16]) + + remaining = msg_len - 32 + data = data[16:] + + while remaining: + rta_len, rta_type = struct.unpack("=HH", data[:4]) + + # This check comes from RTA_OK, and terminates a string of routing + # attributes. + if rta_len < 4: + break + + rta_data = data[4:rta_len] + + increment = (rta_len + 4 - 1) & ~(4 - 1) + data = data[increment:] + remaining -= increment + + # The link is up! + if rta_type == IFLA_IFNAME: + log.debug("New link %s", rta_data) + ifname = str(rta_data).strip('\0') + + operfilename = "/sys/class/net/" + ifname + "/operstate" + operfilename = operfilename.strip("\0") + carrierfilename = "/sys/class/net/" + ifname + "/carrier" + carrierfilename = carrierfilename.strip("\0") + + log.debug("operfilename %s", operfilename) + + carrier="" + operstate="" + + try: + file = open(operfilename, "r") + operstate = file.readline() + file.close() + except Exception as e: + log.error("exception reading operstate %s", str(e)) + + operstate = operstate.rstrip() + log.debug("operstate %s", operstate) + + try: + file = open(carrierfilename, "r") + carrier = file.readline() + file.close() + + except IOError as io: + if io.errno != 22: + log.error("IO error reading carrier %s errno %d", str(io), io.errno) + + except Exception as e: + log.error("exception reading carrier %s", str(e)) + + + carrier = carrier.rstrip() + log.debug("carrier %s", carrier) + + if operstate == "up" or carrier == "1": + log.debug("trigger dhcp") + # assuming dhclient exists + return_code = subprocess.call("dhclient " + ifname, shell=True) + log.debug("dhclient return status %d", return_code) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl index 32de9c9..f61f2f3 100644 --- a/config/cloud.cfg.tmpl +++ b/config/cloud.cfg.tmpl @@ -125,7 +125,8 @@ cloud_final_modules: - phone-home - final-message - power-state-change - + - setup-azure-networking + # System and/or distro specific settings # (not accessible to handlers/transforms) system_info: From 4e71156410693d8e74fa1bf92cc5a01d093dc381 Mon Sep 17 00:00:00 2001 From: Tamilmani Manoharan Date: Fri, 19 Jan 2018 11:16:29 -0800 Subject: [PATCH 2/2] Fixed code styling --- cloudinit/config/cc_setup_azure_networking.py | 2 +- cloudinit/sources/helpers/azuredhcpagent.py | 27 +++++++------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/cloudinit/config/cc_setup_azure_networking.py b/cloudinit/config/cc_setup_azure_networking.py index 0e96bbd..b1e89a3 100644 --- a/cloudinit/config/cc_setup_azure_networking.py +++ b/cloudinit/config/cc_setup_azure_networking.py @@ -15,7 +15,7 @@ **Internal name:** ``cc_setup_azure_networking`` -**Module frequency:** per instance +**Module frequency:** per always **Supported distros:** all """ diff --git a/cloudinit/sources/helpers/azuredhcpagent.py b/cloudinit/sources/helpers/azuredhcpagent.py index 349b668..b9e76ab 100644 --- a/cloudinit/sources/helpers/azuredhcpagent.py +++ b/cloudinit/sources/helpers/azuredhcpagent.py @@ -8,6 +8,7 @@ import os import struct import subprocess + from logging.handlers import RotatingFileHandler RTMGRP_LINK = 1 @@ -20,32 +21,32 @@ def GetLogger(): log = logging.getLogger("azuredhcpagent") log.setLevel(logging.DEBUG) - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)s - %(message)s') - + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)s - %(message)s') handler = RotatingFileHandler("/var/log/azuredhcpagent.log", maxBytes=10485760, backupCount=5) - handler.setFormatter(formatter) log.addHandler(handler) return log def main(): - + """Azuredhcpagent listens on netlink socket to receive link up/down notification and trigger dhcp client + either the link or carrier is up. + """ log = GetLogger() + # Create the netlink socket and bind to RTMGRP_LINK, s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE) s.bind((os.getpid(), RTMGRP_LINK)) - while True: - + while True: data = s.recv(65535) msg_len, msg_type, flags, seq, pid = struct.unpack("=LHHLL", data[:16]) if msg_type == NLMSG_NOOP: - log.debug("noop") + log.debug("nlmsg noop") continue elif msg_type == NLMSG_ERROR: - log.debug("error") + log.debug("nlmsg error") break # We fundamentally only care about NEWLINK messages in this version. @@ -53,9 +54,7 @@ def main(): continue data = data[16:] - family, _, if_type, index, flags, change = struct.unpack("=BBHiII", data[:16]) - remaining = msg_len - 32 data = data[16:] @@ -68,7 +67,6 @@ def main(): break rta_data = data[4:rta_len] - increment = (rta_len + 4 - 1) & ~(4 - 1) data = data[increment:] remaining -= increment @@ -77,14 +75,10 @@ def main(): if rta_type == IFLA_IFNAME: log.debug("New link %s", rta_data) ifname = str(rta_data).strip('\0') - operfilename = "/sys/class/net/" + ifname + "/operstate" operfilename = operfilename.strip("\0") carrierfilename = "/sys/class/net/" + ifname + "/carrier" carrierfilename = carrierfilename.strip("\0") - - log.debug("operfilename %s", operfilename) - carrier="" operstate="" @@ -102,15 +96,12 @@ def main(): file = open(carrierfilename, "r") carrier = file.readline() file.close() - except IOError as io: if io.errno != 22: log.error("IO error reading carrier %s errno %d", str(io), io.errno) - except Exception as e: log.error("exception reading carrier %s", str(e)) - carrier = carrier.rstrip() log.debug("carrier %s", carrier)