~hudson-openstack/nova/trunk

« back to all changes in this revision

Viewing changes to tools/esx/guest_tool.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
 
 
3
 
# Copyright (c) 2011 Citrix Systems, Inc.
4
 
# Copyright 2011 OpenStack LLC.
5
 
#
6
 
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
7
 
#    not use this file except in compliance with the License. You may obtain
8
 
#    a copy of the License at
9
 
#
10
 
#         http://www.apache.org/licenses/LICENSE-2.0
11
 
#
12
 
#    Unless required by applicable law or agreed to in writing, software
13
 
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
 
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
 
#    License for the specific language governing permissions and limitations
16
 
#    under the License.
17
 
 
18
 
"""
19
 
Guest tools for ESX to set up network in the guest.
20
 
On Windows we require pyWin32 installed on Python.
21
 
"""
22
 
 
23
 
import array
24
 
import gettext
25
 
import logging
26
 
import os
27
 
import platform
28
 
import socket
29
 
import struct
30
 
import subprocess
31
 
import sys
32
 
import time
33
 
 
34
 
gettext.install('nova', unicode=1)
35
 
 
36
 
PLATFORM_WIN = 'win32'
37
 
PLATFORM_LINUX = 'linux2'
38
 
ARCH_32_BIT = '32bit'
39
 
ARCH_64_BIT = '64bit'
40
 
NO_MACHINE_ID = 'No machine id'
41
 
 
42
 
# Logging
43
 
FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
44
 
if sys.platform == PLATFORM_WIN:
45
 
    LOG_DIR = os.path.join(os.environ.get('ALLUSERSPROFILE'), 'openstack')
46
 
elif sys.platform == PLATFORM_LINUX:
47
 
    LOG_DIR = '/var/log/openstack'
48
 
else:
49
 
    LOG_DIR = 'logs'
50
 
if not os.path.exists(LOG_DIR):
51
 
    os.mkdir(LOG_DIR)
52
 
LOG_FILENAME = os.path.join(LOG_DIR, 'openstack-guest-tools.log')
53
 
logging.basicConfig(filename=LOG_FILENAME, format=FORMAT)
54
 
 
55
 
if sys.hexversion < 0x3000000:
56
 
    _byte = ord    # 2.x chr to integer
57
 
else:
58
 
    _byte = int    # 3.x byte to integer
59
 
 
60
 
 
61
 
class ProcessExecutionError:
62
 
    """Process Execution Error Class."""
63
 
 
64
 
    def __init__(self, exit_code, stdout, stderr, cmd):
65
 
        self.exit_code = exit_code
66
 
        self.stdout = stdout
67
 
        self.stderr = stderr
68
 
        self.cmd = cmd
69
 
 
70
 
    def __str__(self):
71
 
        return str(self.exit_code)
72
 
 
73
 
 
74
 
def _bytes2int(bytes):
75
 
    """Convert bytes to int."""
76
 
    intgr = 0
77
 
    for byt in bytes:
78
 
        intgr = (intgr << 8) + _byte(byt)
79
 
    return intgr
80
 
 
81
 
 
82
 
def _parse_network_details(machine_id):
83
 
    """
84
 
    Parse the machine_id to get MAC, IP, Netmask and Gateway fields per NIC.
85
 
    machine_id is of the form ('NIC_record#NIC_record#', '')
86
 
    Each of the NIC will have record NIC_record in the form
87
 
    'MAC;IP;Netmask;Gateway;Broadcast;DNS' where ';' is field separator.
88
 
    Each record is separated by '#' from next record.
89
 
    """
90
 
    logging.debug(_("Received machine_id from vmtools : %s") % machine_id[0])
91
 
    network_details = []
92
 
    if machine_id[1].strip() == "1":
93
 
        pass
94
 
    else:
95
 
        for machine_id_str in machine_id[0].split('#'):
96
 
            network_info_list = machine_id_str.split(';')
97
 
            if len(network_info_list) % 6 != 0:
98
 
                break
99
 
            no_grps = len(network_info_list) / 6
