Esempio n. 1
0
def get_authentication_headers(bot):
    """Returns authentication headers and their expiration time.

  The returned headers will be passed with each HTTP request to the Swarming
  server (and only Swarming server). The bot will use the returned headers until
  they are close to expiration (usually 6 min, see AUTH_HEADERS_EXPIRATION_SEC
  in remote_client.py), and then it'll attempt to refresh them by calling
  get_authentication_headers again.

  Can be used to implement per-bot authentication. If no headers are returned,
  the server will use only IP whitelist for bot authentication.

  On GCE will use OAuth token of the default GCE service account. It should have
  "User info" API scope enabled (this can be set when starting an instance). The
  server should be configured (via bots.cfg) to trust this account (see
  'require_service_account' in bots.proto).

  May be called by different threads, but never concurrently.

  Arguments:
  - bot: bot.Bot instance. See ../api/bot.py.

  Returns:
    Tuple (dict with headers or None, unix timestamp of when they expire).
  """
    if platforms.is_gce():
        # By default, VMs do not have "User info" API enabled, as commented above.
        # When this is the case, the oauth token is unusable. So do not use the
        # oauth token in this case and fall back to IP based whitelisting.
        if ('https://www.googleapis.com/auth/userinfo.email'
                in platforms.gce.oauth2_available_scopes('default')):
            tok, exp = platforms.gce.oauth2_access_token_with_expiration(
                'default')
            return {'Authorization': 'Bearer %s' % tok}, exp
    return (None, None)
Esempio n. 2
0
def can_send_metric():
  """True if 'send_metric' really does something."""
  if platforms.is_gce():
    # Scope to use Cloud Monitoring.
    scope = 'https://www.googleapis.com/auth/monitoring'
    return scope in platforms.gce.oauth2_available_scopes()
  return False
Esempio n. 3
0
  def test_get_dimensions(self):
    dimensions = os_utilities.get_dimensions()
    for key, values in dimensions.iteritems():
      self.assertIsInstance(key, unicode)
      self.assertIsInstance(values, list)
      for value in values:
        self.assertIsInstance(value, unicode)
    actual = set(dimensions)
    # Only set when the process is running in a properly configured GUI context.
    actual.discard(u'locale')
    # Only set on machines with SSD.
    actual.discard(u'ssd')
    # There are cases where this dimension is not set.
    actual.discard(u'machine_type')

    expected = {u'cores', u'cpu', u'gpu', u'id', u'os', u'pool', u'python'}
    if platforms.is_gce():
      expected.add(u'image')
      expected.add(u'zone')
    if sys.platform == 'darwin':
      expected.add(u'hidpi')
      expected.add(u'mac_model')
      expected.add(u'xcode_version')
    if sys.platform == 'linux2':
      expected.add(u'inside_docker')
      expected.add(u'kvm')
    if sys.platform == 'win32':
      expected.add(u'integrity')
    self.assertEqual(expected, actual)
Esempio n. 4
0
def can_send_metric():
  """True if 'send_metric' really does something."""
  if platforms.is_gce():
    # Scope to use Cloud Monitoring.
    scope = 'https://www.googleapis.com/auth/monitoring'
    return scope in platforms.gce.oauth2_available_scopes()
  return False
Esempio n. 5
0
def get_dimensions():
    """Returns the default dimensions."""
    os_name = get_os_name()
    cpu_type = get_cpu_type()
    cpu_bitness = get_cpu_bitness()
    dimensions = {
        u'cores': [unicode(get_num_processors())],
        u'cpu': [
            cpu_type,
            cpu_type + u'-' + cpu_bitness,
        ],
        u'gpu': get_gpu()[0],
        u'id': [get_hostname_short()],
        u'os': [os_name],
        u'pool': [u'default'],
    }
    if u'avx2' in get_cpuinfo().get(u'flags', []):
        dimensions[u'cpu'].append(cpu_type + u'-' + cpu_bitness + u'-avx2')
    os_version_name = get_os_version_name()
    if os_version_name:
        # This only happens on Windows.
        dimensions[u'os'].append(u'%s-%s' % (os_name, os_version_name))
    else:
        dimensions[u'os'].append(u'%s-%s' % (os_name, get_os_version_number()))
    if u'none' not in dimensions[u'gpu']:
        hidpi = get_monitor_hidpi()
        if hidpi:
            dimensions[u'hidpi'] = hidpi

    machine_type = get_machine_type()
    if machine_type:
        dimensions[u'machine_type'] = [machine_type]
    if platforms.is_gce():
        dimensions[u'zone'] = [platforms.gce.get_zone()]

    if cpu_type.startswith(u'arm') and cpu_type != u'arm':
        dimensions[u'cpu'].append(u'arm')
        dimensions[u'cpu'].append(u'arm-' + cpu_bitness)
        dimensions[u'cpu'].sort()

    if sys.platform == 'linux2':
        dimensions[u'os'].append(u'Linux')
        dimensions[u'os'].sort()

    if sys.platform == 'darwin':
        udids = platforms.osx.get_ios_device_ids()
        device_types = set()
        for udid in udids:
            version = platforms.osx.get_ios_version(udid)
            if version:
                dimensions[u'os'].append('iOS-%s' % version)
            device_type = platforms.osx.get_ios_device_type(udid)
            if device_type:
                device_types.add(device_type)
        if device_types:
            dimensions[u'device'] = sorted(device_types)
        dimensions[u'xcode_version'] = platforms.osx.get_xcode_versions()

    return dimensions
