def shut_down_appscale_if_running(cls, options):
    """ Checks if AppScale is running and shuts it down as this is an offline upgrade.
      Args:
        options: A Namespace that has fields for each parameter that can be
          passed in via the command-line interface.
    """
    if os.path.exists(LocalState.get_secret_key_location(options.keyname)):
      AppScaleLogger.warn("AppScale needs to be down for this upgrade. "
        "Upgrade process could take a while and it is not reversible.")

      if not options.test:
        response = raw_input(
          'Are you sure you want to proceed with shutting down AppScale to '
          'continue the upgrade? (y/N) ')
        if response.lower() not in ['y', 'yes']:
          raise AppScaleException("Cancelled AppScale upgrade.")

      AppScaleLogger.log("Shutting down AppScale...")
      cls.terminate_instances(options)
    else:
      AppScaleLogger.warn("Upgrade process could take a while and it is not reversible.")

      if options.test:
        return

      response = raw_input(
        'Are you sure you want to proceed with the upgrade? (y/N) ')
      if response.lower() not in ['y', 'yes']:
        raise AppScaleException("Cancelled AppScale upgrade.")
Esempio n. 2
0
    def terminate_virtualized_cluster(cls, keyname, is_verbose):
        """Stops all API services running on all nodes in the currently running
    AppScale deployment.

    Args:
      keyname: The name of the SSH keypair used for this AppScale deployment.
      is_verbose: A bool that indicates if we should print the commands executed
        to stdout.
    """
        AppScaleLogger.log(
            "Terminating appscale deployment with keyname {0}".format(keyname))
        time.sleep(2)

        shadow_host = LocalState.get_host_with_role(keyname, 'shadow')
        try:
            secret = LocalState.get_secret_key(keyname)
        except IOError:
            # We couldn't find the secret key: AppScale is most likely not
            # running.
            raise AppScaleException("Couldn't find AppScale secret key.")

        acc = AppControllerClient(shadow_host, secret)
        try:
            all_ips = acc.get_all_public_ips()
        except Exception as exception:
            AppScaleLogger.warn(
                'Saw Exception while getting deployments IPs {0}'.format(
                    str(exception)))
            all_ips = LocalState.get_all_public_ips(keyname)

        threads = []
        for ip in all_ips:
            thread = threading.Thread(target=cls.stop_remote_appcontroller,
                                      args=(ip, keyname, is_verbose))
            thread.start()
            threads.append(thread)

        for thread in threads:
            thread.join()

        boxes_shut_down = 0
        is_running_regex = re.compile("appscale-controller stop")
        for ip in all_ips:
            AppScaleLogger.log(
                "Shutting down AppScale API services at {0}".format(ip))
            while True:
                remote_output = cls.ssh(ip, keyname, 'ps x', is_verbose)
                AppScaleLogger.verbose(remote_output, is_verbose)
                if not is_running_regex.match(remote_output):
                    break
                time.sleep(0.3)
            boxes_shut_down += 1

        if boxes_shut_down != len(all_ips):
            raise AppScaleException(
                "Couldn't terminate your AppScale deployment on"
                " all machines - please do so manually.")

        AppScaleLogger.log(
            "Terminated AppScale on {0} machines.".format(boxes_shut_down))
Esempio n. 3
0
    def reserve_app_id(self, username, app_id, app_language):
        """ Tells the AppController to reserve the given app_id for a particular
    user.

    Args:
      username: A str representing the app administrator's e-mail address.
      app_id: A str representing the application ID to reserve.
      app_language: The runtime (Python 2.5/2.7, Java, or Go) that the app runs
        over.
    """
        result = self.run_with_timeout(self.DEFAULT_TIMEOUT,
                                       'Reserve app id request timed out.',
                                       self.DEFAULT_NUM_RETRIES,
                                       self.server.reserve_app_id, username,
                                       app_id, app_language, self.secret)
        if result == "true":
            AppScaleLogger.log(
                "We have reserved {0} for your app".format(app_id))
        elif result == "Error: appname already exists":
            AppScaleLogger.log("We are uploading a new version of your app.")
        elif result == "Error: User not found":
            raise AppScaleException(
                "No information found about user {0}".format(username))
        else:
            AppScaleLogger.log("Result {0}".format(result))
            raise AppScaleException(result)
Esempio n. 4
0
    def remove_app(cls, options):
        """Instructs AppScale to no longer host the named application.

    Args:
      options: A Namespace that has fields for each parameter that can be
        passed in via the command-line interface.
    """
        if not options.confirm:
            response = raw_input("Are you sure you want to remove this " + \
              "application? (Y/N) ")
            if response not in ['y', 'yes', 'Y', 'YES']:
                raise AppScaleException("Cancelled application removal.")

        login_host = LocalState.get_login_host(options.keyname)
        secret = LocalState.get_secret_key(options.keyname)
        acc = AppControllerClient(login_host, secret)

        if not acc.does_app_exist(options.appname):
            raise AppScaleException(
                "The given application is not currently running.")

        acc.stop_app(options.appname)
        AppScaleLogger.log("Please wait for your app to shut down.")
        while True:
            if acc.is_app_running(options.appname):
                time.sleep(cls.SLEEP_TIME)
            else:
                break
        AppScaleLogger.success("Done shutting down {0}".format(
            options.appname))
