Package virtualization :: Module poller
[hide private]
[frames] | no frames]

Source Code for Module virtualization.poller

  1  # 
  2  # Copyright (c) 2008--2014 Red Hat, Inc. 
  3  # 
  4  # This software is licensed to you under the GNU General Public License, 
  5  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  6  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  8  # along with this software; if not, see 
  9  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
 10  # 
 11  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 12  # granted to use or replicate Red Hat trademarks that are incorporated 
 13  # in this software or its documentation. 
 14  # 
 15   
 16  import os 
 17  import subprocess 
 18  import sys 
 19   
 20  import binascii 
 21  import traceback 
 22  import gettext 
 23  t = gettext.translation('rhn-virtualization', fallback=True) 
 24  # Python 3 translations don't have a ugettext method 
 25  if not hasattr(t, 'ugettext'): 
 26      t.ugettext = t.gettext 
 27  _ = t.ugettext 
 28   
 29  from up2date_client import rhncli 
 30   
 31  try: 
 32      import libvirt 
 33  except ImportError: 
 34      # There might not be a libvirt. 
 35      libvirt = None 
 36   
 37  from optparse import OptionParser 
 38   
 39  from virtualization.state              import State 
 40  from virtualization.errors             import VirtualizationException 
 41  from virtualization.constants          import PropertyType,        \ 
 42                                                VirtualizationType,  \ 
 43                                                IdentityType,        \ 
 44                                                VIRT_STATE_NAME_MAP, \ 
 45                                                VIRT_VDSM_STATUS_MAP 
 46  from virtualization.notification       import Plan,                \ 
 47                                                EventType,           \ 
 48                                                TargetType 
 49  from virtualization.util               import hyphenize_uuid,      \ 
 50                                                is_fully_virt 
 51  from virtualization.poller_state_cache import PollerStateCache 
 52   
 53  from virtualization.domain_directory   import DomainDirectory 
 54   
 55  from spacewalk.common.usix import raise_with_tb 
 56  ############################################################################### 
 57  # Globals 
 58  ############################################################################### 
 59   
 60  options = None 
 61   
 62  ############################################################################### 
 63  # Public Interface 
 64  ############################################################################### 
 65   