Esempio n. 6
0
def get_dimensions():
  """Returns the default dimensions."""
  os_name = get_os_name()
  cpu_type = get_cpu_type()
  cpu_bitness = get_cpu_bitness()
  dimensions = {
    u'cores': [unicode(get_num_processors())],
    u'cpu': [
      cpu_type,
      cpu_type + u'-' + cpu_bitness,
    ],
    u'gpu': get_gpu()[0],
    u'id': [get_hostname_short()],
    u'os': [os_name],
    u'pool': [u'default'],
  }
  if u'avx2' in get_cpuinfo().get(u'flags', []):
    dimensions[u'cpu'].append(cpu_type + u'-' + cpu_bitness + u'-avx2')
  os_version_name = get_os_version_name()
  if os_version_name:
    # This only happens on Windows.
    dimensions[u'os'].append(u'%s-%s' % (os_name, os_version_name))
  else:
    dimensions[u'os'].append(u'%s-%s' % (os_name, get_os_version_number()))
  if u'none' not in dimensions[u'gpu']:
    hidpi = get_monitor_hidpi()
    if hidpi:
      dimensions[u'hidpi'] = hidpi

  machine_type = get_machine_type()
  if machine_type:
    dimensions[u'machine_type'] = [machine_type]
  if platforms.is_gce():
    dimensions[u'zone'] = [platforms.gce.get_zone()]

  if cpu_type.startswith(u'arm') and cpu_type != u'arm':
    dimensions[u'cpu'].append(u'arm')
    dimensions[u'cpu'].append(u'arm-' + cpu_bitness)
    dimensions[u'cpu'].sort()

  if sys.platform == 'linux2':
    dimensions[u'os'].append(u'Linux')
    dimensions[u'os'].sort()

  if sys.platform == 'darwin':
    udids = platforms.osx.get_ios_device_ids()
    device_types = set()
    for udid in udids:
      version = platforms.osx.get_ios_version(udid)
      if version:
        dimensions[u'os'].append('iOS-%s' % version)
      device_type = platforms.osx.get_ios_device_type(udid)
      if device_type:
        device_types.add(device_type)
    if device_types:
      dimensions[u'device'] = sorted(device_types)
    dimensions[u'xcode_version'] = platforms.osx.get_xcode_versions()

  return dimensions
Esempio n. 7
0
def get_cpuinfo():
  """Returns the flags of the processor."""
  if sys.platform == 'darwin':
    info = platforms.osx.get_cpuinfo()
  elif sys.platform == 'win32':
    info = platforms.win.get_cpuinfo()
  elif sys.platform == 'linux2':
     info = platforms.linux.get_cpuinfo()
  else:
    info = {}
  if platforms.is_gce():
    # On GCE, the OS reports a generic CPU. Replace with GCE-specific details,
    # keeping the CPU flags as reported by the OS.
    info.update(platforms.gce.get_cpuinfo() or {})
  return info
Esempio n. 8
0
def get_hostname():
    """Returns the machine's hostname."""
    if platforms.is_gce():
        # When running on GCE, always use the hostname as defined by GCE. It's
        # possible the VM hadn't learned about it yet.
        meta = platforms.gce.get_metadata() or {}
        hostname = meta.get('instance', {}).get('hostname')
        if hostname:
            return unicode(hostname)

    # Windows enjoys putting random case in there. Enforces lower case for sanity.
    hostname = socket.getfqdn().lower()
    if hostname.endswith('.in-addr.arpa'):
        # When OSX fails to get the FDQN, it returns as the base name the IPv4
        # address reversed, which is not useful. Get the base hostname as defined by
        # the host itself instead of the FQDN since the returned FQDN is useless.
        hostname = socket.gethostname()
    return unicode(hostname)