Esempio n. 5
0
    def ensure_machine_is_compatible(cls, host, keyname, is_verbose):
        """Verifies that the specified host has AppScale installed on it.

    This also validates that the host has the right version of AppScale
    installed on it.

    Args:
      host: A str representing the host that may or may not be
        AppScale-compatible.
      keyname: A str representing the SSH keypair name that can log into the
        named host.
      is_verbose: A bool that indicates if we should print the commands we
        execute to validate the machine to stdout.
    Raises:
      AppScaleException: If the specified host does not have AppScale installed,
        or has the wrong version of AppScale installed.
    """
        # First, make sure the image is an AppScale image.
        remote_version = cls.get_host_appscale_version(host, keyname,
                                                       is_verbose)
        if not remote_version:
            raise AppScaleException("The machine at {0} does not have " \
              "AppScale installed.".format(host))

        # Make sure the remote version is compatible with the tools version.
        reduced_version = '.'.join(x for x in remote_version.split('.')[:2])
        if not APPSCALE_VERSION.startswith(reduced_version):
            raise AppScaleException(
                "The machine at {0} has AppScale {1} installed,"
                " but you are trying to use it with version {2} of the AppScale "
                "Tools. Please use the same version of the AppScale Tools that the "
                "host machine runs.".format(host, remote_version,
                                            APPSCALE_VERSION))
    def tail(self, node, file_regex):
        """'tail' provides a simple way to follow log files in an AppScale
    deployment, instead of having to ssh in to a machine, locate the logs
    directory, and then tail it.

    Args:
      node: An int that indicates the id of the machine to tail logs from.
      file_regex: The regular expression that should be used to indicate which
        logs to tail from on the remote host.
    Raises:
      AppScalefileException: If there is no AppScalefile in the current working
        directory.
      TypeError: If index is not an int.
    """
        contents = self.read_appscalefile()
        contents_as_yaml = yaml.safe_load(contents)

        # ensure that index is an int
        # TODO(cgb): Consider node = *, to tail from all nodes.
        try:
            index = int(node)
        except ValueError:
            raise TypeError("Usage: appscale tail <node id to tail from> " + \
              "<regex of files to tail>\nExample: appscale tail 0 controller*")

        # get a list of the nodes running
        if 'keyname' in contents_as_yaml:
            keyname = contents_as_yaml['keyname']
        else:
            keyname = "appscale"

        try:
            with open(self.get_locations_json_file(keyname)) as f:
                nodes_json_raw = f.read()
        except IOError:
            raise AppScaleException(
                "AppScale does not currently appear to" +
                " be running. Please start it and try again.")

        # make sure there is a node at position 'index'
        nodes = json.loads(nodes_json_raw)
        try:
            ip = nodes[index]['public_ip']
        except IndexError:
            raise AppScaleException(
                "Cannot tail from node at index " + str(index) +
                ", as there are only " + str(len(nodes)) +
                " in the currently running AppScale deployment.")

        # construct the ssh command to exec with that IP address
        tail = "tail -F /var/log/appscale/{0}".format(file_regex)
        command = [
            "ssh", "-o", "StrictHostkeyChecking=no", "-i",
            self.get_key_location(keyname), "root@" + ip, tail
        ]

        # exec the ssh command
        subprocess.call(command)
Esempio n. 7
0
    def ssh(self, node):
        """ 'ssh' provides a simple way to log into virtual machines in an AppScale
    deployment, using the SSH key provided in the user's AppScalefile.

    Args:
      node: An int that represents the node to SSH to. The value is used as an
        index into the list of nodes running in the AppScale deployment,
        starting with zero.
    Raises:
      AppScalefileException: If there is no AppScalefile in the current
        directory.
      TypeError: If the user does not provide an integer for 'node'.
    """
        contents = self.read_appscalefile()
        contents_as_yaml = yaml.safe_load(contents)

        if 'keyname' in contents_as_yaml:
            keyname = contents_as_yaml['keyname']
        else:
            keyname = "appscale"

        if node is None:
            node = "shadow"

        try:
            index = int(node)
            nodes = self.get_nodes(keyname)
            # make sure there is a node at position 'index'
            ip = nodes[index]['public_ip']
        except IndexError:
            raise AppScaleException(
                "Cannot ssh to node at index " + ", as there are only " +
                str(len(nodes)) +
                " in the currently running AppScale deployment.")
        except ValueError:
            try:
                ip = LocalState.get_host_with_role(keyname, node.lower())
            except AppScaleException:
                raise AppScaleException("No role exists by that name. "
                                        "Valid roles are {}".format(
                                            NodeLayout.ADVANCED_FORMAT_KEYS))

        # construct the ssh command to exec with that IP address
        command = [
            "ssh", "-o", "StrictHostkeyChecking=no", "-i",
            self.get_key_location(keyname), "root@" + ip
        ]

        # exec the ssh command
        try:
            subprocess.check_call(command)
        except subprocess.CalledProcessError:
            raise AppScaleException(
                "Unable to ssh to the machine at "
                "{}. Please make sure this machine is reachable, "
                "has a public ip, or that the role is in use by "
                "the deployment.".format(ip))
  def remove_app(cls, options):
    """Instructs AppScale to no longer host the named application.

    Args:
      options: A Namespace that has fields for each parameter that can be
        passed in via the command-line interface.
    """
    if not options.confirm:
      response = raw_input(
        'Are you sure you want to remove this application? (y/N) ')
      if response.lower() not in ['y', 'yes']:
        raise AppScaleException("Cancelled application removal.")

    login_host = LocalState.get_login_host(options.keyname)
    secret = LocalState.get_secret_key(options.keyname)
    acc = AppControllerClient(login_host, secret)

    if not acc.is_app_running(options.appname):
      raise AppScaleException("The given application is not currently running.")

    # Makes a call to the AppController to get all the stats and looks
    # through them for the http port the app can be reached on.
    http_port = None
    for _ in range(cls.MAX_RETRIES + 1):
      result = acc.get_all_stats()
      try:
        json_result = json.loads(result)
        apps_result = json_result['apps']
        current_app = apps_result[options.appname]
        http_port = current_app['http']
        if http_port:
          break
        time.sleep(cls.SLEEP_TIME)
      except (KeyError, ValueError):
        AppScaleLogger.verbose("Got json error from get_all_data result.",
            options.verbose)
        time.sleep(cls.SLEEP_TIME)
    if not http_port:
      raise AppScaleException(
        "Unable to get the serving port for the application.")

    acc.stop_app(options.appname)
    AppScaleLogger.log("Please wait for your app to shut down.")

    for _ in range(cls.MAX_RETRIES + 1):
      if RemoteHelper.is_port_open(login_host, http_port, options.verbose):
        time.sleep(cls.SLEEP_TIME)
        AppScaleLogger.log("Waiting for {0} to terminate...".format(
          options.appname))
      else:
        AppScaleLogger.success("Done shutting down {0}.".format(
          options.appname))
        return
    AppScaleLogger.warn("App {0} may still be running.".format(
      options.appname))
