summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rwxr-xr-xgoogle-daemon/etc/init.d/google-clock-sync-manager153
-rwxr-xr-xgoogle-daemon/etc/init/google-clock-sync-manager.conf5
-rw-r--r--google-daemon/usr/lib/systemd/system/google-clock-sync-manager.service11
-rw-r--r--google-daemon/usr/share/google/google_daemon/accounts_manager.py6
-rwxr-xr-xgoogle-daemon/usr/share/google/google_daemon/desired_accounts.py6
-rwxr-xr-xgoogle-daemon/usr/share/google/google_daemon/manage_accounts.py6
-rwxr-xr-xgoogle-daemon/usr/share/google/google_daemon/manage_clock_sync.py85
-rwxr-xr-xgoogle-daemon/usr/share/google/google_daemon/metadata_watcher.py97
9 files changed, 361 insertions, 10 deletions
diff --git a/README.md b/README.md
index fd7fb54..0ebc126 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@ This repository is the collection of packages that are installed on the standard
1. [Image Bundle](https://cloud.google.com/compute/docs/images#buildingimage) - Tool that creates an image file out of a disk attached to a GCE VM.
1. [Google Startup Scripts](https://cloud.google.com/compute/docs/startupscript) - Scripts and configuration files that setup a Linux-based image to work smoothly with GCE.
-1. Google Daemon - A service that manages user accounts, maintains ssh login keys, and syncs public endpoint IP addresses.
+1. Google Daemon - A service that manages user accounts, maintains ssh login keys, syncs the system clock after migration, and syncs public endpoint IP addresses.
## Installation
The easiest way to install these packages into a Linux-based image is to extract each tarball to `/` (root). Image Bundle does not have a directory structure, it is recommended to it extract to `/usr/share/imagebundle`. The tarballs are available in [releases](https://github.com/GoogleCloudPlatform/compute-image-packages/releases).
diff --git a/google-daemon/etc/init.d/google-clock-sync-manager b/google-daemon/etc/init.d/google-clock-sync-manager
new file mode 100755
index 0000000..b85f9de
--- /dev/null
+++ b/google-daemon/etc/init.d/google-clock-sync-manager
@@ -0,0 +1,153 @@
+#! /bin/sh
+# 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.
+#
+### BEGIN INIT INFO
+# Provides: google-clock-manager
+# Required-Start: $network $syslog
+# Required-Stop: $network
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Example initscript
+# Description: This file should be used to construct scripts to be
+# placed in /etc/init.d.
+### END INIT INFO
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="Google clock sync manager"
+NAME=google-clock-sync-manager
+DAEMON=/usr/share/google/google_daemon/manage_clock_sync.py
+DAEMON_ARGS=""
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+# If we're running under upstart, let the upstart config file handle things.
+# Debian 7 and newer have a near-one-liner function to detect this...
+if type init_is_upstart >/dev/null 2>&1; then
+ # ... which we can use if present.
+ init_is_upstart && exit 0
+else
+ # Otherwise, directly include the core line of Debian 7's version.
+ # Authorship credit: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=661109
+ if [ -x /sbin/initctl ] && /sbin/initctl version | /bin/grep -q upstart; then
+ exit 0
+ fi
+fi
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --make-pidfile --background \
+ --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon --start --quiet --make-pidfile --background \
+ --pidfile $PIDFILE --exec $DAEMON -- \
+ $DAEMON_ARGS || return 2
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 \
+ --pidfile $PIDFILE
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc -p "$PIDFILE" "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ restart|force-reload)
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/google-daemon/etc/init/google-clock-sync-manager.conf b/google-daemon/etc/init/google-clock-sync-manager.conf
new file mode 100755
index 0000000..6d23a28
--- /dev/null
+++ b/google-daemon/etc/init/google-clock-sync-manager.conf
@@ -0,0 +1,5 @@
+# This service syncs the clock after migration in a Google Compute Engine instance.
+start on google-rc-local-has-run
+
+respawn
+exec /usr/share/google/google_daemon/manage_clock_sync.py
diff --git a/google-daemon/usr/lib/systemd/system/google-clock-sync-manager.service b/google-daemon/usr/lib/systemd/system/google-clock-sync-manager.service
new file mode 100644
index 0000000..c4fcc9b
--- /dev/null
+++ b/google-daemon/usr/lib/systemd/system/google-clock-sync-manager.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Google Compute Engine Clock Sync Daemon
+After=network.target
+Requires=network.target
+
+[Service]
+Type=simple
+ExecStart=/usr/share/google/google_daemon/manage_clock_sync.py
+
+[Install]
+WantedBy=multi-user.target
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)