summaryrefslogtreecommitdiff
path: root/google-daemon/usr/share/google/google_daemon/metadata_watcher.py
blob: af0a90ad908ed7ae60b7ec0f12cdb592bdcc0f42 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#!/usr/bin/python
# Copyright 2015 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import httplib
import time
import urllib
import urllib2


METADATA_URL = 'http://metadata.google.internal/computeMetadata/v1/'


class Error(Exception):
  pass


class UnexpectedStatusException(Error):
  pass


class MetadataWatcher(object):
  """Watches for changing metadata."""

  def __init__(self, httplib_module=httplib, time_module=time,
               urllib_module=urllib, urllib2_module=urllib2):
    self.httplib = httplib_module
    self.time = time_module
    self.urllib = urllib_module
    self.urllib2 = urllib2_module

  def WatchMetadataForever(self, metadata_key, handler, initial_value=None):
    """Watches for a change in the value of metadata.

    Args:
      metadata_key: The key identifying which metadata to watch for changes.
      handler: A callable to call when the metadata value changes. Will be passed
        a single parameter, the new value of the metadata.
      initial_value: The expected initial value for the metadata. The handler will
        not be called on the initial metadata request unless the value differs
        from this.

    Raises:
      UnexpectedStatusException: If the http request is unsuccessful for an
        unexpected reason.
    """
    params = {
        'wait_for_change': 'true',
        'last_etag': 0,
        }

    value = initial_value
    while True:
      # start a hanging-GET request for metadata key.
      url = '{base_url}{key}?{params}'.format(
          base_url=METADATA_URL,
          key=metadata_key,
          params=self.urllib.urlencode(params)
          )
      req = self.urllib2.Request(url, headers={'Metadata-Flavor': 'Google'})

      try:
        response = self.urllib2.urlopen(req)
        content = response.read()
        status = response.getcode()
      except self.urllib2.HTTPError as e:
        content = None
        status = e.code

      if status == self.httplib.SERVICE_UNAVAILABLE:
        self.time.sleep(1)
        continue
      elif status == self.httplib.OK:
        # Extract new metadata value and latest etag.
        new_value = content
        headers = response.info()
        params['last_etag'] = headers['ETag']
      else:
        raise UnexpectedStatusException(status)

      # If the metadata value changed, call the appropriate handler.
      if value == initial_value:
        value = new_value
      elif value != new_value:
        value = new_value
        handler(value)