Esempio n. 9
0
    def ssh(self, node):
        """'ssh' provides a simple way to log into virtual machines in an AppScale
    deployment, using the SSH key provided in the user's AppScalefile.

    Args:
      node: An int that represents the node to SSH to. The value is used as an
        index into the list of nodes running in the AppScale deployment,
        starting with zero.
    Raises:
      AppScalefileException: If there is no AppScalefile in the current
        directory.
      TypeError: If the user does not provide an integer for 'node'.
    """
        contents = self.read_appscalefile()
        contents_as_yaml = yaml.safe_load(contents)

        # make sure the user gave us an int for node
        try:
            index = int(node)
        except ValueError:
            raise TypeError("Usage: appscale ssh <node id to ssh to>")

        # get a list of the nodes running
        if 'keyname' in contents_as_yaml:
            keyname = contents_as_yaml['keyname']
        else:
            keyname = "appscale"

        try:
            with open(self.get_locations_json_file(keyname)) as f:
                nodes_json_raw = f.read()
        except IOError as e:
            raise AppScaleException(
                "AppScale does not currently appear to" +
                " be running. Please start it and try again.")

        # make sure there is a node at position 'index'
        nodes = json.loads(nodes_json_raw)
        try:
            ip = nodes[index]['public_ip']
        except IndexError:
            raise AppScaleException(
                "Cannot ssh to node at index " + str(index) +
                ", as there are only " + str(len(nodes)) +
                " in the currently running AppScale deployment.")

        # construct the ssh command to exec with that IP address
        command = [
            "ssh", "-o", "StrictHostkeyChecking=no", "-i",
            self.get_key_location(keyname), "root@" + ip
        ]

        # exec the ssh command
        subprocess.call(command)
Esempio n. 10
0
    def upgrade(cls, options):
        """ Upgrades the deployment to the latest AppScale version.
    Args:
      options: A Namespace that has fields for each parameter that can be
        passed in via the command-line interface.
    """
        node_layout = NodeLayout(options)
        if not node_layout.is_valid():
            raise BadConfigurationException(
                'Your ips_layout is invalid:\n{}'.format(node_layout.errors()))

        latest_tools = APPSCALE_VERSION
        try:
            AppScaleLogger.log(
                'Checking if an update is available for appscale-tools')
            latest_tools = latest_tools_version()
        except:
            # Prompt the user if version metadata can't be fetched.
            if not options.test:
                response = raw_input(
                    'Unable to check for the latest version of appscale-tools. Would '
                    'you like to continue upgrading anyway? (y/N) ')
                if response.lower() not in ['y', 'yes']:
                    raise AppScaleException('Cancelled AppScale upgrade.')

        if latest_tools > APPSCALE_VERSION:
            raise AppScaleException(
                "There is a newer version ({}) of appscale-tools available. Please "
                "upgrade the tools package before running 'appscale upgrade'.".
                format(latest_tools))

        master_ip = node_layout.head_node().public_ip
        upgrade_version_available = cls.get_upgrade_version_available()

        current_version = RemoteHelper.get_host_appscale_version(
            master_ip, options.keyname, options.verbose)

        # Don't run bootstrap if current version is later that the most recent
        # public one. Covers cases of revoked versions/tags and ensures we won't
        # try to downgrade the code.
        if current_version >= upgrade_version_available:
            AppScaleLogger.log(
                'AppScale is already up to date. Skipping code upgrade.')
            AppScaleLogger.log(
                'Running upgrade script to check if any other upgrades are needed.'
            )
            cls.shut_down_appscale_if_running(options)
            cls.run_upgrade_script(options, node_layout)
            return

        cls.shut_down_appscale_if_running(options)
        cls.upgrade_appscale(options, node_layout)