Esempio n. 9
0
def get_hostname():
  """Returns the machine's hostname."""
  if platforms.is_gce():
    # When running on GCE, always use the hostname as defined by GCE. It's
    # possible the VM hadn't learned about it yet.
    meta = platforms.gce.get_metadata() or {}
    hostname = meta.get('instance', {}).get('hostname')
    if hostname:
      return unicode(hostname)

  # Windows enjoys putting random case in there. Enforces lower case for sanity.
  hostname = socket.getfqdn().lower()
  if hostname.endswith('.in-addr.arpa'):
    # When OSX fails to get the FDQN, it returns as the base name the IPv4
    # address reversed, which is not useful. Get the base hostname as defined by
    # the host itself instead of the FQDN since the returned FQDN is useless.
    hostname = socket.gethostname()
  return unicode(hostname)
Esempio n. 10
0
def get_machine_type():
    """Returns a GCE-equivalent machine type.

  If running on GCE, returns the right machine type. Otherwise tries to find the
  'closest' one.
  """
    if platforms.is_gce():
        return platforms.gce.get_machine_type()

    ram_gb = get_physical_ram() / 1024.
    cores = get_num_processors()
    ram_gb_per_core = ram_gb / cores
    logging.info('RAM GB/core = %.3f', ram_gb_per_core)
    best_fit = None
    for ratio, prefix in GCE_RAM_GB_PER_CORE_RATIOS.iteritems():
        delta = (ram_gb_per_core - ratio)**2
        if best_fit is None or delta < best_fit[0]:
            best_fit = (delta, prefix)
    prefix = best_fit[1]
    machine_type = prefix + unicode(cores)
    if machine_type not in GCE_MACHINE_COST_HOUR_US:
        # Try a best fit.
        logging.info('Failed to find a good machine_type match: %s',
                     machine_type)
        for i in (16, 8, 4, 2):
            if cores > i:
                machine_type = prefix + unicode(i)
                break
        else:
            if cores == 1:
                # There's no n1-highcpu-1 nor n1-highmem-1.
                if ram_gb < 1.7:
                    machine_type = u'f1-micro'
                elif ram_gb < 3.75:
                    machine_type = u'g1-small'
                else:
                    machine_type = u'n1-standard-1'
            else:
                logging.info('Failed to find a fit: %s', machine_type)

    if machine_type not in GCE_MACHINE_COST_HOUR_US:
        return None
    return machine_type
Esempio n. 11
0
def get_machine_type():
  """Returns a GCE-equivalent machine type.

  If running on GCE, returns the right machine type. Otherwise tries to find the
  'closest' one.
  """
  if platforms.is_gce():
    return platforms.gce.get_machine_type()

  ram_gb = get_physical_ram() / 1024.
  cores = get_num_processors()
  ram_gb_per_core = ram_gb / cores
  logging.info('RAM GB/core = %.3f', ram_gb_per_core)
  best_fit = None
  for ratio, prefix in GCE_RAM_GB_PER_CORE_RATIOS.iteritems():
    delta = (ram_gb_per_core-ratio)**2
    if best_fit is None or delta < best_fit[0]:
      best_fit = (delta, prefix)
  prefix = best_fit[1]
  machine_type = prefix + unicode(cores)
  if machine_type not in GCE_MACHINE_COST_HOUR_US:
    # Try a best fit.
    logging.info('Failed to find a good machine_type match: %s', machine_type)
    for i in (16, 8, 4, 2):
      if cores > i:
        machine_type = prefix + unicode(i)
        break
    else:
      if cores == 1:
        # There's no n1-highcpu-1 nor n1-highmem-1.
        if ram_gb < 1.7:
          machine_type = u'f1-micro'
        elif ram_gb < 3.75:
          machine_type = u'g1-small'
        else:
          machine_type = u'n1-standard-1'
      else:
        logging.info('Failed to find a fit: %s', machine_type)

  if machine_type not in GCE_MACHINE_COST_HOUR_US:
    return None
  return machine_type
Esempio n. 12
0
def get_dimensions():
  """Returns the default dimensions."""
  os_name = get_os_name()
  cpu_type = get_cpu_type()
  cpu_bitness = get_cpu_bitness()
  dimensions = {
    u'cores': [unicode(get_num_processors())],
    u'cpu': [
      cpu_type,
      cpu_type + u'-' + cpu_bitness,
    ],
    u'gpu': get_gpu()[0],
    u'id': [get_hostname_short()],
    u'os': [os_name],
  }
  os_version_name = get_os_version_name()
  if os_version_name:
    # This only happens on Windows.
    dimensions[u'os'].append(u'%s-%s' % (os_name, os_version_name))
  else:
    dimensions[u'os'].append(u'%s-%s' % (os_name, get_os_version_number()))
  if u'none' not in dimensions[u'gpu']:
    hidpi = get_monitor_hidpi()
    if hidpi:
      dimensions[u'hidpi'] = hidpi

  machine_type = get_machine_type()
  if machine_type:
    dimensions[u'machine_type'] = [machine_type]
  if platforms.is_gce():
    dimensions[u'zone'] = [platforms.gce.get_zone()]

  if cpu_type.startswith(u'arm') and cpu_type != u'arm':
    dimensions[u'cpu'].append(u'arm')
    dimensions[u'cpu'].append(u'arm-' + cpu_bitness)
    dimensions[u'cpu'].sort()

  if sys.platform == 'linux2':
    dimensions[u'os'].append(u'Linux')
    dimensions[u'os'].sort()

  return dimensions