100
 
            i = 0
101
 
            while i < no_grps:
102
 
                k = i * 6
103
 
                network_details.append((
104
 
                                network_info_list[k].strip().lower(),
105
 
                                network_info_list[k + 1].strip(),
106
 
                                network_info_list[k + 2].strip(),
107
 
                                network_info_list[k + 3].strip(),
108
 
                                network_info_list[k + 4].strip(),
109
 
                                network_info_list[k + 5].strip().split(',')))
110
 
                i += 1
111
 
    logging.debug(_("NIC information from vmtools : %s") % network_details)
112
 
    return network_details
113
 
 
114
 
 
115
 
def _get_windows_network_adapters():
116
 
    """Get the list of windows network adapters."""
117
 
    import win32com.client
118
 
    wbem_locator = win32com.client.Dispatch('WbemScripting.SWbemLocator')
119
 
    wbem_service = wbem_locator.ConnectServer('.', 'root\cimv2')
120
 
    wbem_network_adapters = wbem_service.InstancesOf('Win32_NetworkAdapter')
121
 
    network_adapters = []
122
 
    for wbem_network_adapter in wbem_network_adapters:
123
 
        if wbem_network_adapter.NetConnectionStatus == 2 or \
124
 
                                wbem_network_adapter.NetConnectionStatus == 7:
125
 
            adapter_name = wbem_network_adapter.NetConnectionID
126
 
            mac_address = wbem_network_adapter.MacAddress.lower()
127
 
            wbem_network_adapter_config = \
128
 
                wbem_network_adapter.associators_(
129
 
                          'Win32_NetworkAdapterSetting',
130
 
                          'Win32_NetworkAdapterConfiguration')[0]
131
 
            ip_address = ''
132
 
            subnet_mask = ''
133
 
            if wbem_network_adapter_config.IPEnabled:
134
 
                ip_address = wbem_network_adapter_config.IPAddress[0]
135
 
                subnet_mask = wbem_network_adapter_config.IPSubnet[0]
136
 
                #wbem_network_adapter_config.DefaultIPGateway[0]
137
 
            network_adapters.append({'name': adapter_name,
138
 
                                    'mac-address': mac_address,
139
 
                                    'ip-address': ip_address,
140
 
                                    'subnet-mask': subnet_mask})
141
 
    return network_adapters
142
 
 
143
 
 
144
 
def _get_linux_network_adapters():
145
 
    """Get the list of Linux network adapters."""
146
 
    import fcntl
147
 
    max_bytes = 8096
148
 
    arch = platform.architecture()[0]
149
 
    if arch == ARCH_32_BIT:
150
 
        offset1 = 32
151
 
        offset2 = 32
152
 
    elif arch == ARCH_64_BIT:
153
 
        offset1 = 16
154
 
        offset2 = 40
155
 
    else:
156
 
        raise OSError(_("Unknown architecture: %s") % arch)
157
 
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
158
 
    names = array.array('B', '\0' * max_bytes)
159
 
    outbytes = struct.unpack('iL', fcntl.ioctl(
160
 
        sock.fileno(),
161
 
        0x8912,
162
 
        struct.pack('iL', max_bytes, names.buffer_info()[0])))[0]
163
 
    adapter_names = \
164
 
        [names.tostring()[n_counter:n_counter + offset1].split('\0', 1)[0]
165
 
        for n_counter in xrange(0, outbytes, offset2)]
166
 
    network_adapters = []
167
 
    for adapter_name in adapter_names:
168
 
        ip_address = socket.inet_ntoa(fcntl.ioctl(
169
 
            sock.fileno(),
170
 
            0x8915,
171
 
            struct.pack('256s', adapter_name))[20:24])
172
 
        subnet_mask = socket.inet_ntoa(fcntl.ioctl(
173
 
            sock.fileno(),
174
 
            0x891b,
175
 
            struct.pack('256s', adapter_name))[20:24])