Esempio n. 11
0
    def ensure_machine_is_compatible(cls, host, keyname, database, is_verbose):
        """Verifies that the specified host has AppScale installed on it.

    This also validates that the host has the right version of AppScale
    installed on it, as well as the database that the user has asked for.

    Args:
      host: A str representing the host that may or may not be
        AppScale-compatible.
      keyname: A str representing the SSH keypair name that can log into the
        named host.
      database: A str representing the database that the user wants to run
        with.
      is_verbose: A bool that indicates if we should print the commands we
        execute to validate the machine to stdout.
    Raises:
      AppScaleException: If the specified host does not have AppScale installed,
        has the wrong version of AppScale installed, or does not have the
        correct database installed.
    """
        # first, make sure the image is an appscale image
        if not cls.does_host_have_location(host, keyname, '/etc/appscale',
                                           is_verbose):
            raise AppScaleException("The machine at {0} does not have " \
              "AppScale installed.".format(host))

        # next, make sure it has the same version of appscale installed as the tools
        if not cls.does_host_have_location(
                host, keyname, '/etc/appscale/{0}'.format(APPSCALE_VERSION),
                is_verbose):
            host_version = cls.get_host_appscale_version(
                host, keyname, is_verbose)
            if host_version:
                raise AppScaleException(
                    "The machine at {0} has AppScale {1} installed,"
                    " but you are trying to use it with version {2} of the AppScale "
                    "Tools. Please use the same version of the AppScale Tools that the "
                    "host machine runs.".format(host, host_version,
                                                APPSCALE_VERSION))
            else:
                raise AppScaleException("The machine at {0} does not have AppScale "  \
                  "{1} installed.".format(host, APPSCALE_VERSION))

        # finally, make sure it has the database installed that the user requests
        if not cls.does_host_have_location(
                host, keyname, '/etc/appscale/{0}/{1}'.format(
                    APPSCALE_VERSION, database), is_verbose):
            raise AppScaleException("The machine at {0} does not have support for"  \
              " {1} installed.".format(host, database))
Esempio n. 12
0
    def spawn_nodes_in_cloud(cls, options, count=1):
        """Starts a single virtual machine in a cloud infrastructure.

    This method also prepares the virual machine for use by the AppScale Tools.

    Args:
      options: A Namespace that specifies the cloud infrastructure to use, as
        well as how to interact with that cloud.
      count: A int, the number of instances to start.
    Returns:
      The instance ID, public IP address, and private IP address of the machine
        that was started.
    """
        agent = InfrastructureAgentFactory.create_agent(options.infrastructure)
        params = agent.get_params_from_args(options)

        # If we have running instances under the current keyname, we try to
        # re-attach to them. If we have issue finding the locations file or the
        # IP of the head node, we throw an exception.
        login_ip = None
        public_ips, private_ips, instance_ids = agent.describe_instances(
            params)
        if public_ips:
            try:
                login_ip = LocalState.get_login_host(options.keyname)
            except (IOError, BadConfigurationException):
                raise AppScaleException(
                    "Couldn't get login ip for running deployment with keyname"
                    " {}.".format(options.keyname))
            if login_ip not in public_ips:
                raise AppScaleException(
                    "Couldn't recognize running instances for deployment with"
                    " keyname {}.".format(options.keyname))

        if login_ip in public_ips:
            AppScaleLogger.log("Reusing already running instances.")
            return instance_ids, public_ips, private_ips

        agent.configure_instance_security(params)
        instance_ids, public_ips, private_ips = agent.run_instances(
            count=count, parameters=params, security_configured=True)

        if options.static_ip:
            agent.associate_static_ip(params, instance_ids[0],
                                      options.static_ip)
            public_ips[0] = options.static_ip
            AppScaleLogger.log("Static IP associated with head node.")
        return instance_ids, public_ips, private_ips
Esempio n. 13
0
    def terminate_instances(cls, options):
        """Stops all services running in an AppScale deployment, and in cloud
    deployments, also powers off the instances previously spawned.

    Raises:
      AppScaleException: If AppScale is not running, and thus can't be
      terminated.
    """
        if not os.path.exists(
                LocalState.get_locations_yaml_location(options.keyname)):
            raise AppScaleException(
                "AppScale is not running with the keyname {0}".format(
                    options.keyname))

        if LocalState.get_infrastructure(options.keyname) in \
          InfrastructureAgentFactory.VALID_AGENTS:
            RemoteHelper.terminate_cloud_infrastructure(
                options.keyname, options.verbose)
        else:
            RemoteHelper.terminate_virtualized_cluster(options.keyname,
                                                       options.verbose)

        LocalState.cleanup_appscale_files(options.keyname)
        AppScaleLogger.success(
            "Successfully shut down your AppScale deployment.")
Esempio n. 14
0
    def gather_logs(cls, options):
        """Collects logs from each machine in the currently running AppScale
    deployment.

    Args:
      options: A Namespace that has fields for each parameter that can be
        passed in via the command-line interface.
    """
        # First, make sure that the place we want to store logs doesn't
        # already exist.
        if os.path.exists(options.location):
            raise AppScaleException("Can't gather logs, as the location you " + \
              "specified, {0}, already exists.".format(options.location))

        acc = AppControllerClient(LocalState.get_login_host(options.keyname),
                                  LocalState.get_secret_key(options.keyname))

        # do the mkdir after we get the secret key, so that a bad keyname will
        # cause the tool to crash and not create this directory
        os.mkdir(options.location)

        for ip in acc.get_all_public_ips():
            # Get the logs from each node, and store them in our local directory
            local_dir = "{0}/{1}".format(options.location, ip)
            os.mkdir(local_dir)
            RemoteHelper.scp_remote_to_local(ip, options.keyname,
                                             '/var/log/appscale', local_dir,
                                             options.verbose)
        AppScaleLogger.success("Successfully copied logs to {0}".format(
            options.location))