Esempio n. 13
0
    def test_get_dimensions(self):
        dimensions = os_utilities.get_dimensions()
        for key, values in dimensions.items():
            self.assertIsInstance(key, six.text_type)
            self.assertIsInstance(values, list)
            for value in values:
                self.assertIsInstance(value, six.text_type)
        actual = set(dimensions)
        # Only set when the process is running in a properly configured GUI context.
        actual.discard(u'locale')
        # Only set on machines with SSD.
        actual.discard(u'ssd')
        # There are cases where this dimension is not set.
        actual.discard(u'machine_type')
        # Only set on ARM Linux machines.
        actual.discard(u'device_tree_compatible')
        # Only set on bare metal Linux machines.
        actual.discard(u'cpu_governor')
        # Only set on Windows machines.
        actual.discard(u'visual_studio_version')
        # Only set on Windows machines.
        actual.discard(u'windows_client_version')

        expected = {
            u'cores', u'cpu', u'gce', u'gpu', u'id', u'os', u'pool', u'python'
        }
        if platforms.is_gce():
            expected.add(u'image')
            expected.add(u'zone')
            expected.add(u'gcp')
        if sys.platform == 'darwin':
            expected.add(u'mac_model')
            # Bot may not have HiDPI and Xcode preinstalled
            actual.discard(u'hidpi')
            actual.discard(u'xcode_version')
            actual.discard(u'device')  # iOS devices
        if sys.platform.startswith('linux'):
            expected.add(u'inside_docker')
            expected.add(u'kvm')
        if sys.platform == 'win32':
            expected.add(u'integrity')
        self.assertEqual(expected, actual)
Esempio n. 14
0
def get_dimensions():
  """Returns the default dimensions."""
  os_name = get_os_name()
  cpu_type = get_cpu_type()
  cpu_bitness = get_cpu_bitness()
  dimensions = {
    u'cores': [unicode(get_num_processors())],
    u'cpu': [
      cpu_type,
      cpu_type + u'-' + cpu_bitness,
    ],
    u'gpu': get_gpu()[0],
    u'id': [get_hostname_short()],
    u'os': [os_name],
  }
  os_version_name = get_os_version_name()
  if os_version_name:
    # This only happens on Windows.
    dimensions[u'os'].append(u'%s-%s' % (os_name, os_version_name))
  else:
    dimensions[u'os'].append(u'%s-%s' % (os_name, get_os_version_number()))
  if u'none' not in dimensions[u'gpu']:
    hidpi = get_monitor_hidpi()
    if hidpi:
      dimensions[u'hidpi'] = hidpi

  machine_type = get_machine_type()
  if machine_type:
    dimensions[u'machine_type'] = [machine_type]
  if platforms.is_gce():
    dimensions[u'zone'] = [platforms.gce.get_zone()]

  if cpu_type.startswith(u'arm') and cpu_type != u'arm':
    dimensions[u'cpu'].append(u'arm')
    dimensions[u'cpu'].append(u'arm-' + cpu_bitness)
    dimensions[u'cpu'].sort()

  if sys.platform == 'linux2':
    dimensions[u'os'].append(u'Linux')
    dimensions[u'os'].sort()

  return dimensions
