[virt-who] Add systemd support

Radek Novacek rnovacek at fedoraproject.org
Tue Nov 27 12:09:43 UTC 2012


commit 375cedbf373bc6a5e425c4bc9f5b288d3a1e97fd
Author: Radek Novacek <rnovacek at redhat.com>
Date:   Tue Nov 27 13:09:37 2012 +0100

    Add systemd support
    
    - specfile cleanup

 .gitignore                                       |    2 +-
 sources                                          |    2 +-
 virt-who-0.8-add-hyperv-support.patch            | 1026 ++++++++++++++++++++++
 virt-who-0.8-create-pid-file-asap.patch          |   60 ++
 virt-who-0.8-fix-adding-https-to-esx-url.patch   |   22 +
 virt-who-0.8-help-and-manpage-improvements.patch |   34 +
 virt-who-0.8-journal-logging.patch               |   76 ++
 virt-who-0.8-systemd.patch                       |   46 +
 virt-who.spec                                    |  115 +++-
 9 files changed, 1366 insertions(+), 17 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 7b1c028..1e9a8f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1 @@
-/virt-who-0.2.tar.gz
+/virt-who-0.8.tar.gz
diff --git a/sources b/sources
index 2ef97f8..0bf4ad5 100644
--- a/sources
+++ b/sources
@@ -1 +1 @@
-35b4b5372d7585f45422373255b32113  virt-who-0.2.tar.gz
+e628065668fb1035237c363cfc52f5be  virt-who-0.8.tar.gz
diff --git a/virt-who-0.8-add-hyperv-support.patch b/virt-who-0.8-add-hyperv-support.patch
new file mode 100644
index 0000000..013c810
--- /dev/null
+++ b/virt-who-0.8-add-hyperv-support.patch
@@ -0,0 +1,1026 @@
+diff --git a/README.hyperv b/README.hyperv
+new file mode 100644
+index 0000000..532f64e
+--- /dev/null
++++ b/README.hyperv
+@@ -0,0 +1,28 @@
++In order to make virt-who connection to Hyper-V work, some steps needs to be done first.
++
++1. Windows Remote Management must be enabled and HTTP or HTTPS listener must be running.
++
++    Following command can be used on Hyper-V server:
++
++        winrm quickconfig
++
++
++2. Firewall must allow Remote Administration
++
++    Following command can be used on Hyper-V server:
++
++        netsh advfirewall firewall set rule group="Remote Administration" new enable=yes
++
++
++3. Unencrypted connection must be enabled for HTTP (not required for HTTPS)
++
++    Following command can be used on Hyper-V server:
++
++        winrm set winrm/config/service @{AllowUnencrypted="true"}
++
++
++4. Only Basic and NTLM authentication methods are supported
++
++    Verify that at least one of methods Basic or Negotiate is enabled (True)
++
++        winrm get winrm/config/service/auth
+diff --git a/hyperv.py b/hyperv.py
+new file mode 100644
+index 0000000..2fcb1a3
+--- /dev/null
++++ b/hyperv.py
+@@ -0,0 +1,292 @@
++
++import sys
++import httplib
++import urlparse
++import base64
++from uuid import uuid1
++
++# Import XML parser
++try:
++    from elementtree import ElementTree
++except ImportError:
++    from xml.etree import ElementTree
++
++import ntlm
++
++NAMESPACES = {
++    's':     'http://www.w3.org/2003/05/soap-envelope',
++    'wsa':   'http://schemas.xmlsoap.org/ws/2004/08/addressing',
++    'wsman': 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd',
++    'wsen':  'http://schemas.xmlsoap.org/ws/2004/09/enumeration'
++}
++
++ENVELOPE = """<?xml version="1.0" encoding="UTF-8"?>
++<s:Envelope """ + " ".join(('xmlns:%s="%s"' % (k, v) for k, v in NAMESPACES.items())) + """>
++    %s
++    %s
++</s:Envelope>"""
++
++def getHeader(action):
++    return """<s:Header>
++        <wsa:Action s:mustUnderstand="true">""" + NAMESPACES['wsen'] + "/" + action + """</wsa:Action>
++        <wsa:To s:mustUnderstand="true">%(url)s</wsa:To>
++        <wsman:ResourceURI s:mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/wmi/%(namespace)s/*</wsman:ResourceURI>
++        <wsa:MessageID s:mustUnderstand="true">uuid:""" + str(uuid1()) + """</wsa:MessageID>
++        <wsa:ReplyTo>
++            <wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>
++        </wsa:ReplyTo>
++    </s:Header>"""
++
++ENUMERATE_BODY = """<s:Body>
++        <wsen:Enumerate>
++            <wsman:Filter Dialect="http://schemas.microsoft.com/wbem/wsman/1/WQL">%(query)s</wsman:Filter>
++        </wsen:Enumerate>
++    </s:Body>"""
++
++PULL_BODY = """<s:Body>
++        <wsen:Pull>
++            <wsen:EnumerationContext>%(EnumerationContext)s</wsen:EnumerationContext>
++        </wsen:Pull>
++    </s:Body>"""
++
++ENUMERATE_XML = ENVELOPE % (getHeader("Enumerate"), ENUMERATE_BODY)
++PULL_XML = ENVELOPE % (getHeader("Pull"), PULL_BODY)
++
++class HyperVSoap(object):
++    def __init__(self, url, connection, headers):
++        self.url = url
++        self.connection = connection
++        self.headers = headers
++
++    def post(self, body):
++        self.headers["Content-Length"] = "%d" % len(body)
++        self.connection.request("POST", self.url, body=body, headers=self.headers)
++        response = self.connection.getresponse()
++        if response.status == 401:
++            raise HyperVAuthFailed("Authentication failed")
++        if response.status != 200:
++            raise HyperVException("Communication with Hyper-V failed, HTTP error: %d" % response.status)
++        if response is None:
++            raise HyperVException("No reply from Hyper-V")
++        return response
++
++    @classmethod
++    def _Instance(cls, xml):
++        def stripNamespace(tag):
++            return tag[tag.find("}") + 1:]
++        children = xml.getchildren()
++        if len(children) < 1:
++            return None
++        child = children[0]
++        properties = {}
++        for ch in child.getchildren():
++            properties[stripNamespace(ch.tag)] = ch.text
++        return properties
++
++    def Enumerate(self, query, namespace="root/virtualization"):
++        data = ENUMERATE_XML % { 'url': self.url, 'query': query, 'namespace': namespace }
++        response = self.post(data)
++        d = response.read()
++        xml = ElementTree.fromstring(d)
++        if xml.tag != "{%(s)s}Envelope" % NAMESPACES:
++            raise HyperVException("Wrong reply format")
++        responses = xml.findall("{%(s)s}Body/{%(wsen)s}EnumerateResponse" % NAMESPACES)
++        if len(responses) < 1:
++            raise HyperVException("Wrong reply format")
++        contexts = responses[0].getchildren()
++        if len(contexts) < 1:
++            raise HyperVException("Wrong reply format")
++
++        if contexts[0].tag != "{%(wsen)s}EnumerationContext" % NAMESPACES:
++            raise HyperVException("Wrong reply format")
++        return contexts[0].text
++
++    def _PullOne(self, uuid, namespace):
++        data = PULL_XML % { 'url': self.url, 'EnumerationContext': uuid, 'namespace': namespace }
++        response = self.post(data)
++        d = response.read()
++        xml = ElementTree.fromstring(d)
++        if xml.tag != "{%(s)s}Envelope" % NAMESPACES:
++            raise HyperVException("Wrong reply format")
++        responses = xml.findall("{%(s)s}Body/{%(wsen)s}PullResponse" % NAMESPACES)
++        if len(responses) < 0:
++            raise HyperVException("Wrong reply format")
++
++        uuid = None
++        instance = None
++
++        for node in responses[0].getchildren():
++            if node.tag == "{%(wsen)s}EnumerationContext" % NAMESPACES:
++                uuid = node.text
++            elif node.tag == "{%(wsen)s}Items" % NAMESPACES:
++                instance = HyperVSoap._Instance(node)
++
++        return uuid, instance
++
++    def Pull(self, uuid, namespace="root/virtualization"):
++        instances = []
++        while uuid is not None:
++            uuid, instance = self._PullOne(uuid, namespace)
++            if instance is not None:
++                instances.append(instance)
++        return instances
++
++
++class HyperVException(Exception):
++    pass
++
++class HyperVAuthFailed(HyperVException):
++    pass
++
++
++class HyperV:
++    def __init__(self, logger, url, username, password):
++        self.logger = logger
++        self.username = username
++        self.password = password
++
++        # Parse URL and create proper one
++        if "//" not in url:
++            url = "//" + url
++        parsed = urlparse.urlsplit(url, "http")
++        if ":" not in parsed[1]:
++            if parsed[0] == "https":
++                self.host = parsed[1] + ":5986"
++            else:
++                self.host = parsed[1] + ":5985"
++        else:
++            self.host = parsed[1]
++        if parsed[2] == "":
++            path = "wsman"
++        else:
++            path = parsed[2]
++        self.url = urlparse.urlunsplit((parsed[0], self.host, path, "", ""))
++
++        logger.debug("Hyper-V url: %s" % self.url)
++
++        # Check if we have domain defined and set flags accordingly
++        user_parts = username.split('\\', 1)
++        if len(user_parts) == 1:
++            self.username = user_parts[0]
++            self.domainname = ''
++            self.type1_flags = ntlm.NTLM_TYPE1_FLAGS & ~ntlm.NTLM_NegotiateOemDomainSupplied
++        else:
++            self.domainname = user_parts[0].upper()
++            self.username = user_parts[1]
++            self.type1_flags = ntlm.NTLM_TYPE1_FLAGS
++
++    def connect(self):
++        if self.url.startswith("https"):
++            connection = httplib.HTTPSConnection(self.host)
++        else:
++            connection = httplib.HTTPConnection(self.host)
++
++        headers = {}
++        headers["Connection"] = "Keep-Alive"
++        headers["Content-Length"] = "0"
++
++        connection.request("POST", self.url, headers=headers)
++        response = connection.getresponse()
++        response.read()
++        if response.status == 200:
++            return connection, headers
++        elif response.status == 404:
++            raise HyperVException("Invalid HyperV url: %s" % self.url)
++        elif response.status != 401:
++            raise HyperVException("Unable to connect to HyperV at: %s" % self.url)
++        # 401 - need authentication
++
++        authenticate_header = response.getheader("WWW-Authenticate", "")
++        if 'Negotiate' in authenticate_header:
++            try:
++                self.ntlmAuth(connection, headers)
++            except HyperVAuthFailed:
++                if 'Basic' in authenticate_header:
++                    self.basicAuth(connection, headers)
++                else:
++                    raise
++        elif 'Basic' in authenticate_header:
++            self.basicAuth(connection, headers)
++        else:
++            raise HyperVAuthFailed("Server doesn't known any supported authentication method")
++        return connection, headers
++
++    def ntlmAuth(self, connection, headers):
++        self.logger.debug("Using NTLM authentication")
++        # Use ntlm
++        headers["Authorization"] = "Negotiate %s" % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.username, self.type1_flags)
++
++        connection.request("POST", self.url, headers=headers)
++        response = connection.getresponse()
++        response.read()
++        if response.status != 401:
++            raise HyperVAuthFailed("NTLM negotiation failed")
++
++        auth_header = response.getheader("WWW-Authenticate", "")
++        if auth_header == "":
++            raise HyperVAuthFailed("NTLM negotiation failed")
++
++        nego, challenge = auth_header.split(" ")
++        if nego != "Negotiate":
++            print >>sys.stderr, "Wrong header: ", auth_header
++            sys.exit(1)
++
++        nonce, flags = ntlm.parse_NTLM_CHALLENGE_MESSAGE(challenge)
++        headers["Authorization"] = "Negotiate %s" % ntlm.create_NTLM_AUTHENTICATE_MESSAGE(nonce, self.username, self.domainname, self.password, flags)
++
++        connection.request("POST", self.url, headers=headers)
++        response = connection.getresponse()
++        response.read()
++        if response.status == 200:
++            headers.pop("Authorization")
++            self.logger.debug("NTLM authentication successful")
++        else:
++            raise HyperVAuthFailed("NTLM negotiation failed")
++
++    def basicAuth(self, connection, headers):
++        self.logger.debug("Using Basic authentication")
++
++        passphrase = "%s:%s" % (self.username, self.password)
++        encoded = base64.encodestring(passphrase)
++        headers["Authorization"] = "Basic %s" % encoded.replace('\n', '')
++
++    @classmethod
++    def decodeWinUUID(cls, uuid):
++        """ Windows UUID needs to be decoded using following key
++        From: {78563412-AB90-EFCD-1234-567890ABCDEF}
++        To:    12345678-90AB-CDEF-1234-567890ABCDEF
++        """
++        if uuid[0] == "{":
++            s = uuid[1:-1]
++        else:
++            s = uuid
++        return s[6:8] + s[4:6] + s[2:4] + s[0:2] + "-" + s[11:13] + s[9:11] + "-" + s[16:18] + s[14:16] + s[18:]
++
++    def getHostGuestMapping(self):
++        guests = []
++        connection, headers = self.connect()
++        hypervsoap = HyperVSoap(self.url, connection, headers)
++        # SettingType == 3 means current setting, 5 is snapshot - we don't want snapshots
++        uuid = hypervsoap.Enumerate("select BIOSGUID from Msvm_VirtualSystemSettingData where SettingType = 3")
++        for instance in hypervsoap.Pull(uuid):
++            guests.append(HyperV.decodeWinUUID(instance["BIOSGUID"]))
++        uuid = hypervsoap.Enumerate("select UUID from Win32_ComputerSystemProduct", "root/cimv2")
++        host = None
++        for instance in hypervsoap.Pull(uuid, "root/cimv2"):
++            host = HyperV.decodeWinUUID(instance["UUID"])
++        return { host: guests }
++
++    def ping(self):
++        return True
++
++if __name__ == '__main__':
++    # TODO: read from config
++    if len(sys.argv) < 4:
++        print "Usage: %s url username password"
++        sys.exit(0)
++
++    import logging
++    logger = logging.Logger("")
++    logger.addHandler(logging.StreamHandler())
++    hyperv = HyperV(logger, sys.argv[1], sys.argv[2], sys.argv[3])
++    print hyperv.getHostGuestMapping()
+diff --git a/ntlm.py b/ntlm.py
+new file mode 100644
+index 0000000..d950611
+--- /dev/null
++++ b/ntlm.py
+@@ -0,0 +1,494 @@
++# This library is free software: you can redistribute it and/or
++# modify it under the terms of the GNU Lesser General Public
++# License as published by the Free Software Foundation, either
++# version 3 of the License, or (at your option) any later version.
++
++# This library is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++# Lesser General Public License for more details.
++#
++# You should have received a copy of the GNU Lesser General Public
++# License along with this library.  If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
++
++import struct
++import base64
++import string
++import hashlib
++import hmac
++import random
++from socket import gethostname
++
++import M2Crypto
++
++
++# DES handling functions
++
++def des_encrypt(key_str, plain_text):
++    k = str_to_key56(key_str)
++    k = key56_to_key64(k)
++    key_str = ''
++    for i in k:
++        key_str += chr(i & 0xFF)
++    des = M2Crypto.EVP.Cipher("des_ecb", key=key_str, op=M2Crypto.encrypt, iv='\0'*16)
++
++    return des.update(plain_text)
++
++def str_to_key56(key_str):
++    if len(key_str) < 7:
++        key_str = key_str + '\000\000\000\000\000\000\000'[:(7 - len(key_str))]
++    key_56 = []
++    for i in key_str[:7]: key_56.append(ord(i))
++
++    return key_56
++
++def key56_to_key64(key_56):
++    ""
++    key = []
++    for i in range(8): key.append(0)
++
++    key[0] = key_56[0];
++    key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1);
++    key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2);
++    key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3);
++    key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4);
++    key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5);
++    key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6);
++    key[7] =  (key_56[6] << 1) & 0xFF;
++
++    key = set_key_odd_parity(key)
++
++    return key
++
++def set_key_odd_parity(key):
++    ""
++    for i in range(len(key)):
++        for k in range(7):
++            bit = 0
++            t = key[i] >> k
++            bit = (t ^ bit) & 0x1
++        key[i] = (key[i] & 0xFE) | bit
++
++    return key
++
++# NTLM implemntation
++
++NTLM_NegotiateUnicode                =  0x00000001
++NTLM_NegotiateOEM                    =  0x00000002
++NTLM_RequestTarget                   =  0x00000004
++NTLM_Unknown9                        =  0x00000008
++NTLM_NegotiateSign                   =  0x00000010
++NTLM_NegotiateSeal                   =  0x00000020
++NTLM_NegotiateDatagram               =  0x00000040
++NTLM_NegotiateLanManagerKey          =  0x00000080
++NTLM_Unknown8                        =  0x00000100
++NTLM_NegotiateNTLM                   =  0x00000200
++NTLM_NegotiateNTOnly                 =  0x00000400
++NTLM_Anonymous                       =  0x00000800
++NTLM_NegotiateOemDomainSupplied      =  0x00001000
++NTLM_NegotiateOemWorkstationSupplied =  0x00002000
++NTLM_Unknown6                        =  0x00004000
++NTLM_NegotiateAlwaysSign             =  0x00008000
++NTLM_TargetTypeDomain                =  0x00010000
++NTLM_TargetTypeServer                =  0x00020000
++NTLM_TargetTypeShare                 =  0x00040000
++NTLM_NegotiateExtendedSecurity       =  0x00080000
++NTLM_NegotiateIdentify               =  0x00100000
++NTLM_Unknown5                        =  0x00200000
++NTLM_RequestNonNTSessionKey          =  0x00400000
++NTLM_NegotiateTargetInfo             =  0x00800000
++NTLM_Unknown4                        =  0x01000000
++NTLM_NegotiateVersion                =  0x02000000
++NTLM_Unknown3                        =  0x04000000
++NTLM_Unknown2                        =  0x08000000
++NTLM_Unknown1                        =  0x10000000
++NTLM_Negotiate128                    =  0x20000000
++NTLM_NegotiateKeyExchange            =  0x40000000
++NTLM_Negotiate56                     =  0x80000000
++
++# we send these flags with our type 1 message
++NTLM_TYPE1_FLAGS = (NTLM_NegotiateUnicode | \
++                    NTLM_NegotiateOEM | \
++                    NTLM_RequestTarget | \
++                    NTLM_NegotiateNTLM | \
++                    NTLM_NegotiateOemDomainSupplied | \
++                    NTLM_NegotiateOemWorkstationSupplied | \
++                    NTLM_NegotiateAlwaysSign | \
++                    NTLM_NegotiateExtendedSecurity | \
++                    NTLM_NegotiateVersion | \
++                    NTLM_Negotiate128 | \
++                    NTLM_Negotiate56 )
++NTLM_TYPE2_FLAGS = (NTLM_NegotiateUnicode | \
++                    NTLM_RequestTarget | \
++                    NTLM_NegotiateNTLM | \
++                    NTLM_NegotiateAlwaysSign | \
++                    NTLM_NegotiateExtendedSecurity | \
++                    NTLM_NegotiateTargetInfo | \
++                    NTLM_NegotiateVersion | \
++                    NTLM_Negotiate128 | \
++                    NTLM_Negotiate56)
++
++NTLM_MsvAvEOL             = 0 # Indicates that this is the last AV_PAIR in the list. AvLen MUST be 0. This type of information MUST be present in the AV pair list.
++NTLM_MsvAvNbComputerName  = 1 # The server's NetBIOS computer name. The name MUST be in Unicode, and is not null-terminated. This type of information MUST be present in the AV_pair list.
++NTLM_MsvAvNbDomainName    = 2 # The server's NetBIOS domain name. The name MUST be in Unicode, and is not null-terminated. This type of information MUST be present in the AV_pair list.
++NTLM_MsvAvDnsComputerName = 3 # The server's Active Directory DNS computer name. The name MUST be in Unicode, and is not null-terminated.
++NTLM_MsvAvDnsDomainName   = 4 # The server's Active Directory DNS domain name. The name MUST be in Unicode, and is not null-terminated.
++NTLM_MsvAvDnsTreeName     = 5 # The server's Active Directory (AD) DNS forest tree name. The name MUST be in Unicode, and is not null-terminated.
++NTLM_MsvAvFlags           = 6 # A field containing a 32-bit value indicating server or client configuration. 0x00000001: indicates to the client that the account authentication is constrained. 0x00000002: indicates that the client is providing message integrity in the MIC field (section 2.2.1.3) in the AUTHENTICATE_MESSAGE.
++NTLM_MsvAvTimestamp       = 7 # A FILETIME structure ([MS-DTYP] section 2.3.1) in little-endian byte order that contains the server local time.<12>
++NTLM_MsAvRestrictions     = 8 #A Restriction_Encoding structure (section 2.2.2.2). The Value field contains a structure representing the integrity level of the security principal, as well as a MachineID created at computer startup to identify the calling machine. <13>
++
++
++"""
++utility functions for Microsoft NTLM authentication
++
++References:
++[MS-NLMP]: NT LAN Manager (NTLM) Authentication Protocol Specification
++http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NLMP%5D.pdf
++
++[MS-NTHT]: NTLM Over HTTP Protocol Specification
++http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NTHT%5D.pdf
++
++Cntlm Authentication Proxy
++http://cntlm.awk.cz/
++
++NTLM Authorization Proxy Server
++http://sourceforge.net/projects/ntlmaps/
++
++Optimized Attack for NTLM2 Session Response
++http://www.blackhat.com/presentations/bh-asia-04/bh-jp-04-pdfs/bh-jp-04-seki.pdf
++"""
++def dump_NegotiateFlags(NegotiateFlags):
++    if NegotiateFlags & NTLM_NegotiateUnicode:
++        print "NTLM_NegotiateUnicode set"
++    if NegotiateFlags & NTLM_NegotiateOEM:
++        print "NTLM_NegotiateOEM set"
++    if NegotiateFlags & NTLM_RequestTarget:
++        print "NTLM_RequestTarget set"
++    if NegotiateFlags & NTLM_Unknown9:
++        print "NTLM_Unknown9 set"
++    if NegotiateFlags & NTLM_NegotiateSign:
++        print "NTLM_NegotiateSign set"
++    if NegotiateFlags & NTLM_NegotiateSeal:
++        print "NTLM_NegotiateSeal set"
++    if NegotiateFlags & NTLM_NegotiateDatagram:
++        print "NTLM_NegotiateDatagram set"
++    if NegotiateFlags & NTLM_NegotiateLanManagerKey:
++        print "NTLM_NegotiateLanManagerKey set"
++    if NegotiateFlags & NTLM_Unknown8:
++        print "NTLM_Unknown8 set"
++    if NegotiateFlags & NTLM_NegotiateNTLM:
++        print "NTLM_NegotiateNTLM set"
++    if NegotiateFlags & NTLM_NegotiateNTOnly:
++        print "NTLM_NegotiateNTOnly set"
++    if NegotiateFlags & NTLM_Anonymous:
++        print "NTLM_Anonymous set"
++    if NegotiateFlags & NTLM_NegotiateOemDomainSupplied:
++        print "NTLM_NegotiateOemDomainSupplied set"
++    if NegotiateFlags & NTLM_NegotiateOemWorkstationSupplied:
++        print "NTLM_NegotiateOemWorkstationSupplied set"
++    if NegotiateFlags & NTLM_Unknown6:
++        print "NTLM_Unknown6 set"
++    if NegotiateFlags & NTLM_NegotiateAlwaysSign:
++        print "NTLM_NegotiateAlwaysSign set"
++    if NegotiateFlags & NTLM_TargetTypeDomain:
++        print "NTLM_TargetTypeDomain set"
++    if NegotiateFlags & NTLM_TargetTypeServer:
++        print "NTLM_TargetTypeServer set"
++    if NegotiateFlags & NTLM_TargetTypeShare:
++        print "NTLM_TargetTypeShare set"
++    if NegotiateFlags & NTLM_NegotiateExtendedSecurity:
++        print "NTLM_NegotiateExtendedSecurity set"
++    if NegotiateFlags & NTLM_NegotiateIdentify:
++        print "NTLM_NegotiateIdentify set"
++    if NegotiateFlags & NTLM_Unknown5:
++        print "NTLM_Unknown5 set"
++    if NegotiateFlags & NTLM_RequestNonNTSessionKey:
++        print "NTLM_RequestNonNTSessionKey set"
++    if NegotiateFlags & NTLM_NegotiateTargetInfo:
++        print "NTLM_NegotiateTargetInfo set"
++    if NegotiateFlags & NTLM_Unknown4:
++        print "NTLM_Unknown4 set"
++    if NegotiateFlags & NTLM_NegotiateVersion:
++        print "NTLM_NegotiateVersion set"
++    if NegotiateFlags & NTLM_Unknown3:
++        print "NTLM_Unknown3 set"
++    if NegotiateFlags & NTLM_Unknown2:
++        print "NTLM_Unknown2 set"
++    if NegotiateFlags & NTLM_Unknown1:
++        print "NTLM_Unknown1 set"
++    if NegotiateFlags & NTLM_Negotiate128:
++        print "NTLM_Negotiate128 set"
++    if NegotiateFlags & NTLM_NegotiateKeyExchange:
++        print "NTLM_NegotiateKeyExchange set"
++    if NegotiateFlags & NTLM_Negotiate56:
++        print "NTLM_Negotiate56 set"
++
++def create_NTLM_NEGOTIATE_MESSAGE(user, type1_flags=NTLM_TYPE1_FLAGS):
++    BODY_LENGTH = 40
++    Payload_start = BODY_LENGTH # in bytes
++    protocol = 'NTLMSSP\0'    #name
++
++    type = struct.pack('<I',1) #type 1
++
++    flags =  struct.pack('<I', type1_flags)
++    Workstation = gethostname().upper().encode('ascii')
++    user_parts = user.split('\\', 1)
++    DomainName = user_parts[0].upper().encode('ascii')
++    EncryptedRandomSessionKey = ""
++
++    WorkstationLen = struct.pack('<H', len(Workstation))
++    WorkstationMaxLen = struct.pack('<H', len(Workstation))
++    WorkstationBufferOffset = struct.pack('<I', Payload_start)
++    Payload_start += len(Workstation)
++    DomainNameLen = struct.pack('<H', len(DomainName))
++    DomainNameMaxLen = struct.pack('<H', len(DomainName))
++    DomainNameBufferOffset = struct.pack('<I',Payload_start)
++    Payload_start += len(DomainName)
++    ProductMajorVersion = struct.pack('<B', 5)
++    ProductMinorVersion = struct.pack('<B', 1)
++    ProductBuild = struct.pack('<H', 2600)
++    VersionReserved1 = struct.pack('<B', 0)
++    VersionReserved2 = struct.pack('<B', 0)
++    VersionReserved3 = struct.pack('<B', 0)
++    NTLMRevisionCurrent = struct.pack('<B', 15)
++
++    msg1 = protocol + type + flags + \
++            DomainNameLen + DomainNameMaxLen + DomainNameBufferOffset + \
++            WorkstationLen + WorkstationMaxLen + WorkstationBufferOffset + \
++            ProductMajorVersion + ProductMinorVersion + ProductBuild + \
++            VersionReserved1 + VersionReserved2 + VersionReserved3 + NTLMRevisionCurrent
++    assert BODY_LENGTH==len(msg1), "BODY_LENGTH: %d != msg1: %d" % (BODY_LENGTH,len(msg1))
++    msg1 += Workstation + DomainName
++    msg1 = base64.encodestring(msg1)
++    msg1 = string.replace(msg1, '\n', '')
++    return msg1
++
++def parse_NTLM_CHALLENGE_MESSAGE(msg2):
++    ""
++    msg2 = base64.decodestring(msg2)
++    Signature = msg2[0:8]
++    msg_type = struct.unpack("<I",msg2[8:12])[0]
++    assert(msg_type==2)
++    TargetNameLen = struct.unpack("<H",msg2[12:14])[0]
++    TargetNameMaxLen = struct.unpack("<H",msg2[14:16])[0]
++    TargetNameOffset = struct.unpack("<I",msg2[16:20])[0]
++    TargetName = msg2[TargetNameOffset:TargetNameOffset+TargetNameMaxLen]
++    NegotiateFlags = struct.unpack("<I",msg2[20:24])[0]
++    ServerChallenge = msg2[24:32]
++    Reserved = msg2[32:40]
++    TargetInfoLen = struct.unpack("<H",msg2[40:42])[0]
++    TargetInfoMaxLen = struct.unpack("<H",msg2[42:44])[0]
++    TargetInfoOffset = struct.unpack("<I",msg2[44:48])[0]
++    TargetInfo = msg2[TargetInfoOffset:TargetInfoOffset+TargetInfoLen]
++    i=0
++    TimeStamp = '\0'*8
++    while(i<TargetInfoLen):
++        AvId = struct.unpack("<H",TargetInfo[i:i+2])[0]
++        AvLen = struct.unpack("<H",TargetInfo[i+2:i+4])[0]
++        AvValue = TargetInfo[i+4:i+4+AvLen]
++        i = i+4+AvLen
++        if AvId == NTLM_MsvAvTimestamp:
++            TimeStamp = AvValue
++        #~ print AvId, AvValue.decode('utf-16')
++    return (ServerChallenge, NegotiateFlags)
++
++def create_NTLM_AUTHENTICATE_MESSAGE(nonce, user, domain, password, NegotiateFlags):
++    ""
++    is_unicode  = NegotiateFlags & NTLM_NegotiateUnicode
++    is_NegotiateExtendedSecurity = NegotiateFlags & NTLM_NegotiateExtendedSecurity
++
++    flags =  struct.pack('<I',NTLM_TYPE2_FLAGS)
++
++    BODY_LENGTH = 72
++    Payload_start = BODY_LENGTH # in bytes
++
++    Workstation = gethostname().upper()
++    DomainName = domain.upper()
++    UserName = user
++    EncryptedRandomSessionKey = ""
++    if is_unicode:
++        Workstation = Workstation.encode('utf-16-le')
++        DomainName = DomainName.encode('utf-16-le')
++        UserName = UserName.encode('utf-16-le')
++        EncryptedRandomSessionKey = EncryptedRandomSessionKey.encode('utf-16-le')
++    LmChallengeResponse = calc_resp(create_LM_hashed_password_v1(password), nonce)
++    NtChallengeResponse = calc_resp(create_NT_hashed_password_v1(password), nonce)
++
++    if is_NegotiateExtendedSecurity:
++        pwhash = create_NT_hashed_password_v1(password, UserName, DomainName)
++        ClientChallenge = ""
++        for i in range(8):
++           ClientChallenge+= chr(random.getrandbits(8))
++        (NtChallengeResponse, LmChallengeResponse) = ntlm2sr_calc_resp(pwhash, nonce, ClientChallenge) #='\x39 e3 f4 cd 59 c5 d8 60')
++    Signature = 'NTLMSSP\0'
++    MessageType = struct.pack('<I',3)  #type 3
++
++    DomainNameLen = struct.pack('<H', len(DomainName))
++    DomainNameMaxLen = struct.pack('<H', len(DomainName))
++    DomainNameOffset = struct.pack('<I', Payload_start)
++    Payload_start += len(DomainName)
++
++    UserNameLen = struct.pack('<H', len(UserName))
++    UserNameMaxLen = struct.pack('<H', len(UserName))
++    UserNameOffset = struct.pack('<I', Payload_start)
++    Payload_start += len(UserName)
++
++    WorkstationLen = struct.pack('<H', len(Workstation))
++    WorkstationMaxLen = struct.pack('<H', len(Workstation))
++    WorkstationOffset = struct.pack('<I', Payload_start)
++    Payload_start += len(Workstation)
++
++    LmChallengeResponseLen = struct.pack('<H', len(LmChallengeResponse))
++    LmChallengeResponseMaxLen = struct.pack('<H', len(LmChallengeResponse))
++    LmChallengeResponseOffset = struct.pack('<I', Payload_start)
++    Payload_start += len(LmChallengeResponse)
++
++    NtChallengeResponseLen = struct.pack('<H', len(NtChallengeResponse))
++    NtChallengeResponseMaxLen = struct.pack('<H', len(NtChallengeResponse))
++    NtChallengeResponseOffset = struct.pack('<I', Payload_start)
++    Payload_start += len(NtChallengeResponse)
++
++    EncryptedRandomSessionKeyLen = struct.pack('<H', len(EncryptedRandomSessionKey))
++    EncryptedRandomSessionKeyMaxLen = struct.pack('<H', len(EncryptedRandomSessionKey))
++    EncryptedRandomSessionKeyOffset = struct.pack('<I',Payload_start)
++    Payload_start +=  len(EncryptedRandomSessionKey)
++    NegotiateFlags = flags
++
++    ProductMajorVersion = struct.pack('<B', 5)
++    ProductMinorVersion = struct.pack('<B', 1)
++    ProductBuild = struct.pack('<H', 2600)
++    VersionReserved1 = struct.pack('<B', 0)
++    VersionReserved2 = struct.pack('<B', 0)
++    VersionReserved3 = struct.pack('<B', 0)
++    NTLMRevisionCurrent = struct.pack('<B', 15)
++
++    MIC = struct.pack('<IIII',0,0,0,0)
++    msg3 = Signature + MessageType + \
++            LmChallengeResponseLen + LmChallengeResponseMaxLen + LmChallengeResponseOffset + \
++            NtChallengeResponseLen + NtChallengeResponseMaxLen + NtChallengeResponseOffset + \
++            DomainNameLen + DomainNameMaxLen + DomainNameOffset + \
++            UserNameLen + UserNameMaxLen + UserNameOffset + \
++            WorkstationLen + WorkstationMaxLen + WorkstationOffset + \
++            EncryptedRandomSessionKeyLen + EncryptedRandomSessionKeyMaxLen + EncryptedRandomSessionKeyOffset + \
++            NegotiateFlags + \
++            ProductMajorVersion + ProductMinorVersion + ProductBuild + \
++            VersionReserved1 + VersionReserved2 + VersionReserved3 + NTLMRevisionCurrent
++    assert BODY_LENGTH==len(msg3), "BODY_LENGTH: %d != msg3: %d" % (BODY_LENGTH,len(msg3))
++    Payload = DomainName + UserName + Workstation + LmChallengeResponse + NtChallengeResponse + EncryptedRandomSessionKey
++    msg3 += Payload
++    msg3 = base64.encodestring(msg3)
++    msg3 = string.replace(msg3, '\n', '')
++    return msg3
++
++def calc_resp(password_hash, server_challenge):
++    """calc_resp generates the LM response given a 16-byte password hash and the
++        challenge from the Type-2 message.
++        @param password_hash
++            16-byte password hash
++        @param server_challenge
++            8-byte challenge from Type-2 message
++        returns
++            24-byte buffer to contain the LM response upon return
++    """
++    # padding with zeros to make the hash 21 bytes long
++    password_hash = password_hash + '\0' * (21 - len(password_hash))
++    return des_encrypt(password_hash[ 0: 7], server_challenge[0:8]) + \
++           des_encrypt(password_hash[ 7:14], server_challenge[0:8]) + \
++           des_encrypt(password_hash[14:21], server_challenge[0:8])
++
++def ComputeResponse(ResponseKeyNT, ResponseKeyLM, ServerChallenge, ServerName, ClientChallenge='\xaa'*8, Time='\0'*8):
++    LmChallengeResponse = hmac.new(ResponseKeyLM, ServerChallenge+ClientChallenge).digest() + ClientChallenge
++
++    Responserversion = '\x01'
++    HiResponserversion = '\x01'
++    temp = Responserversion + HiResponserversion + '\0'*6 + Time + ClientChallenge + '\0'*4 + ServerChallenge + '\0'*4
++    NTProofStr  = hmac.new(ResponseKeyNT, ServerChallenge + temp).digest()
++    NtChallengeResponse = NTProofStr + temp
++
++    SessionBaseKey = hmac.new(ResponseKeyNT, NTProofStr).digest()
++    return (NtChallengeResponse, LmChallengeResponse)
++
++def ntlm2sr_calc_resp(ResponseKeyNT, ServerChallenge, ClientChallenge='\xaa'*8):
++    LmChallengeResponse = ClientChallenge + '\0'*16
++    sess = hashlib.md5(ServerChallenge+ClientChallenge).digest()
++    NtChallengeResponse = calc_resp(ResponseKeyNT, sess[0:8])
++    return (NtChallengeResponse, LmChallengeResponse)
++
++def create_LM_hashed_password_v1(passwd):
++    "setup LanManager password"
++    "create LanManager hashed password"
++
++    # fix the password length to 14 bytes
++    passwd = string.upper(passwd)
++    lm_pw = passwd + '\0' * (14 - len(passwd))
++    lm_pw = passwd[0:14]
++
++    # do hash
++    magic_str = "KGS!@#$%" # page 57 in [MS-NLMP]
++
++    return des_encrypt(lm_pw[0:7], magic_str) + des_encrypt(lm_pw[7:14], magic_str)
++
++def create_NT_hashed_password_v1(passwd, user=None, domain=None):
++    "create NT hashed password"
++    digest = hashlib.new('md4', passwd.encode('utf-16le')).digest()
++    return digest
++
++def create_NT_hashed_password_v2(passwd, user, domain):
++    "create NT hashed password"
++    digest = create_NT_hashed_password_v1(passwd)
++
++    return hmac.new(digest, (user.upper()+domain).encode('utf-16le')).digest()
++    return digest
++
++def create_sessionbasekey(password):
++    return hashlib.new('md4', create_NT_hashed_password_v1(password)).digest()
++
++if __name__ == "__main__":
++    def ByteToHex( byteStr ):
++        """
++        Convert a byte string to it's hex string representation e.g. for output.
++        """
++        return ' '.join( [ "%02X" % ord( x ) for x in byteStr ] )
++
++    def HexToByte( hexStr ):
++        """
++        Convert a string hex byte values into a byte string. The Hex Byte values may
++        or may not be space separated.
++        """
++        bytes = []
++
++        hexStr = ''.join( hexStr.split(" ") )
++
++        for i in range(0, len(hexStr), 2):
++            bytes.append( chr( int (hexStr[i:i+2], 16 ) ) )
++
++        return ''.join( bytes )
++
++    ServerChallenge = HexToByte("01 23 45 67 89 ab cd ef")
++    ClientChallenge = '\xaa'*8
++    Time = '\x00'*8
++    Workstation = "COMPUTER".encode('utf-16-le')
++    ServerName = "Server".encode('utf-16-le')
++    User = "User"
++    Domain = "Domain"
++    Password = "Password"
++    RandomSessionKey = '\55'*16
++    assert HexToByte("e5 2c ac 67 41 9a 9a 22 4a 3b 10 8f 3f a6 cb 6d") == create_LM_hashed_password_v1(Password)                  # [MS-NLMP] page 72
++    assert HexToByte("a4 f4 9c 40 65 10 bd ca b6 82 4e e7 c3 0f d8 52") == create_NT_hashed_password_v1(Password)    # [MS-NLMP] page 73
++    assert HexToByte("d8 72 62 b0 cd e4 b1 cb 74 99 be cc cd f1 07 84") == create_sessionbasekey(Password)
++    assert HexToByte("67 c4 30 11 f3 02 98 a2 ad 35 ec e6 4f 16 33 1c 44 bd be d9 27 84 1f 94") == calc_resp(create_NT_hashed_password_v1(Password), ServerChallenge)
++    assert HexToByte("98 de f7 b8 7f 88 aa 5d af e2 df 77 96 88 a1 72 de f1 1c 7d 5c cd ef 13") == calc_resp(create_LM_hashed_password_v1(Password), ServerChallenge)
++
++    (NTLMv1Response,LMv1Response) = ntlm2sr_calc_resp(create_NT_hashed_password_v1(Password), ServerChallenge, ClientChallenge)
++    assert HexToByte("aa aa aa aa aa aa aa aa 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00") == LMv1Response  # [MS-NLMP] page 75
++    assert HexToByte("75 37 f8 03 ae 36 71 28 ca 45 82 04 bd e7 ca f8 1e 97 ed 26 83 26 72 32") == NTLMv1Response
++
++    assert HexToByte("0c 86 8a 40 3b fd 7a 93 a3 00 1e f2 2e f0 2e 3f") == create_NT_hashed_password_v2(Password, User, Domain)    # [MS-NLMP] page 76
++    ResponseKeyLM = ResponseKeyNT = create_NT_hashed_password_v2(Password, User, Domain)
++    (NTLMv2Response,LMv2Response) = ComputeResponse(ResponseKeyNT, ResponseKeyLM, ServerChallenge, ServerName, ClientChallenge, Time)
++    assert HexToByte("86 c3 50 97 ac 9c ec 10 25 54 76 4a 57 cc cc 19 aa aa aa aa aa aa aa aa") == LMv2Response  # [MS-NLMP] page 76
++
++    # expected failure
++    # According to the spec in section '3.3.2 NTLM v2 Authentication' the NTLMv2Response should be longer than the value given on page 77 (this suggests a mistake in the spec)
++    #~ assert HexToByte("68 cd 0a b8 51 e5 1c 96 aa bc 92 7b eb ef 6a 1c") == NTLMv2Response, "\nExpected: 68 cd 0a b8 51 e5 1c 96 aa bc 92 7b eb ef 6a 1c\nActual:   %s" % ByteToHex(NTLMv2Response) # [MS-NLMP] page 77
+diff --git a/virt-who.8 b/virt-who.8
+index f26175e..4b2c283 100644
+--- a/virt-who.8
++++ b/virt-who.8
+@@ -2,7 +2,7 @@
+ .SH NAME
+ virt-who - Agent for reporting virtual guest IDs to Subscription Asset Manager.
+ .SH SYNOPSIS
+-virt-who [-d] [-i INTERVAL] [-b] [-o] [--libvirt|--vdsm|--esx|--rhevm]
++virt-who [-d] [-i INTERVAL] [-b] [-o] [--libvirt|--vdsm|--esx|--rhevm|--hyperv]
+ .SH OPTIONS
+ .TP
+ \fB\-h\fR, \fB\-\-help\fR
+@@ -31,6 +31,9 @@ Register ESX machines using vCenter
+ .TP
+ \fB\-\-rhevm\fR
+ Register guests using RHEV\-M
++.TP
++\fB\-\-hyperv\fR
++Register guests using Hyper\-V
+ .IP
+ .SS vCenter/ESX options
+ .IP
+@@ -69,6 +72,25 @@ Username for connecting to RHEV\-M
+ .TP
+ \fB\-\-rhevm\-password\fR=\fIPASSWORD\fR
+ Password for connecting to RHEV\-M
++.IP
++.SS Hyper\-V options
++.IP
++Use this options with \fB\-\-hyperv\fR
++.TP
++\fB\-\-hyperv\-owner\fR=\fIOWNER\fR
++Organization who has purchased subscriptions of the products
++.TP
++\fB\-\-hyperv\-env\fR=\fIENV\fR
++Environment where the Hyper\-V belongs to
++.TP
++\fB\-\-hyperv\-server\fR=\fISERVER\fR
++URL of the Hyper\-V server to connect to
++.TP
++\fB\-\-hyperv\-username\fR=\fIUSERNAME\fR
++Username for connecting to Hyper\-V
++.TP
++\fB\-\-hyperv\-password\fR=\fIPASSWORD\fR
++Password for connecting to Hyper\-V
+ .PP
+ .SH ENVIRONMENT
+ virt-who also reads environmental variables. They have the same name as command line arguments but upper-cased, with underscore instead of dash and prefixed with VIRTWHO_ (e.g. VIRTWHO_ONE_SHOT). Empty variables are considered as disabled, non-empty as enabled
+@@ -127,6 +149,13 @@ Use ESX (vCenter) as virtualization backend and specify option required to conne
+ 
+ Use RHEV-M as virtualization backend and specify option required to connect to RHEV-M server.
+ 
++.TP
++4. Hyper-V
++
++# virt-who --hyperv --hyperv-owner=HYPERV_OWNER  --hyperv-env=HYPERV_ENV --hyperv-server=HYPERV_SERVER --hyperv-username=HYPERV_USERNAME --hyperv-password=HYPERV_PASSWORD
++
++Use Hyper-V as virtualization backend and specify option required to connect to Hyper-V server.
++
+ .SH LOGGING
+ virt-who always writes error output to file /var/log/rhsm/rhsm.log. In all modes, excluding background ("-b"), it writes same output also to the standard error output.
+ 
+diff --git a/virt-who.conf b/virt-who.conf
+index 043b695..2c117d2 100644
+--- a/virt-who.conf
++++ b/virt-who.conf
+@@ -18,7 +18,7 @@ VIRTWHO_DEBUG=0
+ # configuration.
+ #VIRTWHO_INTERVAL=0
+ 
+-# virt-who mode, enable only one option from following 4:
++# virt-who mode, enable only one option from following 5:
+ # Use libvirt to list virtual guests [default]
+ #VIRTWHO_LIBVIRT=1
+ # Use vdsm to list virtual guests
+@@ -27,6 +27,8 @@ VIRTWHO_DEBUG=0
+ #VIRTWHO_ESX=0
+ # Register guests using RHEV-M
+ #VIRTWHO_RHEVM=0
++# Register guest using Hyper-V
++#VIRTWHO_HYPERV=0
+ 
+ # Option for ESX mode
+ #VIRTWHO_ESX_OWNER=
+@@ -41,3 +43,10 @@ VIRTWHO_DEBUG=0
+ #VIRTWHO_RHEVM_SERVER=
+ #VIRTWHO_RHEVM_USERNAME=
+ #VIRTWHO_RHEVM_PASSWORD=
++
++# Options for HYPER-V mode
++#VIRTWHO_HYPERV_OWNER=
++#VIRTWHO_HYPERV_ENV=
++#VIRTWHO_HYPERV_SERVER=
++#VIRTWHO_HYPERV_USERNAME=
++#VIRTWHO_HYPERV_PASSWORD=
+diff --git a/virt-who.py b/virt-who.py
+index 7b15f14..85d998a 100644
+--- a/virt-who.py
++++ b/virt-who.py
+@@ -28,6 +28,7 @@ from virt import Virt, VirtError
+ from vdsm import VDSM
+ from vsphere import VSphere
+ from rhevm import RHEVM
++from hyperv import HyperV
+ from event import virEventLoopPureStart
+ from subscriptionmanager import SubscriptionManager, SubscriptionManagerError
+ 
+@@ -98,6 +99,8 @@ class VirtWho(object):
+             self.tryRegisterEventCallback()
+         elif self.options.virtType == "rhevm":
+             self.virt = RHEVM(self.logger, self.options.server, self.options.username, self.options.password)
++        elif self.options.virtType == "hyperv":
++            self.virt = HyperV(self.logger, self.options.server, self.options.username, self.options.password)
+         else:
+             # ESX
+             self.virt = VSphere(self.logger, self.options.server, self.options.username, self.options.password)
+@@ -171,7 +174,7 @@ class VirtWho(object):
+                 return False
+ 
+         try:
+-            if self.options.virtType not in ["esx", "rhevm"]:
++            if self.options.virtType not in ["esx", "rhevm", "hyperv"]:
+                 virtualGuests = self.virt.listDomains()
+             else:
+                 virtualGuests = self.virt.getHostGuestMapping()
+@@ -187,7 +190,7 @@ class VirtWho(object):
+                 return False
+ 
+         try:
+-            if self.options.virtType not in ["esx", "rhevm"]:
++            if self.options.virtType not in ["esx", "rhevm", "hyperv"]:
+                 self.subscriptionManager.sendVirtGuests(virtualGuests)
+             else:
+                 result = self.subscriptionManager.hypervisorCheckIn(self.options.owner, self.options.env, virtualGuests)
+@@ -325,6 +328,7 @@ def main():
+     parser.add_option("--vdsm", action="store_const", dest="virtType", const="vdsm", help="Use vdsm to list virtual guests")
+     parser.add_option("--esx", action="store_const", dest="virtType", const="esx", help="Register ESX machines using vCenter")
+     parser.add_option("--rhevm", action="store_const", dest="virtType", const="rhevm", help="Register guests using RHEV-M")
++    parser.add_option("--hyperv", action="store_const", dest="virtType", const="hyperv", help="Register guests using Hyper-V")
+ 
+     esxGroup = OptionGroup(parser, "vCenter/ESX options", "Use this options with --esx")
+     esxGroup.add_option("--esx-owner", action="store", dest="owner", default="", help="Organization who has purchased subscriptions of the products")
+@@ -342,6 +346,14 @@ def main():
+     rhevmGroup.add_option("--rhevm-password", action="store", dest="password", default="", help="Password for connecting to RHEV-M")
+     parser.add_option_group(rhevmGroup)
+ 
++    hypervGroup = OptionGroup(parser, "Hyper-V options", "Use this options with --hyperv")
++    hypervGroup.add_option("--hyperv-owner", action="store", dest="owner", default="", help="Organization who has purchased subscriptions of the products")
++    hypervGroup.add_option("--hyperv-env", action="store", dest="env", default="", help="Environment where the Hyper-V belongs to")
++    hypervGroup.add_option("--hyperv-server", action="store", dest="server", default="", help="URL of the Hyper-V server to connect to")
++    hypervGroup.add_option("--hyperv-username", action="store", dest="username", default="", help="Username for connecting to Hyper-V")
++    hypervGroup.add_option("--hyperv-password", action="store", dest="password", default="", help="Password for connecting to Hyper-V")
++    parser.add_option_group(hypervGroup)
++
+     (options, args) = parser.parse_args()
+ 
+     # Handle enviromental variables
+@@ -379,6 +391,11 @@ def main():
+     if env in ["1", "true"]:
+         options.virtType = "rhevm"
+ 
++    env = os.getenv("VIRTWHO_HYPERV", "0").strip().lower()
++    if env in ["1", "true"]:
++        options.virtType = "hyperv"
++
++
+     def checkEnv(variable, option, name):
+         """
+         If `option` is empty, check enviromental `variable` and return its value.
+@@ -407,6 +424,14 @@ def main():
+         if len(options.password) == 0:
+             options.password = os.getenv("VIRTWHO_RHEVM_PASSWORD", "")
+ 
++    if options.virtType == "hyperv":
++        options.owner = checkEnv("VIRTWHO_HYPERV_OWNER", options.owner, "owner")
++        options.env = checkEnv("VIRTWHO_HYPERV_ENV", options.env, "env")
++        options.server = checkEnv("VIRTWHO_HYPERV_SERVER", options.server, "server")
++        options.username = checkEnv("VIRTWHO_HYPERV_USERNAME", options.username, "username")
++        if len(options.password) == 0:
++            options.password = os.getenv("VIRTWHO_HYPERV_PASSWORD", "")
++
+     if options.interval < 0:
+         logger.warning("Interval is not positive number, ignoring")
+         options.interval = 0
+@@ -434,7 +459,7 @@ def main():
+             logger.debug("Starting event loop")
+             virEventLoopPureStart()
+         else:
+-            logger.warning("Listening for events is not available in VDSM, ESX or RHEV-M mode")
++            logger.warning("Listening for events is not available in VDSM, ESX, RHEV-M or Hyper-V mode")
+ 
+     global RetryInterval
+     if options.interval < RetryInterval:
diff --git a/virt-who-0.8-create-pid-file-asap.patch b/virt-who-0.8-create-pid-file-asap.patch
new file mode 100644
index 0000000..735f5e3
--- /dev/null
+++ b/virt-who-0.8-create-pid-file-asap.patch
@@ -0,0 +1,60 @@
+commit ac15c128524cd317623044d7fbaedb4eef3b557e
+Author: Radek Novacek <rnovacek at redhat.com>
+Date:   Thu Oct 4 12:06:36 2012 +0200
+
+    Create PID file as soon as possible
+    
+    PID file was created too late causing service stop to be unsuccessfull
+    if call too soon after service start.
+    
+    Now PID file is create immediatelly when service is started and
+    recreated after fork.
+
+diff --git a/virt-who.py b/virt-who.py
+index 0c8babf..7b15f14 100644
+--- a/virt-who.py
++++ b/virt-who.py
+@@ -286,7 +286,7 @@ def daemonize(debugMode):
+     os.chdir("/")
+     return True
+ 
+-def createPidFile(logger):
++def createPidFile(logger=None):
+     atexit.register(cleanup)
+     signal.signal(signal.SIGINT, cleanup)
+     signal.signal(signal.SIGTERM, cleanup)
+@@ -297,7 +297,8 @@ def createPidFile(logger):
+         f.write("%d" % os.getpid())
+         f.close()
+     except Exception, e:
+-        logger.error("Unable to create pid file: %s" % str(e))
++        if logger is not None:
++            logger.error("Unable to create pid file: %s" % str(e))
+ 
+ def cleanup(sig=None, stack=None):
+     try:
+@@ -312,6 +313,7 @@ def main():
+     if os.access(PIDFILE, os.F_OK):
+         print >>sys.stderr, "virt-who seems to be already running. If not, remove %s" % PIDFILE
+         sys.exit(1)
++    createPidFile()
+ 
+     parser = OptionParserEpilog(description="Agent for reporting virtual guest IDs to subscription-manager",
+                                 epilog="virt-who also reads enviromental variables. They have the same name as command line arguments but uppercased, with underscore instead of dash and prefixed with VIRTWHO_ (e.g. VIRTWHO_ONE_SHOT). Empty variables are considered as disabled, non-empty as enabled")
+@@ -425,6 +427,7 @@ def main():
+         # Do a double-fork and other daemon initialization
+         if not daemonize(options.debug):
+             logger.error("Unable to fork, continuing in foreground")
++        createPidFile(logger)
+ 
+     if not options.oneshot:
+         if options.background and options.virtType == "libvirt":
+@@ -444,8 +447,6 @@ def main():
+     except Exception:
+         pass
+ 
+-    createPidFile(logger)
+-
+     logger.debug("Virt-who is running in %s mode" % options.virtType)
+ 
+     if options.oneshot:
diff --git a/virt-who-0.8-fix-adding-https-to-esx-url.patch b/virt-who-0.8-fix-adding-https-to-esx-url.patch
new file mode 100644
index 0000000..87dd2ff
--- /dev/null
+++ b/virt-who-0.8-fix-adding-https-to-esx-url.patch
@@ -0,0 +1,22 @@
+commit dbef0c2b5f54118677b73a08edb963e185631ea1
+Author: Radek Novacek <rnovacek at redhat.com>
+Date:   Thu Oct 25 11:14:14 2012 +0200
+
+    Fix adding https:// to ESX url
+
+diff --git a/vsphere.py b/vsphere.py
+index 3dffca9..46b5fb3 100644
+--- a/vsphere.py
++++ b/vsphere.py
+@@ -114,9 +114,9 @@ class VSphere:
+             self.url = "https://%s" % self.url
+ 
+         # Connect to the vCenter server
+-        self.client = suds.client.Client("%s/sdk/vimService.wsdl" % url)
++        self.client = suds.client.Client("%s/sdk/vimService.wsdl" % self.url)
+ 
+-        self.client.set_options(location="%s/sdk" % url)
++        self.client.set_options(location="%s/sdk" % self.url)
+ 
+         # Get Meta Object Reference to ServiceInstance which is the root object of the inventory
+         self.moRef = suds.sudsobject.Property('ServiceInstance')
diff --git a/virt-who-0.8-help-and-manpage-improvements.patch b/virt-who-0.8-help-and-manpage-improvements.patch
new file mode 100644
index 0000000..1d55b95
--- /dev/null
+++ b/virt-who-0.8-help-and-manpage-improvements.patch
@@ -0,0 +1,34 @@
+commit 690929f3e2d5169ad143d225cda24880722eae82
+Author: Radek Novacek <rnovacek at redhat.com>
+Date:   Wed Oct 24 10:13:41 2012 +0200
+
+    Help and manpage improvements
+
+diff --git a/virt-who.8 b/virt-who.8
+index 4b2c283..715cce1 100644
+--- a/virt-who.8
++++ b/virt-who.8
+@@ -150,7 +150,7 @@ Use ESX (vCenter) as virtualization backend and specify option required to conne
+ Use RHEV-M as virtualization backend and specify option required to connect to RHEV-M server.
+ 
+ .TP
+-4. Hyper-V
++5. Hyper-V
+ 
+ # virt-who --hyperv --hyperv-owner=HYPERV_OWNER  --hyperv-env=HYPERV_ENV --hyperv-server=HYPERV_SERVER --hyperv-username=HYPERV_USERNAME --hyperv-password=HYPERV_PASSWORD
+ 
+diff --git a/virt-who.py b/virt-who.py
+index 85d998a..52ec318 100644
+--- a/virt-who.py
++++ b/virt-who.py
+@@ -341,8 +341,8 @@ def main():
+     rhevmGroup = OptionGroup(parser, "RHEV-M options", "Use this options with --rhevm")
+     rhevmGroup.add_option("--rhevm-owner", action="store", dest="owner", default="", help="Organization who has purchased subscriptions of the products")
+     rhevmGroup.add_option("--rhevm-env", action="store", dest="env", default="", help="Environment where the RHEV-M belongs to")
+-    rhevmGroup.add_option("--rhevm-server", action="store", dest="server", default="", help="URL of the RHEV-M server to connect to")
+-    rhevmGroup.add_option("--rhevm-username", action="store", dest="username", default="", help="Username for connecting to RHEV-M")
++    rhevmGroup.add_option("--rhevm-server", action="store", dest="server", default="", help="URL of the RHEV-M server to connect to (preferable use secure connection - https://<ip or domain name>:<secure port, usually 8443>)")
++    rhevmGroup.add_option("--rhevm-username", action="store", dest="username", default="", help="Username for connecting to RHEV-M in the format username at domain")
+     rhevmGroup.add_option("--rhevm-password", action="store", dest="password", default="", help="Password for connecting to RHEV-M")
+     parser.add_option_group(rhevmGroup)
+ 
diff --git a/virt-who-0.8-journal-logging.patch b/virt-who-0.8-journal-logging.patch
new file mode 100644
index 0000000..0bb6038
--- /dev/null
+++ b/virt-who-0.8-journal-logging.patch
@@ -0,0 +1,76 @@
+commit af7d47978d6e141ca01e925fce02aa73a14baf69
+Author: Radek Novacek <rnovacek at redhat.com>
+Date:   Tue Nov 27 12:35:33 2012 +0100
+
+    Use journal for logging when available
+
+diff --git a/log.py b/log.py
+index e415001..bc85544 100644
+--- a/log.py
++++ b/log.py
+@@ -24,6 +24,40 @@ import logging.handlers
+ import os
+ import sys
+ 
++journalEnabled = False
++try:
++    from systemd import journal
++    journalEnabled = True
++except ImportError:
++    pass
++
++class NoExceptionFormatter(logging.Formatter):
++    def format(self, record):
++        if record.exc_info is not None:
++            record.exc_text = "\t" + str(record.exc_info[1])
++        return logging.Formatter.format(self, record)
++
++class JournalHandler(logging.Handler):
++    def __init__(self, level=logging.NOTSET):
++        logging.Handler.__init__(self, level)
++
++    def emit(self, record):
++        if journalEnabled:
++            try:
++                msg = self.format(record)
++                args = ['MESSAGE=' + record.message,
++                        'LOGGER=' + record.name,
++                        'THREAD_NAME=' + record.threadName,
++                        'CODE_FILE=' + record.pathname,
++                        'CODE_LINE=%d' % record.lineno,
++                        'CODE_FUNC=' + record.funcName]
++
++                journal.sendv(*args)
++            except (KeyboardInterrupt, SystemExit):
++                raise
++            except:
++                self.handleError(record)
++
+ def getLogger(debug, background):
+     logger = logging.getLogger("rhsm-app")
+     logger.setLevel(logging.DEBUG)
+@@ -56,10 +90,21 @@ def getLogger(debug, background):
+             streamHandler.setLevel(logging.WARNING)
+ 
+             # Don't print exceptions to stdout in non-debug mode
+-            f = logging.Filter()
+-            f.filter = lambda record: record.exc_info is None
+-            streamHandler.addFilter(f)
++            streamHandler.setFormatter(NoExceptionFormatter())
++
+ 
+         logger.addHandler(streamHandler)
+ 
++    if os.getppid() == 1:
++        # Also log to journal if available
++        journalHandler = JournalHandler()
++        if debug:
++            journalHandler.setLevel(logging.DEBUG)
++        else:
++            journalHandler.setLevel(logging.WARNING)
++
++            # Don't print exceptions to journal in non-debug mode
++            journalHandler.setFormatter(NoExceptionFormatter())
++
++        logger.addHandler(journalHandler)
+     return logger
diff --git a/virt-who-0.8-systemd.patch b/virt-who-0.8-systemd.patch
new file mode 100644
index 0000000..0b28566
--- /dev/null
+++ b/virt-who-0.8-systemd.patch
@@ -0,0 +1,46 @@
+commit 6ee26970e80b2b9705e970ec3547ca3c97e8e52f
+Author: Radek Novacek <rnovacek at redhat.com>
+Date:   Tue Nov 27 09:40:50 2012 +0100
+
+    Add .service file for systemd
+
+diff --git a/Makefile b/Makefile
+index 6dee540..f7d066a 100644
+--- a/Makefile
++++ b/Makefile
+@@ -13,13 +13,13 @@ check:
+ 	pyflakes *.py
+ 
+ install:
+-	install -d $(DESTDIR)/usr/share/$(name)/ $(DESTDIR)/usr/bin $(DESTDIR)/etc/rc.d/init.d $(DESTDIR)/etc/sysconfig $(DESTDIR)/usr/share/man/man8/
++	install -d $(DESTDIR)/usr/share/$(name)/ $(DESTDIR)/usr/bin $(DESTDIR)/usr/lib/systemd/system $(DESTDIR)/etc/sysconfig $(DESTDIR)/usr/share/man/man8/
+ 	install -pm 0644 *.py $(DESTDIR)/usr/share/$(name)/
+ 	install virt-who $(DESTDIR)/usr/bin/
+-	install virt-who-initscript $(DESTDIR)/etc/rc.d/init.d/virt-who
++	install -pm 0644 virt-who.service $(DESTDIR)/usr/lib/systemd/system/
+ 	install -pm 0644 virt-who.conf $(DESTDIR)/etc/sysconfig/virt-who
+ 	gzip -c virt-who.8 > virt-who.8.gz
+-	install virt-who.8.gz $(DESTDIR)/usr/share/man/man8/
++	install -pm 0644 virt-who.8.gz $(DESTDIR)/usr/share/man/man8/
+ 
+ srpm: pack
+ 	rpmbuild --define "_sourcedir $(PWD)" --define "_specdir $(PWD)" --define "_srcrpmdir $(PWD)" -bs $(name).spec
+diff --git a/virt-who.service b/virt-who.service
+new file mode 100644
+index 0000000..74982c5
+--- /dev/null
++++ b/virt-who.service
+@@ -0,0 +1,13 @@
++[Unit]
++Description=Daemon for reporting virtual guest IDs to subscription-manager
++After=syslog.target libvirtd.service network.target
++
++[Service]
++Type=forking
++PIDFile=/var/run/virt-who.pid
++ExecStart=/usr/bin/virt-who
++EnvironmentFile=-/etc/sysconfig/virt-who
++
++[Install]
++WantedBy=multi-user.target
++
diff --git a/virt-who.spec b/virt-who.spec
index 1b9d0a2..cdad967 100644
--- a/virt-who.spec
+++ b/virt-who.spec
@@ -1,19 +1,42 @@
 Name:           virt-who
-Version:        0.2
-Release:        3%{?dist}
+Version:        0.8
+Release:        6%{?dist}
 Summary:        Agent for reporting virtual guest IDs to subscription-manager
 
-Group:          System Environment/Base
 License:        GPLv2+
 URL:            https://fedorahosted.org/virt-who/
 Source0:        https://fedorahosted.org/releases/v/i/virt-who/%{name}-%{version}.tar.gz
-BuildRoot:      %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
 
 BuildArch:      noarch
 BuildRequires:  python2-devel
 Requires:       libvirt-python
 Requires:       libvirt
-Requires:       python-rhsm
+# python-rhsm 1.0.4 contains new module for reading certificates
+Requires:       python-rhsm >= 1.0.4
+# python-suds is required for vSphere support
+Requires:       python-suds
+# m2crypto is required for Hyper-V support
+Requires:       m2crypto
+# for journal logging
+Requires:       systemd-python
+# systemd stuff
+BuildRequires:  systemd
+Requires(post): systemd
+Requires(preun): systemd
+Requires(postun): systemd
+
+# Add Hyper-V support
+Patch0:         virt-who-0.8-add-hyperv-support.patch
+# Create PID file ASAP to prevent service stop fails
+Patch1:         virt-who-0.8-create-pid-file-asap.patch
+# Help and manpage improvements
+Patch2:         virt-who-0.8-help-and-manpage-improvements.patch
+# Fix adding https:// to ESX url
+Patch3:         virt-who-0.8-fix-adding-https-to-esx-url.patch
+# systemd support
+Patch4:         virt-who-0.8-systemd.patch
+# logging using journal
+Patch5:         virt-who-0.8-journal-logging.patch
 
 %description
 Agent that collects information about virtual guests present in the system and
@@ -21,35 +44,97 @@ report them to the subscription manager.
 
 %prep
 %setup -q
-
+%patch0 -p1
+%patch1 -p1
+%patch2 -p1
+%patch3 -p1
+%patch4 -p1
+%patch5 -p1
 
 %build
 
 
 %install
-rm -rf $RPM_BUILD_ROOT
-
 make DESTDIR=$RPM_BUILD_ROOT install
 
 # Don't run test suite in check section, because it need the system to be
 # registered to subscription-manager server
 
-%clean
-rm -rf $RPM_BUILD_ROOT
 
+%post
+%systemd_post virt-who.service
+
+%preun
+%systemd_preun virt-who.service
+
+%postun
+%systemd_postun_with_restart virt-who.service 
 
 %files
-%doc README LICENSE
+%doc README README.hyperv LICENSE
 %{_bindir}/virt-who
 %{_datadir}/virt-who/
+%{_unitdir}/virt-who.service
+%config(noreplace) %{_sysconfdir}/sysconfig/virt-who
+%{_mandir}/man8/virt-who.8.gz
 
 
 %changelog
-* Sun Jul 22 2012 Fedora Release Engineering <rel-eng at lists.fedoraproject.org> - 0.2-3
-- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild
+* Tue Nov 27 2012 Radek Novacek <rnovacek at redhat.com> 0.8-6
+- Add systemd support
+- specfile cleanup
+
+* Thu Oct 25 2012 Radek Novacek <rnovacek at redhat.com> 0.8-5
+- Fix adding https:// to ESX url
+
+* Wed Oct 24 2012 Radek Novacek <rnovacek at redhat.com> 0.8-4
+- Help and manpage improvements
+
+* Wed Oct 17 2012 Radek Novacek <rnovacek at redhat.com> 0.8-3
+- Fix bugs in Hyper-V support (patch rebased)
+- Create PID file ASAP to prevent service stop fails
+
+* Thu Oct 11 2012 Radek Novacek <rnovacek at redhat.com> 0.8-2
+- Add support for accessing Hyper-V
+
+* Wed Sep 26 2012 Radek Novacek <rnovacek at redhat.com> 0.8-1
+- Upstream version 0.8
+- RFE: command line improvements
+- Add support for accessing RHEV-M
+- Fix printing tracebacks on terminal
+
+* Thu Apr 26 2012 Radek Novacek <rnovacek at redhat.com> 0.6-6
+- Handle unknown libvirt event properly
+
+* Wed Apr 18 2012 Radek Novacek <rnovacek at redhat.com> 0.6-5
+- Enable debug output to be written to stderr
+- Log guest list to log even in non-debug mode
+
+* Tue Apr 17 2012 Radek Novacek <rnovacek at redhat.com> 0.6-4
+- Fix regression in double fork patch
+
+* Wed Mar 28 2012 Radek Novacek <rnovacek at redhat.com> 0.6-3
+- Do double fork when daemon is starting
+
+* Fri Mar 09 2012 Radek Novacek <rnovacek at redhat.com> 0.6-2
+- Add python-suds require
+- Requires python-rhsm >= 0.98.6
+
+* Thu Mar 01 2012 Radek Novacek <rnovacek at redhat.com> 0.6-1
+- Rebase to virt-who-0.6
+
+* Wed Oct 12 2011 Radek Novacek <rnovacek at redhat.com> 0.3-3
+- Use updateConsumer API instead of updateConsumerFact (fixes limit 255 chars of uuid list)
+- Requires python-rhsm >= 0.96.13 
+
+* Wed Sep 07 2011 Radek Novacek <rnovacek at redhat.com> - 0.3-2
+- Add upstream patch that prevents failure when server not implements /status/ command
+
+* Thu Sep 01 2011 Radek Novacek <rnovacek at redhat.com> - 0.3-1
+- Add initscript and configuration file
 
-* Sat Jan 14 2012 Fedora Release Engineering <rel-eng at lists.fedoraproject.org> - 0.2-2
-- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild
+* Mon Aug 22 2011 Radek Novacek <rnovacek at redhat.com> - 0.2-2
+- Bump release because of tagging in wrong branch
 
 * Mon Aug 22 2011 Radek Novacek <rnovacek at redhat.com> - 0.2-1
 - Update to upstream version 0.2


More information about the scm-commits mailing list