#!/usr/bin/env python

# Copyright (c) 2011 Citrix Systems, Inc.
# Copyright 2011 OpenStack LLC.
# Copyright 2011 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# 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.

#
# XenAPI plugin for reading/writing information to xenstore
#

try:
    import json
except ImportError:
    import simplejson as json
import os
import random
import subprocess
import tempfile
import time

import XenAPIPlugin

from pluginlib_nova import *
configure_logging("xenstore")
import xenstore

AGENT_TIMEOUT = 30


def jsonify(fnc):
    def wrapper(*args, **kwargs):
        return json.dumps(fnc(*args, **kwargs))
    return wrapper


class TimeoutError(StandardError):
    pass


@jsonify
def key_init(self, arg_dict):
    """Handles the Diffie-Hellman key exchange with the agent to
    establish the shared secret key used to encrypt/decrypt sensitive
    info to be passed, such as passwords. Returns the shared
    secret key value.
    """
    pub = int(arg_dict["pub"])
    arg_dict["value"] = json.dumps({"name": "keyinit", "value": pub})
    request_id = arg_dict["id"]
    arg_dict["path"] = "data/host/%s" % request_id
    xenstore.write_record(self, arg_dict)
    try:
        resp = _wait_for_agent(self, request_id, arg_dict)
    except TimeoutError, e:
        raise PluginError("%s" % e)
    return resp


@jsonify
def password(self, arg_dict):
    """Writes a request to xenstore that tells the agent to set
    the root password for the given VM. The password should be 
    encrypted using the shared secret key that was returned by a 
    previous call to key_init. The encrypted password value should
    be passed as the value for the 'enc_pass' key in arg_dict.
    """
    pub = int(arg_dict["pub"])
    enc_pass = arg_dict["enc_pass"]
    arg_dict["value"] = json.dumps({"name": "password", "value": enc_pass})
    request_id = arg_dict["id"]
    arg_dict["path"] = "data/host/%s" % request_id
    xenstore.write_record(self, arg_dict)
    try:
        resp = _wait_for_agent(self, request_id, arg_dict)
    except TimeoutError, e:
        raise PluginError("%s" % e)
    return resp


def _wait_for_agent(self, request_id, arg_dict):
    """Periodically checks xenstore for a response from the agent.
    The request is always written to 'data/host/{id}', and
    the agent's response for that request will be in 'data/guest/{id}'.
    If no value appears from the agent within the time specified by
    AGENT_TIMEOUT, the original request is deleted and a TimeoutError
    is returned.
    """
    arg_dict["path"] = "data/guest/%s" % request_id
    arg_dict["ignore_missing_path"] = True
    start = time.time()
    while True:
        if time.time() - start > AGENT_TIMEOUT:
            # No response within the timeout period; bail out
            # First, delete the request record
            arg_dict["path"] = "data/host/%s" % request_id
            xenstore.delete_record(self, arg_dict)
            raise TimeoutError("TIMEOUT: No response from agent within %s seconds." %
                    AGENT_TIMEOUT)
        ret = xenstore.read_record(self, arg_dict)
        # Note: the response for None with be a string that includes
        # double quotes.
        if ret != '"None"':
            # The agent responded
            return ret
        else:
            time.sleep(3)


if __name__ == "__main__":
    XenAPIPlugin.dispatch(
        {"key_init": key_init,
        "password": password})