Esempio n. 15
0
def get_cost_hour():
    """Returns the cost in $USD/h as a floating point value if applicable."""
    # Machine.
    machine_type = get_machine_type()
    if platforms.is_gce():
        if platforms.gce.get_zone().startswith('us-'):
            machine_cost = GCE_MACHINE_COST_HOUR_US.get(machine_type, 0.)
        else:
            machine_cost = GCE_MACHINE_COST_HOUR_EUROPE_ASIA.get(
                machine_type, 0.)
    else:
        # Guess an equivalent machine_type.
        machine_cost = GCE_MACHINE_COST_HOUR_US.get(machine_type, 0.)

    # OS.
    os_cost = 0.
    if sys.platform == 'darwin':
        # Apple tax. It's 50% better, right?
        os_cost = GCE_WINDOWS_COST_CORE_HOUR * 1.5 * get_num_processors()
    elif sys.platform == 'win32':
        # MS tax.
        if machine_type in ('f1-micro', 'g1-small'):
            os_cost = 0.02
        else:
            os_cost = GCE_WINDOWS_COST_CORE_HOUR * get_num_processors()

    # Disk.
    # TODO(maruel): Figure out the disk type. The metadata is not useful AFAIK.
    # Assume HDD for now, it's the cheapest. That's not true, we do have SSDs.
    disk_gb_cost = 0.
    for disk in get_disks_info().itervalues():
        disk_gb_cost += disk[u'free_mb'] / 1024. * (GCE_HDD_GB_COST_MONTH /
                                                    30. / 24.)

    # TODO(maruel): Network. It's not a constant cost, it's per task.
    # See https://cloud.google.com/monitoring/api/metrics
    # compute.googleapis.com/instance/network/sent_bytes_count
    return machine_cost + os_cost + disk_gb_cost
Esempio n. 16
0
def get_authentication_headers(bot):
  """Returns authentication headers and their expiration time.
  The returned headers will be passed with each HTTP request to the Swarming
  server (and only Swarming server). The bot will use the returned headers until
  they are close to expiration (usually 6 min, see AUTH_HEADERS_EXPIRATION_SEC
  in remote_client.py), and then it'll attempt to refresh them by calling
  get_authentication_headers again.
  Can be used to implement per-bot authentication. If no headers are returned,
  the server will use only IP whitelist for bot authentication.
  On GCE will use OAuth token of the default GCE service account. It should have
  "User info" API scope enabled (this can be set when starting an instance). The
  server should be configured (via bots.cfg) to trust this account (see
  'require_service_account' in bots.proto).
  May be called by different threads, but never concurrently.
  Arguments:
  - bot: bot.Bot instance. See ../api/bot.py.
  Returns:
    Tuple (dict with headers or None, unix timestamp of when they expire).
  """
  if platforms.is_gce():
    tok = platforms.gce.oauth2_access_token()
    return {'Authorization': 'Bearer %s' % tok}, time.time() + 5*60
  return (None, None)
Esempio n. 17
0
def get_cost_hour():
  """Returns the cost in $USD/h as a floating point value if applicable."""
  # Machine.
  machine_type = get_machine_type()
  if platforms.is_gce():
    if platforms.gce.get_zone().startswith('us-'):
      machine_cost = GCE_MACHINE_COST_HOUR_US[machine_type]
    else:
      machine_cost = GCE_MACHINE_COST_HOUR_EUROPE_ASIA[machine_type]
  else:
    # Guess an equivalent machine_type.
    machine_cost = GCE_MACHINE_COST_HOUR_US.get(machine_type, 0.)

  # OS.
  os_cost = 0.
  if sys.platform == 'darwin':
    # Apple tax. It's 50% better, right?
    os_cost = GCE_WINDOWS_COST_CORE_HOUR * 1.5 * get_num_processors()
  elif sys.platform == 'win32':
    # MS tax.
    if machine_type in ('f1-micro', 'g1-small'):
      os_cost = 0.02
    else:
      os_cost = GCE_WINDOWS_COST_CORE_HOUR * get_num_processors()

  # Disk.
  # TODO(maruel): Figure out the disk type. The metadata is not useful AFAIK.
  # Assume HDD for now, it's the cheapest. That's not true, we do have SSDs.
  disk_gb_cost = 0.
  for disk in get_disks_info().itervalues():
    disk_gb_cost += disk['free_mb'] / 1024. * (
        GCE_HDD_GB_COST_MONTH / 30. / 24.)

  # TODO(maruel): Network. It's not a constant cost, it's per task.
  # See https://cloud.google.com/monitoring/api/metrics
  # compute.googleapis.com/instance/network/sent_bytes_count
  return machine_cost + os_cost + disk_gb_cost
Esempio n. 18
0
def authenticated_http_request(service_account, *args, **kwargs):
  """Sends an OAuth2-authenticated HTTP request.

  Args:
    service_account: Service account to use. For GCE, the name of the service
      account, otherwise the path to the service account JSON file.

  Raises:
    AuthenticatedHttpRequestFailure
  """
  scopes = kwargs.pop('scopes', [])
  kwargs['headers'] = kwargs.get('headers', {}).copy()
  http = httplib2.Http(ca_certs=tools.get_cacerts_bundle())

  # Authorize the request. In general, we need to produce an OAuth2 bearer token
  # using a service account JSON file. However on GCE there is a shortcut: it
  # can fetch the current bearer token right from the instance metadata without
  # the need for the oauth2client.client library.
  if platforms.is_gce():
    try:
      gce_bearer_token = platforms.gce.oauth2_access_token(
          account=service_account)
    except (IOError, urllib2.HTTPError) as e:
      raise AuthenticatedHttpRequestFailure(e)
    kwargs['headers']['Authorization'] = 'Bearer %s' % gce_bearer_token
  else:
    try:
      oauth2client = get_oauth2_client(service_account, scopes)
    except (IOError, OSError, ValueError) as e:
      raise AuthenticatedHttpRequestFailure(e)
    http = oauth2client.authorize(http)

  try:
    return http.request(*args, **kwargs)
  except client.Error as e:
    raise AuthenticatedHttpRequestFailure(e)