176
 
        raw_mac_address = '%012x' % _bytes2int(fcntl.ioctl(
177
 
            sock.fileno(),
178
 
            0x8927,
179
 
            struct.pack('256s', adapter_name))[18:24])
180
 
        mac_address = ":".join([raw_mac_address[m_counter:m_counter + 2]
181
 
            for m_counter in range(0, len(raw_mac_address), 2)]).lower()
182
 
        network_adapters.append({'name': adapter_name,
183
 
                                 'mac-address': mac_address,
184
 
                                 'ip-address': ip_address,
185
 
                                 'subnet-mask': subnet_mask})
186
 
    return network_adapters
187
 
 
188
 
 
189
 
def _get_adapter_name_and_ip_address(network_adapters, mac_address):
190
 
    """Get the adapter name based on the MAC address."""
191
 
    adapter_name = None
192
 
    ip_address = None
193
 
    for network_adapter in network_adapters:
194
 
        if network_adapter['mac-address'] == mac_address.lower():
195
 
            adapter_name = network_adapter['name']
196
 
            ip_address = network_adapter['ip-address']
197
 
            break
198
 
    return adapter_name, ip_address
199
 
 
200
 
 
201
 
def _get_win_adapter_name_and_ip_address(mac_address):
202
 
    """Get Windows network adapter name."""
203
 
    network_adapters = _get_windows_network_adapters()
204
 
    return _get_adapter_name_and_ip_address(network_adapters, mac_address)
205
 
 
206
 
 
207
 
def _get_linux_adapter_name_and_ip_address(mac_address):
208
 
    """Get Linux network adapter name."""
209
 
    network_adapters = _get_linux_network_adapters()
210
 
    return _get_adapter_name_and_ip_address(network_adapters, mac_address)
211
 
 
212
 
 
213
 
def _execute(cmd_list, process_input=None, check_exit_code=True):
214
 
    """Executes the command with the list of arguments specified."""
215
 
    cmd = ' '.join(cmd_list)
216
 
    logging.debug(_("Executing command: '%s'") % cmd)
217
 
    env = os.environ.copy()
218
 
    obj = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
219
 
        stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
220
 
    result = None
221
 
    if process_input is not None:
222
 
        result = obj.communicate(process_input)
223
 
    else:
224
 
        result = obj.communicate()
225
 
    obj.stdin.close()
226
 
    if obj.returncode:
227
 
        logging.debug(_("Result was %s") % obj.returncode)
228
 
        if check_exit_code and obj.returncode != 0:
229
 
            (stdout, stderr) = result
230
 
            raise ProcessExecutionError(exit_code=obj.returncode,
231
 
                                        stdout=stdout,
232
 
                                        stderr=stderr,
233
 
                                        cmd=cmd)
234
 
    time.sleep(0.1)
235
 
    return result
236
 
 
237
 
 
238
 
def _windows_set_networking():
239
 
    """Set IP address for the windows VM."""
240
 
    program_files = os.environ.get('PROGRAMFILES')
241
 
    program_files_x86 = os.environ.get('PROGRAMFILES(X86)')
242
 
    vmware_tools_bin = None
243
 
    if os.path.exists(os.path.join(program_files, 'VMware', 'VMware Tools',
244
 
                                   'vmtoolsd.exe')):
245
 
        vmware_tools_bin = os.path.join(program_files, 'VMware',
246
 
                                    'VMware Tools', 'vmtoolsd.exe')
247
 
    elif os.path.exists(os.path.join(program_files, 'VMware', 'VMware Tools',
248
 
                                     'VMwareService.exe')):
249
 
        vmware_tools_bin = os.path.join(program_files, 'VMware',
250
 
                                      'VMware Tools', 'VMwareService.exe')
251
 
    elif program_files_x86 and os.path.exists(os.path.join(program_files_x86,
252
 
                                       'VMware', 'VMware Tools',
253
 
                                       'VMwareService.exe')):
254
 
        vmware_tools_bin = os.path.join(program_files_x86, 'VMware',
255
 
                                        'VMware Tools', 'VMwareService.exe')
256
 
    if vmware_tools_bin:
257
 
        cmd = ['"' + vmware_tools_bin + '"', '--cmd', 'machine.id.get']
258
 
        for network_detail in _parse_network_details(_execute(cmd,
259
 
                                              check_exit_code=False)):
260
 
            mac_address, ip_address, subnet_mask, gateway, broadcast,\
261
 
                dns_servers = network_detail
262
 
            adapter_name, current_ip_address = \
263
 
                    _get_win_adapter_name_and_ip_address(mac_address)
264
 
            if adapter_name and not ip_address == current_ip_address:
265
 
                cmd = ['netsh', 'interface', 'ip', 'set', 'address',
266
 
                       'name="%s"' % adapter_name, 'source=static', ip_address,
267
 
                       subnet_mask, gateway, '1']
268
 
                _execute(cmd)
269
 
                # Windows doesn't let you manually set the broadcast address
270
 
                for dns_server in dns_servers:
271
 
                    if dns_server:
272
 
                        cmd = ['netsh', 'interface', 'ip', 'add', 'dns',
273
 
                               'name="%s"' % adapter_name, dns_server]
274
 
                        _execute(cmd)
275
 
    else:
276
 
        logging.warn(_("VMware Tools is not installed"))
277
 
 
278
 
 
279
 
def _filter_duplicates(all_entries):
280
 
    final_list = []
281
 
    for entry in all_entries:
282
 
        if entry and entry not in final_list:
283
 
            final_list.append(entry)
284
 
    return final_list
285
 
 
286
 
 
287
 
def _set_rhel_networking(network_details=None):
288
 
    """Set IPv4 network settings for RHEL distros."""
289
 
    network_details = network_details or []
290
 
    all_dns_servers = []
291
 
    for network_detail in network_details:
292
 
        mac_address, ip_address, subnet_mask, gateway, broadcast,\
293
 
            dns_servers = network_detail
294
 
        all_dns_servers.extend(dns_servers)
295
 
        adapter_name, current_ip_address = \
296
 
                _get_linux_adapter_name_and_ip_address(mac_address)
297
 
        if adapter_name and not ip_address == current_ip_address:
298
 
            interface_file_name = \
299
 
                '/etc/sysconfig/network-scripts/ifcfg-%s' % adapter_name
300
 
            # Remove file
301
 
            os.remove(interface_file_name)
302
 
            # Touch file
303
 
            _execute(['touch', interface_file_name])
304
 
            interface_file = open(interface_file_name, 'w')
305
 
            interface_file.write('\nDEVICE=%s' % adapter_name)
306
 
            interface_file.write('\nUSERCTL=yes')
307
 
            interface_file.write('\nONBOOT=yes')
308
 
            interface_file.write('\nBOOTPROTO=static')
309
 
            interface_file.write('\nBROADCAST=%s' % broadcast)
310
 
            interface_file.write('\nNETWORK=')
311
 
            interface_file.write('\nGATEWAY=%s' % gateway)
312
 
            interface_file.write('\nNETMASK=%s' % subnet_mask)
313
 
            interface_file.write('\nIPADDR=%s' % ip_address)
314
 
            interface_file.write('\nMACADDR=%s' % mac_address)
315
 
            interface_file.close()
316
 
    if all_dns_servers:
317
 
        dns_file_name = "/etc/resolv.conf"
318
 
        os.remove(dns_file_name)
319
 
        _execute(['touch', dns_file_name])
320
 
        dns_file = open(dns_file_name, 'w')
321
 
        dns_file.write("; generated by OpenStack guest tools")
322
 
        unique_entries = _filter_duplicates(all_dns_servers)
323
 
        for dns_server in unique_entries:
324
 
            dns_file.write("\nnameserver %s" % dns_server)
325
 
        dns_file.close()
326
 
    _execute(['/sbin/service', 'network', 'restart'])
327
 
 
328
 
 
329
 
def _set_ubuntu_networking(network_details=None):
330
 
    """Set IPv4 network settings for Ubuntu."""
331
 
    network_details = network_details or []