Esempio n. 15
0
  def get_serving_info(self, app_id, keyname):
    """Finds out what host and port are used to host the named application.

    Args:
      app_id: The application that we should find a serving URL for.
    Returns:
      A tuple containing the host and port where the application is serving
        traffic from.
    """
    total_wait_time = 0
    # first, wait for the app to start serving
    sleep_time = self.STARTING_SLEEP_TIME
    while True:
      if self.does_app_exist(app_id):
        break
      else:
        AppScaleLogger.log("Waiting {0} second(s) to check on application...".\
          format(sleep_time))
        time.sleep(sleep_time)
        sleep_time = min(sleep_time * 2, self.MAX_SLEEP_TIME)
        total_wait_time += sleep_time
      if total_wait_time > self.MAX_WAIT_TIME:
        raise AppScaleException("App took too long to upload")
    # next, get the serving host and port
    app_data = self.server.get_app_data(app_id, self.secret)
    host = LocalState.get_login_host(keyname)
    port = int(re.search(".*\sports: (\d+)[\s|:]", app_data).group(1))
    return host, port
 def confirm_or_abort(cls, message):
     AppScaleLogger.warn(message)
     confirm = raw_input("Are you sure you want to do this? (Y/N) ")
     if confirm.lower() == 'y' or confirm.lower() == 'yes':
         return
     else:
         raise AppScaleException('AppScale termination was cancelled.')
Esempio n. 17
0
  def register(self, deployment_id):
    """ Allows users to register their AppScale deployment with the AppScale
    Portal.

    Raises:
      AppScaleException: If the deployment has already been registered.
    """
    appscale_yaml = yaml.safe_load(self.read_appscalefile())
    if 'keyname' in appscale_yaml:
      keyname = appscale_yaml['keyname']
    else:
      keyname = 'appscale'

    nodes = self.get_nodes(keyname)
    head_node = self.get_head_node(nodes)
    if RegistrationHelper.appscale_has_deployment_id(head_node, keyname):
      existing_id = RegistrationHelper.get_deployment_id(head_node, keyname)
      if existing_id != deployment_id:
        raise AppScaleException(
          'This deployment has already been registered with a different ID.')

    if 'infrastructure' in appscale_yaml:
      deployment_type = 'cloud'
    else:
      deployment_type = 'cluster'

    deployment = RegistrationHelper.update_deployment(deployment_type, nodes,
      deployment_id)

    RegistrationHelper.set_deployment_id(head_node, keyname, deployment_id)

    AppScaleLogger.success(
      'Registration complete for AppScale deployment {0}.'
      .format(deployment['name']))
Esempio n. 18
0
    def terminate_instances(cls, options):
        """Stops all services running in an AppScale deployment, and in cloud
    deployments, also powers off the instances previously spawned.

    Raises:
      AppScaleException: If AppScale is not running, and thus can't be
      terminated.
    """
        if not os.path.exists(
                LocalState.get_secret_key_location(options.keyname)):
            raise AppScaleException(
                "AppScale is not running with the keyname {0}".format(
                    options.keyname))

        infrastructure = LocalState.get_infrastructure(options.keyname)

        # If the user is on a cloud deployment, and not backing their data to
        # persistent disks, warn them before shutting down AppScale.
        # Also, if we're in developer mode, skip the warning.
        if infrastructure != "xen" and not LocalState.are_disks_used(
                options.keyname) and not options.test:
            LocalState.ensure_user_wants_to_terminate()

        if infrastructure in InfrastructureAgentFactory.VALID_AGENTS:
            RemoteHelper.terminate_cloud_infrastructure(
                options.keyname, options.verbose)
        else:
            RemoteHelper.terminate_virtualized_cluster(options.keyname,
                                                       options.verbose)

        LocalState.cleanup_appscale_files(options.keyname)
        AppScaleLogger.success(
            "Successfully shut down your AppScale deployment.")
Esempio n. 19
0
    def get_app_admin(self, app_id):
        """ Queries the AppController to see which user owns the given application.

    Args:
      app_id: The name of the app that we should see the administrator on.
    Returns:
      A str containing the name of the application's administrator, or None
        if there is none.
    Raises:
      AppScaleException if the AppController returns an error.
    """
        app_data_json = self.run_with_timeout(
            self.DEFAULT_TIMEOUT, 'Get app admin request timed out.',
            self.DEFAULT_NUM_RETRIES, self.server.get_app_data, app_id,
            self.secret)
        if not app_data_json:
            return None

        try:
            app_data = json.loads(app_data_json)
        except ValueError as decode_error:
            if 'Error:' in app_data_json:
                raise AppScaleException(app_data_json)
            raise decode_error

        if 'owner' not in app_data:
            return None

        return app_data['owner']