Esempio n. 19
0
def authenticated_http_request(service_account, *args, **kwargs):
  """Sends an OAuth2-authenticated HTTP request.

  Args:
    service_account: Service account to use. For GCE, the name of the service
      account, otherwise the path to the service account JSON file.

  Raises:
    AuthenticatedHttpRequestFailure
  """
  scopes = kwargs.pop('scopes', [])
  kwargs['headers'] = kwargs.get('headers', {}).copy()
  http = httplib2.Http(ca_certs=tools.get_cacerts_bundle())

  # Authorize the request. In general, we need to produce an OAuth2 bearer token
  # using a service account JSON file. However on GCE there is a shortcut: it
  # can fetch the current bearer token right from the instance metadata without
  # the need for the oauth2client.client library.
  if platforms.is_gce():
    try:
      gce_bearer_token, _ = platforms.gce.oauth2_access_token_with_expiration(
          account=service_account)
    except (IOError, urllib2.HTTPError) as e:
      raise AuthenticatedHttpRequestFailure(e)
    kwargs['headers']['Authorization'] = 'Bearer %s' % gce_bearer_token
  else:
    try:
      oauth2client = get_oauth2_client(service_account, scopes)
    except (IOError, OSError, ValueError) as e:
      raise AuthenticatedHttpRequestFailure(e)
    http = oauth2client.authorize(http)

  try:
    return http.request(*args, **kwargs)
  except client.Error as e:
    raise AuthenticatedHttpRequestFailure(e)
Esempio n. 20
0
def get_dimensions():
    """Returns the default dimensions."""
    dimensions = {
        u'cores': [six.ensure_text(str(get_num_processors()))],
        u'cpu': get_cpu_dimensions(),
        u'gpu': get_gpu()[0],
        u'id': [get_hostname_short()],
        u'os': get_os_values(),
        # This value is frequently overridden by bots.cfg via luci-config.
        u'pool': [u'default'],
        u'python': [six.ensure_text(sys.version).split()[0]],
    }

    # Conditional dimensions:
    id_override = os.environ.get('SWARMING_BOT_ID')
    if id_override:
        dimensions[u'id'] = [six.ensure_text(id_override)]

    caches = get_named_caches_info()
    if caches:
        dimensions[u'caches'] = sorted(caches)

    if u'none' not in dimensions[u'gpu']:
        hidpi = get_monitor_hidpi()
        if hidpi:
            dimensions[u'hidpi'] = hidpi

    machine_type = get_machine_type()
    if machine_type:
        dimensions[u'machine_type'] = [machine_type]

    if platforms.is_gce():
        dimensions[u'gce'] = [u'1']
        image = platforms.gce.get_image()
        if image:
            dimensions[u'image'] = [image]
        dimensions[u'zone'] = platforms.gce.get_zones()
        dimensions[u'gcp'] = platforms.gce.get_gcp()
    else:
        dimensions[u'gce'] = [u'0']

    loc = get_locale()
    if loc:
        dimensions[u'locale'] = [loc]

    ssd = get_ssd()
    if ssd:
        dimensions[u'ssd'] = [u'1']
    else:
        dimensions[u'ssd'] = [u'0']

    if sys.platform.startswith('linux'):
        inside_docker = platforms.linux.get_inside_docker()
        if not inside_docker:
            dimensions[u'inside_docker'] = [u'0']
        else:
            dimensions[u'inside_docker'] = [u'1', inside_docker]

        dimensions[u'kvm'] = [six.text_type(int(platforms.linux.get_kvm()))]

        comp = platforms.linux.get_device_tree_compatible()
        if comp:
            dimensions[u'device_tree_compatible'] = comp
        # Just check CPU #0. In practice different CPU core could have different CPU
        # governor.
        gov = platforms.linux.get_cpu_scaling_governor(0)
        if gov:
            dimensions[u'cpu_governor'] = gov

    if sys.platform == 'darwin':
        model = platforms.osx.get_hardware_model_string()
        if model:
            dimensions[u'mac_model'] = [model]
        xcode_versions = platforms.osx.get_xcode_versions()
        if xcode_versions:
            dimensions[u'xcode_version'] = xcode_versions

        # iOS devices
        udids = platforms.osx.get_ios_device_ids()
        device_types = set()
        for udid in udids:
            version = platforms.osx.get_ios_version(udid)
            if version:
                dimensions[u'os'].append('iOS-%s' % version)
                dimensions[u'os'].sort()
            device_type = platforms.osx.get_ios_device_type(udid)
            if device_type:
                device_types.add(device_type)
        if device_types:
            dimensions[u'device'] = sorted(device_types)

    if sys.platform == 'win32':
        integrity = platforms.win.get_integrity_level()
        if integrity is not None:
            dimensions[u'integrity'] = [integrity]
        vs_versions = platforms.win.get_visual_studio_versions()
        if vs_versions:
            dimensions[u'visual_studio_version'] = vs_versions
        windows_client_versions = platforms.win.get_client_versions()
        if windows_client_versions:
            dimensions[u'windows_client_version'] = windows_client_versions

    return dimensions