332
 
    all_dns_servers = []
333
 
    interface_file_name = '/etc/network/interfaces'
334
 
    # Remove file
335
 
    os.remove(interface_file_name)
336
 
    # Touch file
337
 
    _execute(['touch', interface_file_name])
338
 
    interface_file = open(interface_file_name, 'w')
339
 
    for device, network_detail in enumerate(network_details):
340
 
        mac_address, ip_address, subnet_mask, gateway, broadcast,\
341
 
            dns_servers = network_detail
342
 
        all_dns_servers.extend(dns_servers)
343
 
        adapter_name, current_ip_address = \
344
 
                _get_linux_adapter_name_and_ip_address(mac_address)
345
 
 
346
 
        if adapter_name:
347
 
            interface_file.write('\nauto %s' % adapter_name)
348
 
            interface_file.write('\niface %s inet static' % adapter_name)
349
 
            interface_file.write('\nbroadcast %s' % broadcast)
350
 
            interface_file.write('\ngateway %s' % gateway)
351
 
            interface_file.write('\nnetmask %s' % subnet_mask)
352
 
            interface_file.write('\naddress %s\n' % ip_address)
353
 
        logging.debug(_("Successfully configured NIC %d with "
354
 
                        "NIC info %s") % (device, network_detail))
355
 
    interface_file.close()
356
 
 
357
 
    if all_dns_servers:
358
 
        dns_file_name = "/etc/resolv.conf"
359
 
        os.remove(dns_file_name)
360
 
        _execute(['touch', dns_file_name])
361
 
        dns_file = open(dns_file_name, 'w')
362
 
        dns_file.write("; generated by OpenStack guest tools")
363
 
        unique_entries = _filter_duplicates(all_dns_servers)
364
 
        for dns_server in unique_entries:
365
 
            dns_file.write("\nnameserver %s" % dns_server)
366
 
        dns_file.close()
367
 
 
368
 
    logging.debug(_("Restarting networking....\n"))
369
 
    _execute(['/etc/init.d/networking', 'restart'])
370
 
 
371
 
 
372
 
def _linux_set_networking():
373
 
    """Set IP address for the Linux VM."""
374
 
    vmware_tools_bin = None
375
 
    if os.path.exists('/usr/sbin/vmtoolsd'):
376
 
        vmware_tools_bin = '/usr/sbin/vmtoolsd'
377
 
    elif os.path.exists('/usr/bin/vmtoolsd'):
378
 
        vmware_tools_bin = '/usr/bin/vmtoolsd'
379
 
    elif os.path.exists('/usr/sbin/vmware-guestd'):
380
 
        vmware_tools_bin = '/usr/sbin/vmware-guestd'
381
 
    elif os.path.exists('/usr/bin/vmware-guestd'):
382
 
        vmware_tools_bin = '/usr/bin/vmware-guestd'
383
 
    if vmware_tools_bin:
384
 
        cmd = [vmware_tools_bin, '--cmd', 'machine.id.get']
385
 
        network_details = _parse_network_details(_execute(cmd,
386
 
                                                check_exit_code=False))
387
 
        # TODO(sateesh): For other distros like suse, debian, BSD, etc.
388
 
        if(platform.dist()[0] == 'Ubuntu'):
389
 
            _set_ubuntu_networking(network_details)
390
 
        elif (platform.dist()[0] == 'redhat'):
391
 
            _set_rhel_networking(network_details)
392
 
        else:
393
 
            logging.warn(_("Distro '%s' not supported") % platform.dist()[0])
394
 
    else:
395
 
        logging.warn(_("VMware Tools is not installed"))
396
 
 
397
 
if __name__ == '__main__':
398
 
    pltfrm = sys.platform
399
 
    if pltfrm == PLATFORM_WIN:
400
 
        _windows_set_networking()
401
 
    elif pltfrm == PLATFORM_LINUX:
402
 
        _linux_set_networking()
403
 
    else:
404
 
        raise NotImplementedError(_("Platform not implemented: '%s'") % pltfrm)