Esempio n. 20
0
    def copy_deployment_credentials(cls, host, options):
        """Copies credentials needed to start the AppController and have it create
    other instances (in cloud deployments).

    Args:
      host: A str representing the machine (reachable from this computer) to
        copy our deployment credentials to.
      options: A Namespace that indicates which SSH keypair to use, and whether
        or not we are running in a cloud infrastructure.
    """
        local_secret_key = LocalState.get_secret_key_location(options.keyname)
        cls.scp(host, options.keyname, local_secret_key,
                '{}/secret.key'.format(cls.CONFIG_DIR), options.verbose)

        local_ssh_key = LocalState.get_key_path_from_name(options.keyname)
        cls.scp(host, options.keyname, local_ssh_key,
                '{}/ssh.key'.format(cls.CONFIG_DIR), options.verbose)

        LocalState.generate_ssl_cert(options.keyname, options.verbose)

        local_cert = LocalState.get_certificate_location(options.keyname)
        cls.scp(host, options.keyname, local_cert,
                '{}/certs/mycert.pem'.format(cls.CONFIG_DIR), options.verbose)

        local_private_key = LocalState.get_private_key_location(
            options.keyname)
        cls.scp(host, options.keyname, local_private_key,
                '{}/certs/mykey.pem'.format(cls.CONFIG_DIR), options.verbose)

        hash_id = subprocess.Popen([
            "openssl", "x509", "-hash", "-noout", "-in",
            LocalState.get_certificate_location(options.keyname)
        ],
                                   stdout=subprocess.PIPE).communicate()[0]
        symlink_cert = 'ln -fs {}/certs/mycert.pem /etc/ssl/certs/{}.0'.\
          format(cls.CONFIG_DIR, hash_id.rstrip())
        cls.ssh(host, options.keyname, symlink_cert, options.verbose)

        # In Google Compute Engine, we also need to copy over our client_secrets
        # file and the OAuth2 file that the user has approved for use with their
        # credentials, otherwise the AppScale VMs won't be able to interact with
        # GCE.
        if options.infrastructure and options.infrastructure == 'gce':
            secrets_location = LocalState.get_client_secrets_location(
                options.keyname)
            if not os.path.exists(secrets_location):
                raise AppScaleException(
                    '{} does not exist.'.format(secrets_location))
            secrets_type = GCEAgent.get_secrets_type(secrets_location)
            cls.scp(host, options.keyname, secrets_location,
                    '{}/client_secrets.json'.format(cls.CONFIG_DIR),
                    options.verbose)
            if secrets_type == CredentialTypes.OAUTH:
                local_oauth = LocalState.get_oauth2_storage_location(
                    options.keyname)
                cls.scp(host, options.keyname, local_oauth,
                        '{}/oauth2.dat'.format(cls.CONFIG_DIR),
                        options.verbose)
Esempio n. 21
0
  def add_keypair(cls, options):
    """Sets up passwordless SSH login to the machines used in a virtualized
    cluster deployment.

    Args:
      options: A Namespace that has fields for each parameter that can be
        passed in via the command-line interface.
    Raises:
      AppScaleException: If any of the machines named in the ips_layout are
        not running, or do not have the SSH daemon running.
    """
    LocalState.require_ssh_commands(options.auto, options.verbose)
    LocalState.make_appscale_directory()

    path = LocalState.LOCAL_APPSCALE_PATH + options.keyname
    if options.add_to_existing:
      public_key = path + ".pub"
      private_key = path
    else:
      public_key, private_key = LocalState.generate_rsa_key(options.keyname,
        options.verbose)

    if options.auto:
      if 'root_password' in options:
        AppScaleLogger.log("Using the provided root password to log into " + \
          "your VMs.")
        password = options.root_password
      else:
        AppScaleLogger.log("Please enter the password for the root user on" + \
          " your VMs:")
        password = getpass.getpass()

    node_layout = NodeLayout(options)
    if not node_layout.is_valid():
      raise BadConfigurationException("There were problems with your " + \
        "placement strategy: " + str(node_layout.errors()))

    all_ips = [node.public_ip for node in node_layout.nodes]
    for ip in all_ips:
      # first, make sure ssh is actually running on the host machine
      if not RemoteHelper.is_port_open(ip, RemoteHelper.SSH_PORT,
        options.verbose):
        raise AppScaleException("SSH does not appear to be running at {0}. " \
          "Is the machine at {0} up and running? Make sure your IPs are " \
          "correct!".format(ip))

      # next, set up passwordless ssh
      AppScaleLogger.log("Executing ssh-copy-id for host: {0}".format(ip))
      if options.auto:
        LocalState.shell("{0} root@{1} {2} {3}".format(cls.EXPECT_SCRIPT, ip,
          private_key, password), options.verbose)
      else:
        LocalState.shell("ssh-copy-id -i {0} root@{1}".format(private_key, ip),
          options.verbose)

    AppScaleLogger.success("Generated a new SSH key for this deployment " + \
      "at {0}".format(private_key))
Esempio n. 22
0
    def terminate_instances(cls, options):
        """Stops all services running in an AppScale deployment, and in cloud
    deployments, also powers off the instances previously spawned.

    Raises:
      AppScaleException: If AppScale is not running, and thus can't be
      terminated.
    """
        try:
            infrastructure = LocalState.get_infrastructure(options.keyname)
        except IOError:
            raise AppScaleException(
                "Cannot find AppScale's configuration for keyname {0}".format(
                    options.keyname))

        if infrastructure == "xen" and options.terminate:
            raise AppScaleException(
                "Terminate option is invalid for cluster mode.")

        if infrastructure == "xen" or not options.terminate:
            # We are in cluster mode: let's check if AppScale is running.
            if not os.path.exists(
                    LocalState.get_secret_key_location(options.keyname)):
                raise AppScaleException(
                    "AppScale is not running with the keyname {0}".format(
                        options.keyname))

        # Stop gracefully the AppScale deployment.
        try:
            RemoteHelper.terminate_virtualized_cluster(options.keyname,
                                                       options.clean,
                                                       options.verbose)
        except (IOError, AppScaleException):
            # Don't fail if we cannot find the configuration.
            pass

        # And if we are on a cloud infrastructure, terminate instances if
        # asked.
        if (infrastructure in InfrastructureAgentFactory.VALID_AGENTS
                and options.terminate):
            RemoteHelper.terminate_cloud_infrastructure(
                options.keyname, options.verbose)
        if options.clean:
            LocalState.clean_local_metadata(keyname=options.keyname)