Esempio n. 21
0
def send_metric(name, value):
  if platforms.is_gce():
    return platforms.gce.send_metric(name, value)
Esempio n. 22
0
def run_bot(arg_error):
    """Runs the bot until it reboots or self-update or a signal is received.

  When a signal is received, simply exit.
  """
    quit_bit = threading.Event()

    def handler(sig, _):
        logging.info('Got signal %s', sig)
        quit_bit.set()

    # TODO(maruel): Set quit_bit when stdin is closed on Windows.

    with subprocess42.set_signal_handler(subprocess42.STOP_SIGNALS, handler):
        config = get_config()
        try:
            # First thing is to get an arbitrary url. This also ensures the network is
            # up and running, which is necessary before trying to get the FQDN below.
            # There's no need to do error handling here - the "ping" is just to "wake
            # up" the network; if there's something seriously wrong, the handshake
            # will fail and we'll handle it there.
            remote = remote_client.createRemoteClient(config['server'], None)
            remote.ping()
        except Exception as e:
            # url_read() already traps pretty much every exceptions. This except
            # clause is kept there "just in case".
            logging.exception('server_ping threw')

        # If we are on GCE, we want to make sure GCE metadata server responds, since
        # we use the metadata to derive bot ID, dimensions and state.
        if platforms.is_gce():
            logging.info('Running on GCE, waiting for the metadata server')
            platforms.gce.wait_for_metadata(quit_bit)
            if quit_bit.is_set():
                logging.info('Early quit 1')
                return 0

        # Next we make sure the bot can make authenticated calls by grabbing
        # the auth headers, retrying on errors a bunch of times. We don't give up
        # if it fails though (maybe the bot will "fix itself" later).
        botobj = get_bot()
        try:
            botobj.remote.initialize(quit_bit)
        except remote_client.InitializationError as exc:
            botobj.post_error('failed to grab auth headers: %s' %
                              exc.last_error)
            logging.error('Can\'t grab auth headers, continuing anyway...')

        if arg_error:
            botobj.post_error('Bootstrapping error: %s' % arg_error)

        if quit_bit.is_set():
            logging.info('Early quit 2')
            return 0

        call_hook(botobj, 'on_bot_startup')

        # Initial attributes passed to bot.Bot in get_bot above were constructed for
        # 'fake' bot ID ('none'). Refresh them to match the real bot ID, now that we
        # have fully initialize bot.Bot object. Note that 'get_dimensions' and
        # 'get_state' may depend on actions done by 'on_bot_startup' hook, that's
        # why we do it here and not in 'get_bot'.
        botobj._update_dimensions(get_dimensions(botobj))
        botobj._update_state(get_state(botobj, 0))

        if quit_bit.is_set():
            logging.info('Early quit 3')
            return 0

        # This is the first authenticated request to the server. If the bot is
        # misconfigured, the request may fail with HTTP 401 or HTTP 403. Instead of
        # dying right away, spin in a loop, hoping the bot will "fix itself"
        # eventually. Authentication errors in /handshake are logged on the server
        # and generate error reports, so bots stuck in this state are discoverable.
        sleep_time = 5
        while not quit_bit.is_set():
            resp = botobj.remote.do_handshake(botobj._attributes)
            if resp:
                logging.info('Connected to %s', resp.get('server_version'))
                if resp.get('bot_version') != botobj._attributes['version']:
                    logging.warning(
                        'Found out we\'ll need to update: server said %s; we\'re %s',
                        resp.get('bot_version'), botobj._attributes['version'])
                # Remember the server-provided per-bot configuration. '/handshake' is
                # the only place where the server returns it. The bot will be sending
                # the 'bot_group_cfg_version' back in each /poll (as part of 'state'),
                # so that the server can instruct the bot to restart itself when
                # config changes.
                cfg_version = resp.get('bot_group_cfg_version')
                if cfg_version:
                    botobj._update_bot_group_cfg(cfg_version,
                                                 resp.get('bot_group_cfg'))
                break
            logging.error(
                'Failed to contact for handshake, retrying in %d sec...',
                sleep_time)
            quit_bit.wait(sleep_time)
            sleep_time = min(300, sleep_time * 2)

        if quit_bit.is_set():
            logging.info('Early quit 4')
            return 0

        # Let the bot to finish the initialization, now that it knows its server
        # defined dimensions.
        call_hook(botobj, 'on_handshake')

        cleanup_bot_directory(botobj)
        clean_cache(botobj)

        if quit_bit.is_set():
            logging.info('Early quit 5')
            return 0

        # This environment variable is accessible to the tasks executed by this bot.
        os.environ['SWARMING_BOT_ID'] = botobj.id.encode('utf-8')

        consecutive_sleeps = 0
        last_action = time.time()
        while not quit_bit.is_set():
            try:
                botobj._update_dimensions(get_dimensions(botobj))
                botobj._update_state(get_state(botobj, consecutive_sleeps))
                did_something = poll_server(botobj, quit_bit, last_action)
                if did_something:
                    last_action = time.time()
                    consecutive_sleeps = 0
                else:
                    consecutive_sleeps += 1
            except Exception as e:
                logging.exception('poll_server failed')
                msg = '%s\n%s' % (e, traceback.format_exc()[-2048:])
                botobj.post_error(msg)
                consecutive_sleeps = 0
        logging.info('Quitting')

    # Tell the server we are going away.
    botobj.post_event('bot_shutdown', 'Signal was received')
    return 0