66 -def poll_hypervisor():
67 """ 68 This function polls the hypervisor for information about the currently 69 running set of domains. It returns a dictionary object that looks like the 70 following: 71 72 { uuid : { 'name' : '...', 73 'uuid' : '...', 74 'virt_type' : '...', 75 'memory_size' : '...', 76 'vcpus' : '...', 77 'state' : '...' }, ... } 78 """ 79 if not libvirt: 80 return {} 81 82 try: 83 conn = libvirt.openReadOnly(None) 84 except libvirt.libvirtError: 85 # virConnectOpen() failed 86 sys.stderr.write(rhncli.utf8_encode(_("Warning: Could not retrieve virtualization information!\n\t" + 87 "libvirtd service needs to be running.\n"))) 88 conn = None 89 90 if not conn: 91 # No connection to hypervisor made 92 return {} 93 94 domainIDs = conn.listDomainsID() 95 96 state = {} 97 98 for domainID in domainIDs: 99 try: 100 domain = conn.lookupByID(domainID) 101 except libvirt.libvirtError: 102 lve = sys.exc_info()[1] 103 raise_with_tb(VirtualizationException("Failed to obtain handle to domain %d: %s" % (domainID, repr(lve)), 104 sys.exc_info()[2])) 105 106 uuid = binascii.hexlify(domain.UUID()) 107 # SEE: http://libvirt.org/html/libvirt-libvirt.html#virDomainInfo 108 # for more info. 109 domain_info = domain.info() 110 111 # Set the virtualization type. We can tell if the domain is fully virt 112 # by checking the domain's OSType() attribute. 113 virt_type = VirtualizationType.PARA 114 if is_fully_virt(domain): 115 virt_type = VirtualizationType.FULLY 116 117 # we need to filter out the small per/minute KB changes 118 # that occur inside a vm. To do this we divide by 1024 to 119 # drop our precision down to megabytes with an int then 120 # back up to KB 121 memory = int(domain_info[2] / 1024); 122 memory = memory * 1024; 123 properties = { 124 PropertyType.NAME : domain.name(), 125 PropertyType.UUID : uuid, 126 PropertyType.TYPE : virt_type, 127 PropertyType.MEMORY : str(memory), # current memory 128 PropertyType.VCPUS : domain_info[3], 129 PropertyType.STATE : VIRT_STATE_NAME_MAP[domain_info[0]] } 130 131 state[uuid] = properties 132 133 if state: _log_debug("Polled state: %s" % repr(state)) 134 135 return state
136
137 -def poll_through_vdsm(server):
138 """ 139 This method polls all the virt guests running on a VDSM enabled Host. 140 Libvirt is disabled by default on RHEV-M managed clients. 141 * Imports the localvdsm client that talks to the localhost 142 and fetches the list of vms and their info. 143 * Extract the data and construct the state to pass it to the 144 execution plan for guest polling. 145 * The server should account for business rules similar to 146 xen/kvm. 147 """ 148 # Extract list of vm's. True returns full list 149 try: 150 domains = server.list(True) 151 except: 152 # Something went wrong in vdsm, exit 153 return {} 154 155 if not len(domains['vmList']): 156 # No domains, exit. 157 return {} 158 159 state = {} 160 for domain in domains['vmList']: 161 #trim uuid 162 uuid = domain['vmId'].lower().replace('-', '') 163 # Map the VDSM status to libvirt for server compatibility 164 status = 'nostate' 165 if domain['status'] in VIRT_VDSM_STATUS_MAP: 166 status = VIRT_VDSM_STATUS_MAP[domain['status']] 167 # This is gonna be fully virt as its managed by VDSM 168 virt_type = VirtualizationType.FULLY 169 170 #Memory 171 memory = int(domain['memSize']) * 1024 172 173 # vcpus 174 if 'smp' in domain: 175 vcpus = domain['smp'] 176 else: 177 vcpus = '1' 178 179 properties = { 180 PropertyType.NAME : domain['vmName'], 181 PropertyType.UUID : uuid, 182 PropertyType.TYPE : virt_type, 183 PropertyType.MEMORY : memory, # current memory 184 PropertyType.VCPUS : vcpus, 185 PropertyType.STATE : status} 186 187 state[uuid] = properties 188 189 if state: _log_debug("Polled state: %s" % repr(state)) 190 191 return state
192 193
194 -def poll_state(uuid):
195 """ 196 Polls just the state of the guest with the provided UUID. This state is 197 returned. 198 """ 199 conn = libvirt.openReadOnly(None) 200 if not conn: 201 raise VirtualizationException("Failed to open connection to hypervisor.") 202 203 # Attempt to connect to the domain. Since there is technically no 204 # "stopped" state, we will assume that if we cannot connect the domain is 205 # not running. Unfortunately, we can't really determine if the domain 206 # actually exists. 207 domain = None 208 try: 209 domain = conn.lookupByUUIDString(hyphenize_uuid(uuid)) 210 except libvirt.libvirtError: 211 # Can't find domain. Return stopped state. 212 return State(None) 213 214 # Now that we have the domain, lookup the state. 215 domain_info = domain.info() 216 return State(VIRT_STATE_NAME_MAP[domain_info[0]])
217 218 ############################################################################### 219 # Helper Functions 220 ############################################################################### 221
222 -def _send_notifications(poller_state):
223 """ 224 This function will send notifications based on vm state change to the 225 server. To reduce the possibility of spamming the server but still 226 maintain an element of consistency, it will compare the previous poll state 227 against the current poll state and only send notifications if something has 228 changed. In the event that the cache might have gotten into an 229 inconsistent state, the cache will be removed after every 50 polls (this is 230 about every 1.5 hours). This will cause the full state to be re-uploaded 231 and put things back in sync, if necessary. 232 """ 233 # Now, if anything changed, send the appropriate notification for it. 234 if poller_state.is_changed(): 235 added = poller_state.get_added() 236 removed = poller_state.get_removed() 237 modified = poller_state.get_modified() 238 239 plan = Plan() 240 241 # Declare virtualization host first 242 plan.add(EventType.EXISTS, 243 TargetType.SYSTEM, 244 { PropertyType.IDENTITY : IdentityType.HOST, 245 PropertyType.UUID : '0000000000000000' }) 246 247 for (uuid, data) in added.items(): 248 plan.add(EventType.EXISTS, TargetType.DOMAIN, data) 249 250 for (uuid, data) in modified.items(): 251 plan.add(EventType.EXISTS, TargetType.DOMAIN, data) 252 253 for (uuid, data) in removed.items(): 254 plan.add(EventType.REMOVED, TargetType.DOMAIN, data) 255 256 plan.execute()
257
258 -def _parse_options():
259 usage = "Usage: %prog [options]" 260 parser = OptionParser(usage) 261 parser.set_defaults(debug=False) 262 parser.add_option("-d", "--debug", action="store_true", dest="debug") 263 global options 264 (options, args) = parser.parse_args()
265
266 -def _log_debug(msg, include_trace = 0):
267 if options and options.debug: 268 print("DEBUG: " + str(msg)) 269 if include_trace: 270 e_info = sys.exc_info() 271 traceback.print_exception(e_info[0], e_info[1], e_info[2])
272 273 ############################################################################### 274 # Main Program 275 ############################################################################### 276 277 if __name__ == "__main__": 278 279 # First, handle the options. 280 _parse_options() 281 282 vdsm_enabled = False 283 server = None 284 try: 285 from virtualization import localvdsm 286 287 status = subprocess.call(['/sbin/service','vdsmd','status'], 288 stdout=open(os.devnull, 'wb'), 289 stderr=subprocess.STDOUT) 290 if status == 0: 291 server = localvdsm.connect() 292 vdsm_enabled = True 293 except ImportError: 294 pass 295 296 # Crawl each of the domains on this host and obtain the new state. 297 if vdsm_enabled: 298 domain_list = poll_through_vdsm(server) 299 elif libvirt: 300 # If libvirt is present but not running, this program is useless. 301 # Libvirt currently writes to stderr if you attempt to connect 302 # and the daemon is not running. The libvirt python bindings provide 303 # no way to change this behavior. Anything written to stderr or stdout 304 # will cause cron to email root with the output. It is not our 305 # job to nag the user every two minutes if libvirt is not running. 306 # The only way to avoid this is to shell out to check if libvirt 307 # is running, and exit if it's not. 308 # See if the libvirtd service is running, discarding all output. 309 # Non-zero exit code means it's not running. 310 if (subprocess.call(['/sbin/service','libvirtd','status'], 311 stdout=open(os.devnull, 'wb'), 312 stderr=subprocess.STDOUT) != 0): 313 sys.exit(0) 314 domain_list = poll_hypervisor() 315 else: 316 # If no libvirt nor vdsm is present, this program is pretty much 317 # useless. Just exit. 318 sys.exit(0) 319 320 # create the unkonwn domain config files (for libvirt only) 321 if libvirt and not vdsm_enabled: 322 uuid_list = list(domain_list.keys()) 323 domain = DomainDirectory() 324 domain.save_unknown_domain_configs(uuid_list) 325 326 cached_state = PollerStateCache(domain_list, 327 debug = options and options.debug) 328 329 # Send notifications, if necessary. 330 _send_notifications(cached_state) 331 332 # Save the new state. 333 cached_state.save() 334