Esempio n. 23
0
  def reserve_app_id(self, username, app_id, app_language):
    """Tells the UserAppServer to reserve the given app_id for a particular
    user.

    Args:
      username: A str representing the app administrator's e-mail address.
      app_id: A str representing the application ID to reserve.
      app_language: The runtime (Python 2.5/2.7, Java, or Go) that the app runs
        over.
    """
    result = self.server.commit_new_app(app_id, username, app_language,
      self.secret)
    if result == "true":
      AppScaleLogger.log("We have reserved {0} for your app".format(app_id))
    elif result == "Error: appname already exist":
      AppScaleLogger.log("We are uploading a new version of your app.")
    elif result == "Error: User not found":
      raise AppScaleException("No information found about user {0}" \
        .format(username))
    else:
      raise AppScaleException(result)
Esempio n. 24
0
    def get_head_node(self, nodes):
        """ Retrieve a node with the 'shadow' role.

    Args:
      nodes: A list of nodes in the running AppScale deployment.
    Returns:
      A string containing the IP address of the head node.
    """
        for node in nodes:
            if 'shadow' in node['jobs']:
                return node['public_ip']

        raise AppScaleException('Unable to find head node.')
Esempio n. 25
0
    def relocate_app(cls, options):
        """Instructs AppScale to move the named application to a different port.

    Args:
      options: A Namespace that has fields for each parameter that can be passed
        in via the command-line interface.
    Raises:
      AppScaleException: If the named application isn't running in this AppScale
        cloud, if the destination port is in use by a different application, or
        if the AppController rejects the request to relocate the application (in
        which case it includes the reason why the rejection occurred).
    """
        login_host = LocalState.get_login_host(options.keyname)
        acc = AppControllerClient(login_host,
                                  LocalState.get_secret_key(options.keyname))

        app_info_map = acc.get_app_info_map()
        if options.appname not in app_info_map.keys():
            raise AppScaleException("The given application, {0}, is not currently " \
              "running in this AppScale cloud, so we can't move it to a different " \
              "port.".format(options.appname))

        relocate_result = acc.relocate_app(options.appname, options.http_port,
                                           options.https_port)
        if relocate_result == "OK":
            AppScaleLogger.success("Successfully issued request to move {0} to " \
              "ports {1} and {2}.".format(options.appname, options.http_port,
              options.https_port))
            RemoteHelper.sleep_until_port_is_open(login_host,
                                                  options.http_port,
                                                  options.verbose)
            AppScaleLogger.success(
                "Your app serves unencrypted traffic at: " +
                "http://{0}:{1}".format(login_host, options.http_port))
            AppScaleLogger.success(
                "Your app serves encrypted traffic at: " +
                "https://{0}:{1}".format(login_host, options.https_port))
        else:
            raise AppScaleException(relocate_result)
Esempio n. 26
0
    def confirm_or_abort(cls, message):
        """ Displays confirmation message and collects user's choice.

    Args:
      message: A str, the message to be displayed.
    Raises:
      AppScaleException: If the user chooses to terminate AppScale.
    """
        AppScaleLogger.warn(message)
        confirm = raw_input("Are you sure you want to do this? (Y/N) ")
        if confirm.lower() == 'y' or confirm.lower() == 'yes':
            return
        else:
            raise AppScaleException('AppScale termination was cancelled.')
    def update_deployment(cls, deployment_type, nodes, deployment_id):
        """ Updates the deployment on the AppScale Portal.

    Args:
      deployment_type: A string designating the type of deployment.
      nodes: A list containing the nodes layout.
      deployment_id: A string containing the deployment ID.
    Returns:
      A dictionary containing the updated deployment info.
    Raises:
      AppScaleException if the deployment ID is invalid or already registered.
    """
        # Remove unneeded info from node layout.
        for node in nodes:
            if 'ssh_key' in node:
                del node['ssh_key']

        deployment_data = urllib.urlencode({
            'deployment_type': deployment_type,
            'node_layout': json.dumps(nodes),
            'appscale_version': APPSCALE_VERSION
        })
        try:
            response = urllib2.urlopen(
                cls.DEPLOYMENTS_URL.format(deployment_id),
                data=deployment_data)
            deployment = response.read()
            return json.loads(deployment)
        except urllib2.HTTPError as error:
            if error.code == cls.HTTP_NOTFOUND:
                raise AppScaleException('This deployment ID does not exist.')
            if error.code == cls.HTTP_METHODNOTALLOWED:
                raise AppScaleException(
                    'This feature is currently unavailable.')
            if error.code == cls.HTTP_BADREQUEST:
                raise AppScaleException(error.read())
Esempio n. 28
0
    def get_host_with_role(cls, keyname, role):
        """Searches through the local metadata to see which virtual machine runs the
    specified role.

    Args:
      keyname: The SSH keypair name that uniquely identifies this AppScale
        deployment.
      role: A str indicating the role to search for.
    Returns:
      A str containing the host that runs the specified service.
    """
        nodes = cls.get_local_nodes_info(keyname)
        for node in nodes:
            if role in node['jobs']:
                return node['public_ip']
        raise AppScaleException("Couldn't find a {0} node.".format(role))
