diff options
Diffstat (limited to 'google-daemon/usr/share/google/google_daemon')
5 files changed, 191 insertions, 9 deletions
diff --git a/google-daemon/usr/share/google/google_daemon/accounts_manager.py b/google-daemon/usr/share/google/google_daemon/accounts_manager.py index 0e9897b..c1f2351 100644 --- a/google-daemon/usr/share/google/google_daemon/accounts_manager.py +++ b/google-daemon/usr/share/google/google_daemon/accounts_manager.py @@ -50,9 +50,9 @@ class AccountsManager(object): while True: # Fork and run the key regeneration and account update while the # parent waits for the subprocess to finish before continuing. - + # Create a pipe used to get the new etag value from child - reader, writer = os.pipe() # these are file descriptors, not file objects + reader, writer = os.pipe() # these are file descriptors, not file objects pid = os.fork() if pid: # we are the parent @@ -117,7 +117,7 @@ class AccountsManager(object): for entry in all_accounts if os.path.isfile(os.path.join(entry.pw_dir, keyfile_suffix))] extra_usernames = set(sshable_usernames) - set(desired_accounts.keys()) - + if desired_accounts: for username, ssh_keys in desired_accounts.iteritems(): if not username: diff --git a/google-daemon/usr/share/google/google_daemon/desired_accounts.py b/google-daemon/usr/share/google/google_daemon/desired_accounts.py index 9afe78c..feb3f14 100755 --- a/google-daemon/usr/share/google/google_daemon/desired_accounts.py +++ b/google-daemon/usr/share/google/google_daemon/desired_accounts.py @@ -87,7 +87,7 @@ def KeyHasExpired(key): expire_str) logging.error('Not expiring key.') return False - + # Expire the key if and only if we have exceeded the expiration timestamp. return (datetime.datetime.utcnow() > expire_time) @@ -170,7 +170,7 @@ class DesiredAccounts(object): return (attribute_value, etag) except urllib2.HTTPError as e: if e.code == 404: - # The attribute doesn't exist. Return None. + # The attribute doesn't exist. Return None. # No need to log a warning. return None # rethrow the exception since we don't know what it is. Let the @@ -231,6 +231,6 @@ class DesiredAccounts(object): logging.debug('Project sshKeys attribute not found.') # sshKeys doesn't exist for either project or instance. account_data = None - + self.attributes_etag = attributes_etag_cache return AccountDataToDictionary(account_data) diff --git a/google-daemon/usr/share/google/google_daemon/manage_accounts.py b/google-daemon/usr/share/google/google_daemon/manage_accounts.py index 0696ab2..7e18ae6 100755 --- a/google-daemon/usr/share/google/google_daemon/manage_accounts.py +++ b/google-daemon/usr/share/google/google_daemon/manage_accounts.py @@ -41,12 +41,12 @@ from utils import System def Main(accounts, desired_accounts, system, logger, log_handler, lock_file, lock_fname=None, single_pass=True, daemon_mode=False, debug_mode=False): - + if not log_handler: log_handler = system.MakeLoggingHandler( 'accounts-from-metadata', logging.handlers.SysLogHandler.LOG_AUTH) system.SetLoggingHandler(logger, log_handler) - + if debug_mode: system.EnableDebugLogging(logger) logging.debug('Running in Debug Mode') @@ -54,7 +54,7 @@ def Main(accounts, desired_accounts, system, logger, accounts_manager = AccountsManager( accounts, desired_accounts, system, lock_file, lock_fname, single_pass) - + if daemon_mode: manager_daemon = AccountsManagerDaemon(None, accounts_manager) manager_daemon.StartDaemon() diff --git a/google-daemon/usr/share/google/google_daemon/manage_clock_sync.py b/google-daemon/usr/share/google/google_daemon/manage_clock_sync.py new file mode 100755 index 0000000..c49f699 --- /dev/null +++ b/google-daemon/usr/share/google/google_daemon/manage_clock_sync.py @@ -0,0 +1,85 @@ +#!/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. + +"""Manages clock syncing after migration on GCE instances.""" + +import logging +import os +import sys + +def FixPath(): + parent_dir = os.path.dirname(os.path.realpath(sys.argv[0])) + if os.path.isdir(parent_dir): + sys.path.append(parent_dir) + + +FixPath() + +from utils import LockFile +from utils import System +from metadata_watcher import MetadataWatcher + + +LOCKFILE = '/var/lock/google-clock-sync.lock' + + +def HandleClockDriftToken(metadata_watcher, on_change): + """Watches for and responds to drift-token changes. + + Args: + metadata_watcher: a MetadataWatcher object. + on_change: a callable to call for any change. + """ + clock_drift_token_key = 'instance/virtual-clock/drift-token' + + def Handler(event): + on_change(event) + + metadata_watcher.WatchMetadataForever(clock_drift_token_key, + Handler, initial_value='') + + +def OnChange(event): + """Called when clock drift token changes. + + Args: + event: the new value of the drift token. + """ + system = System() + logging.info('Clock drift token has changed: %s', event) + logging.info('Syncing system time with hardware clock...') + result = system.RunCommand(['/sbin/hwclock', '--hctosys']) + if system.RunCommandFailed(result): + logging.error('Syncing system time failed.') + else: + logging.info('Synced system time with hardware clock.') + + +def Main(system=System(), logger=logging.getLogger(), log_handler=None, + lock_file=LockFile(), lock_fname=None): + if not log_handler: + log_handler = system.MakeLoggingHandler( + 'google-clock-sync', logging.handlers.SysLogHandler.LOG_SYSLOG) + system.SetLoggingHandler(logger, log_handler) + logging.info('Starting GCE clock sync') + + if not lock_fname: + lock_fname = LOCKFILE + watcher = MetadataWatcher() + lock_file.RunExclusively(lock_fname, HandleClockDriftToken(watcher, OnChange)) + + +if __name__ == '__main__': + Main() diff --git a/google-daemon/usr/share/google/google_daemon/metadata_watcher.py b/google-daemon/usr/share/google/google_daemon/metadata_watcher.py new file mode 100755 index 0000000..af0a90a --- /dev/null +++ b/google-daemon/usr/share/google/google_daemon/metadata_watcher.py @@ -0,0 +1,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) |