Esempio n. 23
0
def get_dimensions():
  """Returns the default dimensions."""
  dimensions = {
    u'cores': [unicode(get_num_processors())],
    u'cpu': get_cpu_dimensions(),
    u'gpu': get_gpu()[0],
    u'id': [get_hostname_short()],
    u'os': get_os_values(),
    # This value is frequently overridden by bots.cfg via luci-config.
    u'pool': [u'default'],
    u'python': [unicode(sys.version).split()[0]],
  }

  # Conditional dimensions:
  id_override = os.environ.get('SWARMING_BOT_ID')
  if id_override:
    dimensions[u'id'] = [unicode(id_override)]

  caches = get_named_caches_info()
  if caches:
    dimensions[u'caches'] = sorted(caches)

  if u'none' not in dimensions[u'gpu']:
    hidpi = get_monitor_hidpi()
    if hidpi:
      dimensions[u'hidpi'] = hidpi

  machine_type = get_machine_type()
  if machine_type:
    dimensions[u'machine_type'] = [machine_type]

  if platforms.is_gce():
    image = platforms.gce.get_image()
    if image:
      dimensions[u'image'] = [image]
    dimensions[u'zone'] = platforms.gce.get_zones()

  loc = get_locale()
  if loc:
    dimensions[u'locale'] = [loc]

  ssd = get_ssd()
  if ssd:
    dimensions[u'ssd'] = [u'1']

  if sys.platform == 'linux2':
    inside_docker = platforms.linux.get_inside_docker()
    if not inside_docker:
      dimensions[u'inside_docker'] = [u'0']
    else:
      dimensions[u'inside_docker'] = [u'1', inside_docker]

    dimensions[u'kvm'] = [unicode(int(platforms.linux.get_kvm()))]

  if sys.platform == 'darwin':
    model = platforms.osx.get_hardware_model_string()
    if model:
      dimensions[u'mac_model'] = [model]
    xcode_versions = platforms.osx.get_xcode_versions()
    if xcode_versions:
      dimensions[u'xcode_version'] = xcode_versions

    # iOS devices
    udids = platforms.osx.get_ios_device_ids()
    device_types = set()
    for udid in udids:
      version = platforms.osx.get_ios_version(udid)
      if version:
        dimensions[u'os'].append('iOS-%s' % version)
        dimensions[u'os'].sort()
      device_type = platforms.osx.get_ios_device_type(udid)
      if device_type:
        device_types.add(device_type)
    if device_types:
      dimensions[u'device'] = sorted(device_types)

  if sys.platform == 'win32':
    integrity = platforms.win.get_integrity_level()
    if integrity is not None:
      dimensions[u'integrity'] = [integrity]

  return dimensions
Esempio n. 24
0
def send_metric(name, value):
  if platforms.is_gce():
    return platforms.gce.send_metric(name, value)