Esempio n. 29
0
  def get_nodes(self, keyname):
    """ Retrieve a list of the running nodes.

    Args:
      keyname: An identifier for the AppScale deployment.
    Returns:
      A list of nodes in the running AppScale deployment.
    Raises:
      AppScaleException: If there is no locations JSON file.
    """
    try:
      with open(self.get_locations_json_file(keyname)) as locations_file:
        return json.loads(locations_file.read())
    except IOError:
      raise AppScaleException("AppScale does not currently appear to"
        " be running. Please start it and try again.")
Esempio n. 30
0
    def upload_app(cls, options):
        """Uploads the given App Engine application into AppScale.

    Args:
      options: A Namespace that has fields for each parameter that can be
        passed in via the command-line interface.
    Returns:
      A tuple containing the host and port where the application is serving
        traffic from.
    """
        if cls.TAR_GZ_REGEX.search(options.file):
            file_location = LocalState.extract_tgz_app_to_dir(
                options.file, options.verbose)
            created_dir = True
        elif cls.ZIP_REGEX.search(options.file):
            file_location = LocalState.extract_zip_app_to_dir(
                options.file, options.verbose)
            created_dir = True
        elif os.path.isdir(options.file):
            file_location = options.file
            created_dir = False
        else:
            raise AppEngineConfigException('{0} is not a tar.gz file, a zip file, ' \
              'or a directory. Please try uploading either a tar.gz file, a zip ' \
              'file, or a directory.'.format(options.file))

        try:
            app_id = AppEngineHelper.get_app_id_from_app_config(file_location)
        except AppEngineConfigException as config_error:
            AppScaleLogger.log(config_error)
            if 'yaml' in str(config_error):
                raise config_error

            # Java App Engine users may have specified their war directory. In that
            # case, just move up one level, back to the app's directory.
            file_location = file_location + os.sep + ".."
            app_id = AppEngineHelper.get_app_id_from_app_config(file_location)

        app_language = AppEngineHelper.get_app_runtime_from_app_config(
            file_location)
        AppEngineHelper.validate_app_id(app_id)

        if app_language == 'java':
            if AppEngineHelper.is_sdk_mismatch(file_location):
                AppScaleLogger.warn(
                    'AppScale did not find the correct SDK jar ' +
                    'versions in your app. The current supported ' +
                    'SDK version is ' + AppEngineHelper.SUPPORTED_SDK_VERSION +
                    '.')

        login_host = LocalState.get_login_host(options.keyname)
        secret_key = LocalState.get_secret_key(options.keyname)
        acc = AppControllerClient(login_host, secret_key)

        if options.test:
            username = LocalState.DEFAULT_USER
        elif options.email:
            username = options.email
        else:
            username = LocalState.get_username_from_stdin(is_admin=False)

        if not acc.does_user_exist(username):
            password = LocalState.get_password_from_stdin()
            RemoteHelper.create_user_accounts(username,
                                              password,
                                              login_host,
                                              options.keyname,
                                              clear_datastore=False)

        app_exists = acc.does_app_exist(app_id)
        app_admin = acc.get_app_admin(app_id)
        if app_admin is not None and username != app_admin:
            raise AppScaleException("The given user doesn't own this application" + \
              ", so they can't upload an app with that application ID. Please " + \
              "change the application ID and try again.")

        if app_exists:
            AppScaleLogger.log(
                "Uploading new version of app {0}".format(app_id))
        else:
            AppScaleLogger.log(
                "Uploading initial version of app {0}".format(app_id))
            acc.reserve_app_id(username, app_id, app_language)

        # Ignore all .pyc files while tarring.
        if app_language == 'python27':
            AppScaleLogger.log("Ignoring .pyc files")

        remote_file_path = RemoteHelper.copy_app_to_host(
            file_location, options.keyname, options.verbose)

        acc.done_uploading(app_id, remote_file_path)
        acc.update([app_id])

        # now that we've told the AppController to start our app, find out what port
        # the app is running on and wait for it to start serving
        AppScaleLogger.log("Please wait for your app to start serving.")

        if app_exists:
            time.sleep(20)  # give the AppController time to restart the app

        # Makes a call to the AppController to get all the stats and looks
        # through them for the http port the app can be reached on.
        sleep_time = 2 * cls.SLEEP_TIME
        current_app = None
        for i in range(cls.MAX_RETRIES):
            try:
                result = acc.get_all_stats()
                json_result = json.loads(result)
                apps_result = json_result['apps']
                current_app = apps_result[app_id]
                http_port = current_app['http']
                break
            except ValueError:
                pass
            except KeyError:
                pass
            AppScaleLogger.verbose("Waiting {0} second(s) for a port to be assigned to {1}".\
              format(sleep_time, app_id), options.verbose)
            time.sleep(sleep_time)
        if not current_app:
            raise AppScaleException(
                "Unable to get the serving port for the application.")

        RemoteHelper.sleep_until_port_is_open(login_host, http_port,
                                              options.verbose)
        AppScaleLogger.success(
            "Your app can be reached at the following URL: " +
            "http://{0}:{1}".format(login_host, http_port))

        if created_dir:
            shutil.rmtree(file_location)

        return (login_host, http_port)