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']))
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) acc = AppControllerClient(login_host, LocalState.get_secret_key( options.keyname)) userappserver_host = acc.get_uaserver_host(options.verbose) userappclient = UserAppClient(userappserver_host, LocalState.get_secret_key( options.keyname)) if not userappclient.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))
def add_instances(cls, options): """Adds additional machines to an AppScale deployment. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. """ if 'master' in options.ips.keys(): raise BadConfigurationException("Cannot add master nodes to an " + \ "already running AppScale deployment.") # Skip checking for -n (replication) because we don't allow the user # to specify it here (only allowed in run-instances). additional_nodes_layout = NodeLayout(options) # In virtualized cluster deployments, we need to make sure that the user # has already set up SSH keys. if LocalState.get_from_yaml(options.keyname, 'infrastructure') == "xen": for ip in options.ips.values(): # throws a ShellException if the SSH key doesn't work RemoteHelper.ssh(ip, options.keyname, "ls", options.verbose) # Finally, find an AppController and send it a message to add # the given nodes with the new roles. AppScaleLogger.log("Sending request to add instances") login_ip = LocalState.get_login_host(options.keyname) acc = AppControllerClient(login_ip, LocalState.get_secret_key(options.keyname)) acc.start_roles_on_nodes(json.dumps(options.ips)) # TODO(cgb): Should we wait for the new instances to come up and get # initialized? AppScaleLogger.success("Successfully sent request to add instances " + \ "to this AppScale deployment.")
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.")
def add_instances(cls, options): """Adds additional machines to an AppScale deployment. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. """ if 'master' in options.ips.keys(): raise BadConfigurationException("Cannot add master nodes to an " + \ "already running AppScale deployment.") # Skip checking for -n (replication) because we don't allow the user # to specify it here (only allowed in run-instances). additional_nodes_layout = NodeLayout(options) # In virtualized cluster deployments, we need to make sure that the user # has already set up SSH keys. if LocalState.get_from_yaml(options.keyname, 'infrastructure') == "xen": for ip in options.ips.values(): # throws a ShellException if the SSH key doesn't work RemoteHelper.ssh(ip, options.keyname, "ls", options.verbose) # Finally, find an AppController and send it a message to add # the given nodes with the new roles. AppScaleLogger.log("Sending request to add instances") login_ip = LocalState.get_login_host(options.keyname) acc = AppControllerClient(login_ip, LocalState.get_secret_key( options.keyname)) acc.start_roles_on_nodes(json.dumps(options.ips)) # TODO(cgb): Should we wait for the new instances to come up and get # initialized? AppScaleLogger.success("Successfully sent request to add instances " + \ "to this AppScale deployment.")
def describe_instances(cls, options): """Queries each node in the currently running AppScale deployment and reports on their status. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. """ login_host = LocalState.get_login_host(options.keyname) login_acc = AppControllerClient( login_host, LocalState.get_secret_key(options.keyname)) for ip in login_acc.get_all_public_ips(): acc = AppControllerClient( ip, LocalState.get_secret_key(options.keyname)) AppScaleLogger.log("Status of node at {0}:".format(ip)) try: AppScaleLogger.log(acc.get_status()) except Exception as exception: AppScaleLogger.warn("Unable to contact machine: {0}\n".format( str(exception))) AppScaleLogger.success("View status information about your AppScale " + \ "deployment at http://{0}:{1}/status".format(login_host, RemoteHelper.APP_DASHBOARD_PORT))
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))
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))
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.")
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.")
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))
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. """ 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, 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) # next, copy over the ssh keypair we generate RemoteHelper.scp(ip, options.keyname, public_key, '/root/.ssh/id_rsa.pub', options.verbose) RemoteHelper.scp(ip, options.keyname, private_key, '/root/.ssh/id_rsa', options.verbose) AppScaleLogger.success("Generated a new SSH key for this deployment " + \ "at {0}".format(private_key))
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))
def run_bootstrap(cls, ip, options, error_ips): try: RemoteHelper.ssh(ip, options.keyname, cls.BOOTSTRAP_CMD, options.verbose) AppScaleLogger.success( 'Successfully updated and built AppScale on {}'.format(ip)) except ShellException: error_ips.append(ip) AppScaleLogger.warn('Unable to upgrade AppScale code on {}.\n' 'Please correct any errors listed in /var/log/appscale/bootstrap.log ' 'on that machine and re-run appscale upgrade.'.format(ip)) return error_ips
def run_bootstrap(cls, ip, options, error_ips): try: RemoteHelper.ssh(ip, options.keyname, cls.BOOTSTRAP_CMD, options.verbose) AppScaleLogger.success("Successfully updated and built AppScale on {}".format(ip)) except ShellException: error_ips.append(ip) AppScaleLogger.warn( "Unable to upgrade AppScale code on {}.\n" "Please correct any errors listed in /var/log/appscale/bootstrap.log " "on that machine and re-run appscale upgrade.".format(ip) ) return error_ips
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)) try: all_ips = acc.get_all_public_ips() except socket.error: # Occurs when the AppController has failed. AppScaleLogger.warn("Couldn't get an up-to-date listing of the " + \ "machines in this AppScale deployment. Using our locally cached " + \ "info instead.") all_ips = LocalState.get_all_public_ips(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 all_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) try: RemoteHelper.scp_remote_to_local(ip, options.keyname, '/var/log/cassandra', local_dir, options.verbose) except ShellException: pass try: RemoteHelper.scp_remote_to_local(ip, options.keyname, '/var/log/zookeeper', local_dir, options.verbose) except ShellException: pass RemoteHelper.scp_remote_to_local(ip, options.keyname, '/var/log/kern.log', local_dir, options.verbose) RemoteHelper.scp_remote_to_local(ip, options.keyname, '/var/log/syslog', local_dir, options.verbose) AppScaleLogger.success("Successfully copied logs to {0}".format( options.location))
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. """ 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, 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) # next, copy over the ssh keypair we generate RemoteHelper.scp(ip, options.keyname, public_key, '/root/.ssh/id_rsa.pub', options.verbose) RemoteHelper.scp(ip, options.keyname, private_key, '/root/.ssh/id_rsa', options.verbose) AppScaleLogger.success("Generated a new SSH key for this deployment " + \ "at {0}".format(private_key))
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))
def set_property(cls, options): """Instructs AppScale to replace the value it uses for a particular AppController instance variable (property) with a new value. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. """ shadow_host = LocalState.get_host_with_role(options.keyname, "shadow") acc = AppControllerClient(shadow_host, LocalState.get_secret_key(options.keyname)) result = acc.set_property(options.property_name, options.property_value) if result == "OK": AppScaleLogger.success("Successfully updated the given property.") else: raise AppControllerException("Unable to update the given property " + "because: {0}".format(result))
def set_property(cls, options): """Instructs AppScale to replace the value it uses for a particular AppController instance variable (property) with a new value. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. """ shadow_host = LocalState.get_host_with_role(options.keyname, 'shadow') acc = AppControllerClient(shadow_host, LocalState.get_secret_key( options.keyname)) result = acc.set_property(options.property_name, options.property_value) if result == 'OK': AppScaleLogger.success("Successfully updated the given property.") else: raise AppControllerException("Unable to update the given property " + "because: {0}".format(result))
def clean(self): """'clean' provides a mechanism that will forcefully shut down all AppScale- related services on virtual machines in a cluster deployment. Returns: A list of the IP addresses where AppScale was shut down. Raises: AppScalefileException: If there is no AppScalefile in the current working directory. BadConfigurationException: If this method is invoked and the AppScalefile indicates that a cloud deployment is being used. """ contents = self.read_appscalefile() contents_as_yaml = yaml.safe_load(contents) if 'ips_layout' not in contents_as_yaml: raise BadConfigurationException("Cannot use 'appscale clean' in a " \ "cloud deployment.") if 'verbose' in contents_as_yaml and contents_as_yaml[ 'verbose'] == True: is_verbose = contents_as_yaml['verbose'] else: is_verbose = False if 'keyname' in contents_as_yaml: keyname = contents_as_yaml['keyname'] else: keyname = 'appscale' all_ips = self.get_all_ips(contents_as_yaml["ips_layout"]) for ip in all_ips: RemoteHelper.ssh(ip, keyname, self.TERMINATE, is_verbose) try: LocalState.cleanup_appscale_files(keyname) except Exception: pass AppScaleLogger.success( "Successfully shut down your AppScale deployment.") return all_ips
def clean(self): """'clean' provides a mechanism that will forcefully shut down all AppScale- related services on virtual machines in a cluster deployment. Returns: A list of the IP addresses where AppScale was shut down. Raises: AppScalefileException: If there is no AppScalefile in the current working directory. BadConfigurationException: If this method is invoked and the AppScalefile indicates that a cloud deployment is being used. """ contents = self.read_appscalefile() contents_as_yaml = yaml.safe_load(contents) if 'ips_layout' not in contents_as_yaml: raise BadConfigurationException("Cannot use 'appscale clean' in a " \ "cloud deployment.") if 'verbose' in contents_as_yaml and contents_as_yaml['verbose'] == True: is_verbose = contents_as_yaml['verbose'] else: is_verbose = False if 'keyname' in contents_as_yaml: keyname = contents_as_yaml['keyname'] else: keyname = 'appscale' all_ips = self.get_all_ips(contents_as_yaml["ips_layout"]) for ip in all_ips: RemoteHelper.ssh(ip, keyname, self.TERMINATE, is_verbose) try: LocalState.cleanup_appscale_files(keyname) except Exception: pass AppScaleLogger.success("Successfully shut down your AppScale deployment.") return all_ips
def reset_password(cls, options): """Resets a user's password 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. """ secret = LocalState.get_secret_key(options.keyname) username, password = LocalState.get_credentials(is_admin=False) encrypted_password = LocalState.encrypt_password(username, password) uac = UserAppClient(LocalState.get_login_host(options.keyname), secret) try: uac.change_password(username, encrypted_password) AppScaleLogger.success("The password was successfully changed for the " "given user.") except Exception as exception: AppScaleLogger.warn( "Could not change the user's password for the " + "following reason: {0}".format(str(exception)) ) sys.exit(1)
def reset_password(cls, options): """Resets a user's password 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. """ secret = LocalState.get_secret_key(options.keyname) username, password = LocalState.get_credentials(is_admin=False) encrypted_password = LocalState.encrypt_password(username, password) uac = UserAppClient(LocalState.get_login_host(options.keyname), secret) try: uac.change_password(username, encrypted_password) AppScaleLogger.success("The password was successfully changed for the " \ "given user.") except Exception as exception: AppScaleLogger.warn("Could not change the user's password for the " + \ "following reason: {0}".format(str(exception))) sys.exit(1)
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)) 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)
def describe_instances(cls, options): """Queries each node in the currently running AppScale deployment and reports on their status. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. """ login_host = LocalState.get_login_host(options.keyname) login_acc = AppControllerClient(login_host, LocalState.get_secret_key(options.keyname)) for ip in login_acc.get_all_public_ips(): acc = AppControllerClient(ip, LocalState.get_secret_key(options.keyname)) AppScaleLogger.log("Status of node at {0}:".format(ip)) try: AppScaleLogger.log(acc.get_status()) except Exception as e: AppScaleLogger.warn("Unable to contact machine: {0}\n".format(str(e))) AppScaleLogger.success("View status information about your AppScale " + \ "deployment at http://{0}/status".format(login_host))
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.")
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)
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_app_to_dir( options.file, options.verbose) created_dir = True else: file_location = options.file created_dir = False 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) acc = AppControllerClient(LocalState.get_login_host(options.keyname), LocalState.get_secret_key(options.keyname)) userappserver_host = acc.get_uaserver_host(options.verbose) userappclient = UserAppClient( userappserver_host, LocalState.get_secret_key(options.keyname)) 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 userappclient.does_user_exist(username): password = LocalState.get_password_from_stdin() RemoteHelper.create_user_accounts(username, password, userappserver_host, options.keyname) app_exists = userappclient.does_app_exist(app_id) app_admin = userappclient.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)) userappclient.reserve_app_id(username, app_id, app_language) 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 serving_host, serving_port = userappclient.get_serving_info( app_id, options.keyname) RemoteHelper.sleep_until_port_is_open(serving_host, serving_port, options.verbose) AppScaleLogger.success( "Your app can be reached at the following URL: " + "http://{0}:{1}".format(serving_host, serving_port)) if created_dir: shutil.rmtree(file_location) return (serving_host, serving_port)
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)
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. """ if cls.TAR_GZ_REGEX.search(options.file): file_location = LocalState.extract_app_to_dir(options.file, options.verbose) created_dir = True else: file_location = options.file created_dir = False 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) acc = AppControllerClient(LocalState.get_login_host(options.keyname), LocalState.get_secret_key(options.keyname)) userappserver_host = acc.get_uaserver_host(options.verbose) userappclient = UserAppClient(userappserver_host, LocalState.get_secret_key( options.keyname)) 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 userappclient.does_user_exist(username): password = LocalState.get_password_from_stdin() RemoteHelper.create_user_accounts(username, password, userappserver_host, options.keyname) if userappclient.does_app_exist(app_id): raise AppScaleException("The given application is already running in " + \ "AppScale. Please choose a different application ID or use " + \ "appscale-remove-app to take down the existing application.") app_admin = userappclient.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.") AppScaleLogger.log("Uploading {0}".format(app_id)) userappclient.reserve_app_id(username, app_id, app_language) 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.") serving_host, serving_port = userappclient.get_serving_info(app_id, options.keyname) RemoteHelper.sleep_until_port_is_open(serving_host, serving_port, options.verbose) AppScaleLogger.success("Your app can be reached at the following URL: " + "http://{0}:{1}".format(serving_host, serving_port)) if created_dir: shutil.rmtree(file_location)
def run_instances(cls, options): """Starts a new AppScale deployment with the parameters given. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. Raises: BadConfigurationException: If the user passes in options that are not sufficient to start an AppScale deplyoment (e.g., running on EC2 but not specifying the AMI to use), or if the user provides us contradictory options (e.g., running on EC2 but not specifying EC2 credentials). """ LocalState.make_appscale_directory() LocalState.ensure_appscale_isnt_running(options.keyname, options.force) if options.infrastructure: AppScaleLogger.log("Starting AppScale " + APPSCALE_VERSION + " over the " + options.infrastructure + " cloud.") else: AppScaleLogger.log("Starting AppScale " + APPSCALE_VERSION + " over a virtualized cluster.") my_id = str(uuid.uuid4()) AppScaleLogger.remote_log_tools_state(options, my_id, "started", APPSCALE_VERSION) node_layout = NodeLayout(options) if not node_layout.is_valid(): raise BadConfigurationException("There were errors with your " + \ "placement strategy:\n{0}".format(str(node_layout.errors()))) if not node_layout.is_supported(): AppScaleLogger.warn("Warning: This deployment strategy is not " + \ "officially supported.") public_ip, instance_id = RemoteHelper.start_head_node(options, my_id, node_layout) AppScaleLogger.log("\nPlease wait for AppScale to prepare your machines " + "for use.") # Write our metadata as soon as possible to let users SSH into those # machines via 'appscale ssh' LocalState.update_local_metadata(options, node_layout, public_ip, instance_id) RemoteHelper.copy_local_metadata(public_ip, options.keyname, options.verbose) acc = AppControllerClient(public_ip, LocalState.get_secret_key( options.keyname)) uaserver_host = acc.get_uaserver_host(options.verbose) RemoteHelper.sleep_until_port_is_open(uaserver_host, UserAppClient.PORT, options.verbose) # Update our metadata again so that users can SSH into other boxes that # may have been started. LocalState.update_local_metadata(options, node_layout, public_ip, instance_id) RemoteHelper.copy_local_metadata(public_ip, options.keyname, options.verbose) AppScaleLogger.log("UserAppServer is at {0}".format(uaserver_host)) uaserver_client = UserAppClient(uaserver_host, LocalState.get_secret_key(options.keyname)) if options.admin_user and options.admin_pass: AppScaleLogger.log("Using the provided admin username/password") username, password = options.admin_user, options.admin_pass elif options.test: AppScaleLogger.log("Using default admin username/password") username, password = LocalState.DEFAULT_USER, LocalState.DEFAULT_PASSWORD else: username, password = LocalState.get_credentials() RemoteHelper.create_user_accounts(username, password, uaserver_host, options.keyname) uaserver_client.set_admin_role(username) RemoteHelper.wait_for_machines_to_finish_loading(public_ip, options.keyname) # Finally, update our metadata once we know that all of the machines are # up and have started all their API services. LocalState.update_local_metadata(options, node_layout, public_ip, instance_id) RemoteHelper.copy_local_metadata(public_ip, options.keyname, options.verbose) RemoteHelper.sleep_until_port_is_open(LocalState.get_login_host( options.keyname), RemoteHelper.APP_LOAD_BALANCER_PORT, options.verbose) AppScaleLogger.success("AppScale successfully started!") AppScaleLogger.success("View status information about your AppScale " + \ "deployment at http://{0}/status".format(LocalState.get_login_host( options.keyname))) AppScaleLogger.remote_log_tools_state(options, my_id, "finished", APPSCALE_VERSION)
def run_upgrade_script(cls, options, node_layout): """ Runs the upgrade script which checks for any upgrades needed to be performed. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. node_layout: A NodeLayout object for the deployment. """ timestamp = datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S') db_ips = [node.private_ip for node in node_layout.nodes if node.is_role('db_master') or node.is_role('db_slave')] zk_ips = [node.private_ip for node in node_layout.nodes if node.is_role('zookeeper')] upgrade_script_command = '{script} --keyname {keyname} '\ '--log-postfix {timestamp} '\ '--db-master {db_master} '\ '--zookeeper {zk_ips} '\ '--database {db_ips} '\ '--replication {replication}'.format( script=cls.UPGRADE_SCRIPT, keyname=options.keyname, timestamp=timestamp, db_master=node_layout.db_master().private_ip, zk_ips=' '.join(zk_ips), db_ips=' '.join(db_ips), replication=node_layout.replication ) master_public_ip = node_layout.head_node().public_ip AppScaleLogger.log("Running upgrade script to check if any other upgrade is needed.") # Run the upgrade command as a background process. error_bucket = Queue.Queue() threading.Thread( target=async_layout_upgrade, args=(master_public_ip, options.keyname, upgrade_script_command, error_bucket, options.verbose) ).start() last_message = None while True: # Check if the SSH thread has crashed. try: ssh_error = error_bucket.get(block=False) AppScaleLogger.warn('Error executing upgrade script') LocalState.generate_crash_log(ssh_error, traceback.format_exc()) except Queue.Empty: pass upgrade_status_file = cls.UPGRADE_STATUS_FILE_LOC + timestamp + ".json" command = 'cat' + " " + upgrade_status_file upgrade_status = RemoteHelper.ssh( master_public_ip, options.keyname, command, options.verbose) json_status = json.loads(upgrade_status) if 'status' not in json_status or 'message' not in json_status: raise AppScaleException('Invalid status log format') if json_status['status'] == 'complete': AppScaleLogger.success(json_status['message']) break if json_status['status'] == 'inProgress': if json_status['message'] != last_message: AppScaleLogger.log(json_status['message']) last_message = json_status['message'] time.sleep(cls.SLEEP_TIME) continue # Assume the message is an error. AppScaleLogger.warn(json_status['message']) raise AppScaleException(json_status['message'])
def run_upgrade_script(cls, options, node_layout): """ Runs the upgrade script which checks for any upgrades needed to be performed. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. node_layout: A NodeLayout object for the deployment. """ timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S") db_ips = [ node.private_ip for node in node_layout.nodes if node.is_role("db_master") or node.is_role("db_slave") ] zk_ips = [node.private_ip for node in node_layout.nodes if node.is_role("zookeeper")] upgrade_script_command = ( "{script} --keyname {keyname} " "--log-postfix {timestamp} " "--db-master {db_master} " "--zookeeper {zk_ips} " "--database {db_ips} " "--replication {replication}".format( script=cls.UPGRADE_SCRIPT, keyname=options.keyname, timestamp=timestamp, db_master=node_layout.db_master().private_ip, zk_ips=" ".join(zk_ips), db_ips=" ".join(db_ips), replication=node_layout.replication, ) ) master_public_ip = node_layout.head_node().public_ip AppScaleLogger.log("Running upgrade script to check if any other upgrade is needed.") # Run the upgrade command as a background process. error_bucket = Queue.Queue() threading.Thread( target=async_layout_upgrade, args=(master_public_ip, options.keyname, upgrade_script_command, error_bucket, options.verbose), ).start() last_message = None while True: # Check if the SSH thread has crashed. try: ssh_error = error_bucket.get(block=False) AppScaleLogger.warn("Error executing upgrade script") LocalState.generate_crash_log(ssh_error, traceback.format_exc()) except Queue.Empty: pass upgrade_status_file = cls.UPGRADE_STATUS_FILE_LOC + timestamp + ".json" command = "cat" + " " + upgrade_status_file upgrade_status = RemoteHelper.ssh(master_public_ip, options.keyname, command, options.verbose) json_status = json.loads(upgrade_status) if "status" not in json_status or "message" not in json_status: raise AppScaleException("Invalid status log format") if json_status["status"] == "complete": AppScaleLogger.success(json_status["message"]) break if json_status["status"] == "inProgress": if json_status["message"] != last_message: AppScaleLogger.log(json_status["message"]) last_message = json_status["message"] time.sleep(cls.SLEEP_TIME) continue # Assume the message is an error. AppScaleLogger.warn(json_status["message"]) raise AppScaleException(json_status["message"])
def run_instances(cls, options): """Starts a new AppScale deployment with the parameters given. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. Raises: AppControllerException: If the AppController on the head node crashes. When this occurs, the message in the exception contains the reason why the AppController crashed. BadConfigurationException: If the user passes in options that are not sufficient to start an AppScale deployment (e.g., running on EC2 but not specifying the AMI to use), or if the user provides us contradictory options (e.g., running on EC2 but not specifying EC2 credentials). """ LocalState.make_appscale_directory() LocalState.ensure_appscale_isnt_running(options.keyname, options.force) if options.infrastructure: if not options.disks and not options.test and not options.force: LocalState.ensure_user_wants_to_run_without_disks() reduced_version = ".".join(x for x in APPSCALE_VERSION.split(".")[:2]) AppScaleLogger.log("Starting AppScale " + reduced_version) my_id = str(uuid.uuid4()) AppScaleLogger.remote_log_tools_state(options, my_id, "started", APPSCALE_VERSION) node_layout = NodeLayout(options) if not node_layout.is_valid(): raise BadConfigurationException( "There were errors with your " + "placement strategy:\n{0}".format(str(node_layout.errors())) ) head_node = node_layout.head_node() # Start VMs in cloud via cloud agent. if options.infrastructure: instance_ids, public_ips, private_ips = RemoteHelper.start_all_nodes(options, len(node_layout.nodes)) AppScaleLogger.log( "\nPlease wait for AppScale to prepare your machines " "for use. This can take few minutes." ) # Set newly obtained node layout info for this deployment. for i, _ in enumerate(instance_ids): node_layout.nodes[i].public_ip = public_ips[i] node_layout.nodes[i].private_ip = private_ips[i] node_layout.nodes[i].instance_id = instance_ids[i] # Enables root logins and SSH access on the head node. RemoteHelper.enable_root_ssh(options, head_node.public_ip) AppScaleLogger.verbose("Node Layout: {}".format(node_layout.to_list()), options.verbose) # Ensure all nodes are compatible. RemoteHelper.ensure_machine_is_compatible(head_node.public_ip, options.keyname, options.verbose) # Use rsync to move custom code into the deployment. if options.scp: AppScaleLogger.log("Copying over local copy of AppScale from {0}".format(options.scp)) RemoteHelper.rsync_files(head_node.public_ip, options.keyname, options.scp, options.verbose) # Start services on head node. RemoteHelper.start_head_node(options, my_id, node_layout) # Write deployment metadata to disk (facilitates SSH operations, etc.) db_master = node_layout.db_master().private_ip head_node = node_layout.head_node().public_ip LocalState.update_local_metadata(options, db_master, head_node) # Copy the locations.json to the head node RemoteHelper.copy_local_metadata(node_layout.head_node().public_ip, options.keyname, options.verbose) # Wait for services on head node to start. secret_key = LocalState.get_secret_key(options.keyname) acc = AppControllerClient(head_node, secret_key) try: while not acc.is_initialized(): AppScaleLogger.log("Waiting for head node to initialize...") # This can take some time in particular the first time around, since # we will have to initialize the database. time.sleep(cls.SLEEP_TIME * 3) except socket.error as socket_error: AppScaleLogger.warn("Unable to initialize AppController: {}".format(socket_error.message)) message = RemoteHelper.collect_appcontroller_crashlog(head_node, options.keyname, options.verbose) raise AppControllerException(message) # Set up admin account. try: # We don't need to have any exception information here: we do expect # some anyway while the UserAppServer is coming up. acc.does_user_exist("non-existent-user", True) except Exception: AppScaleLogger.log("UserAppServer not ready yet. Retrying ...") time.sleep(cls.SLEEP_TIME) if options.admin_user and options.admin_pass: AppScaleLogger.log("Using the provided admin username/password") username, password = options.admin_user, options.admin_pass elif options.test: AppScaleLogger.log("Using default admin username/password") username, password = LocalState.DEFAULT_USER, LocalState.DEFAULT_PASSWORD else: username, password = LocalState.get_credentials() RemoteHelper.create_user_accounts(username, password, head_node, options.keyname) acc.set_admin_role(username, "true", cls.ADMIN_CAPABILITIES) # Wait for machines to finish loading and AppScale Dashboard to be deployed. RemoteHelper.wait_for_machines_to_finish_loading(head_node, options.keyname) RemoteHelper.sleep_until_port_is_open( LocalState.get_login_host(options.keyname), RemoteHelper.APP_DASHBOARD_PORT, options.verbose ) AppScaleLogger.success("AppScale successfully started!") AppScaleLogger.success( "View status information about your AppScale " + "deployment at http://{0}:{1}".format( LocalState.get_login_host(options.keyname), RemoteHelper.APP_DASHBOARD_PORT ) ) AppScaleLogger.remote_log_tools_state(options, my_id, "finished", APPSCALE_VERSION)
def terminate_virtualized_cluster(cls, keyname, clean, 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. clean: A bool representing whether clean should be ran on the nodes. """ AppScaleLogger.log("Stopping 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: machines = len(acc.get_all_public_ips()) - 1 acc.run_terminate(clean) terminated_successfully = True log_dump = u"" while not acc.is_appscale_terminated(): # For terminate receive_server_message will return a JSON string that # is a list of dicts with keys: ip, status, output try: output_list = yaml.safe_load(acc.receive_server_message()) except Exception as e: log_dump += e.message continue for node in output_list: if node.get("status"): machines -= 1 AppScaleLogger.success("Node at {node_ip}: {status}".format( node_ip=node.get("ip"), status="Stopping AppScale finished")) else: AppScaleLogger.warn("Node at {node_ip}: {status}".format( node_ip=node.get("ip"), status="Stopping AppScale failed")) terminated_successfully = False log_dump += u"Node at {node_ip}: {status}\nNode Output:"\ u"{output}".format(node_ip=node.get("ip"), status="Stopping AppScale failed", output=node.get("output")) AppScaleLogger.verbose(u"Output of node at {node_ip}:\n" u"{output}".format(node_ip=node.get("ip"), output=node.get("output")), is_verbose) if not terminated_successfully or machines > 0: LocalState.generate_crash_log(AppControllerException, log_dump) raise AppScaleException("{0} node(s) failed stopping AppScale, " "head node is still running AppScale services." .format(machines)) cls.stop_remote_appcontroller(shadow_host, keyname, is_verbose, clean) except socket.error as socket_error: AppScaleLogger.warn(u'Unable to talk to AppController: {}'. format(socket_error.message)) raise except Exception as exception: AppScaleLogger.verbose(u'Saw Exception while stopping AppScale {0}'. format(str(exception)), is_verbose) raise
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. """ start_time = time.time() 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: # 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 + '.') acc = AppControllerClient(LocalState.get_login_host(options.keyname), LocalState.get_secret_key(options.keyname)) userappserver_host = acc.get_uaserver_host(options.verbose) userappclient = UserAppClient(userappserver_host, LocalState.get_secret_key( options.keyname)) 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 userappclient.does_user_exist(username): password = LocalState.get_password_from_stdin() RemoteHelper.create_user_accounts(username, password, userappserver_host, options.keyname, clear_datastore=False) app_exists = userappclient.does_app_exist(app_id) app_admin = userappclient.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.") eager_app = None eager_enabled = not options.disable_eager t1 = t2 = t3 = t4 = 0 if eager_enabled: t1 = time.time() eager_app = EagerHelper.get_application_info(username, app_language, file_location) valid = EagerHelper.perform_eager_validation(eager_app, options.keyname) if valid: AppScaleLogger.success('EAGER validation was successful. Continuing with the deployment.') else: AppScaleLogger.warn('EAGER validation failed. Aborting app deployment!') end_time = time.time() AppScaleLogger.log("Time elapsed: {0} ms".format((end_time - start_time) * 1000)) AppScaleLogger.log("Time spent on EAGER: {0} ms".format((end_time - t1) * 1000)) return t2 = time.time() 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)) userappclient.reserve_app_id(username, app_id, app_language) 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 serving_host, serving_port = userappclient.get_serving_info(app_id, options.keyname) RemoteHelper.sleep_until_port_is_open(serving_host, serving_port, options.verbose) app_url = "http://{0}:{1}".format(serving_host, serving_port) if eager_enabled and eager_app.api_list: t3 = time.time() EagerHelper.publish_api_list(eager_app.api_list, app_url, options.keyname) t4 = time.time() AppScaleLogger.success("Your app can be reached at the following URL: " + app_url) if created_dir: shutil.rmtree(file_location) end_time = time.time() AppScaleLogger.log("Time elapsed: {0} ms".format((end_time - start_time) * 1000)) AppScaleLogger.log("Time spent on EAGER: {0} ms".format((t2 - t1 + t4 - t3) * 1000)) return (serving_host, serving_port)
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: # 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 + "." ) acc = AppControllerClient( LocalState.get_login_host(options.keyname), LocalState.get_secret_key(options.keyname) ) userappclient = UserAppClient( LocalState.get_login_host(options.keyname), LocalState.get_secret_key(options.keyname) ) 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 userappclient.does_user_exist(username): password = LocalState.get_password_from_stdin() RemoteHelper.create_user_accounts( username, password, LocalState.get_login_host(options.keyname), options.keyname, clear_datastore=False ) app_exists = userappclient.does_app_exist(app_id) app_admin = userappclient.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)) userappclient.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 serving_host, serving_port = userappclient.get_serving_info(app_id, options.keyname) RemoteHelper.sleep_until_port_is_open(serving_host, serving_port, options.verbose) AppScaleLogger.success( "Your app can be reached at the following URL: " + "http://{0}:{1}".format(serving_host, serving_port) ) if created_dir: shutil.rmtree(file_location) return (serving_host, serving_port)
def run_instances(cls, options): """Starts a new AppScale deployment with the parameters given. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. Raises: AppControllerException: If the AppController on the head node crashes. When this occurs, the message in the exception contains the reason why the AppController crashed. BadConfigurationException: If the user passes in options that are not sufficient to start an AppScale deployment (e.g., running on EC2 but not specifying the AMI to use), or if the user provides us contradictory options (e.g., running on EC2 but not specifying EC2 credentials). """ LocalState.make_appscale_directory() LocalState.ensure_appscale_isnt_running(options.keyname, options.force) if options.infrastructure: if not options.disks and not options.test and not options.force: LocalState.ensure_user_wants_to_run_without_disks() AppScaleLogger.log("Starting AppScale " + APPSCALE_VERSION + " over the " + options.infrastructure + " cloud.") else: AppScaleLogger.log("Starting AppScale " + APPSCALE_VERSION + " over a virtualized cluster.") my_id = str(uuid.uuid4()) AppScaleLogger.remote_log_tools_state(options, my_id, "started", APPSCALE_VERSION) node_layout = NodeLayout(options) if not node_layout.is_valid(): raise BadConfigurationException("There were errors with your " + \ "placement strategy:\n{0}".format(str(node_layout.errors()))) if not node_layout.is_supported(): AppScaleLogger.warn("Warning: This deployment strategy is not " + \ "officially supported.") public_ip, instance_id = RemoteHelper.start_head_node( options, my_id, node_layout) AppScaleLogger.log( "\nPlease wait for AppScale to prepare your machines " + "for use.") # Write our metadata as soon as possible to let users SSH into those # machines via 'appscale ssh' LocalState.update_local_metadata(options, node_layout, public_ip, instance_id) RemoteHelper.copy_local_metadata(public_ip, options.keyname, options.verbose) acc = AppControllerClient(public_ip, LocalState.get_secret_key(options.keyname)) try: uaserver_host = acc.get_uaserver_host(options.verbose) except Exception: message = RemoteHelper.collect_appcontroller_crashlog( public_ip, options.keyname, options.verbose) raise AppControllerException(message) RemoteHelper.sleep_until_port_is_open(uaserver_host, UserAppClient.PORT, options.verbose) # Update our metadata again so that users can SSH into other boxes that # may have been started. LocalState.update_local_metadata(options, node_layout, public_ip, instance_id) RemoteHelper.copy_local_metadata(public_ip, options.keyname, options.verbose) AppScaleLogger.log("UserAppServer is at {0}".format(uaserver_host)) uaserver_client = UserAppClient( uaserver_host, LocalState.get_secret_key(options.keyname)) if options.admin_user and options.admin_pass: AppScaleLogger.log("Using the provided admin username/password") username, password = options.admin_user, options.admin_pass elif options.test: AppScaleLogger.log("Using default admin username/password") username, password = LocalState.DEFAULT_USER, LocalState.DEFAULT_PASSWORD else: username, password = LocalState.get_credentials() RemoteHelper.create_user_accounts(username, password, uaserver_host, options.keyname, options.clear_datastore) uaserver_client.set_admin_role(username) RemoteHelper.wait_for_machines_to_finish_loading( public_ip, options.keyname) # Finally, update our metadata once we know that all of the machines are # up and have started all their API services. LocalState.update_local_metadata(options, node_layout, public_ip, instance_id) RemoteHelper.copy_local_metadata(public_ip, options.keyname, options.verbose) RemoteHelper.sleep_until_port_is_open( LocalState.get_login_host(options.keyname), RemoteHelper.APP_DASHBOARD_PORT, options.verbose) AppScaleLogger.success("AppScale successfully started!") AppScaleLogger.success("View status information about your AppScale " + \ "deployment at http://{0}:{1}/status".format(LocalState.get_login_host( options.keyname), RemoteHelper.APP_DASHBOARD_PORT)) AppScaleLogger.remote_log_tools_state(options, my_id, "finished", APPSCALE_VERSION)
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: # 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 + '.') acc = AppControllerClient(LocalState.get_login_host(options.keyname), LocalState.get_secret_key(options.keyname)) userappserver_host = acc.get_uaserver_host(options.verbose) userappclient = UserAppClient( userappserver_host, LocalState.get_secret_key(options.keyname)) 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 userappclient.does_user_exist(username): password = LocalState.get_password_from_stdin() RemoteHelper.create_user_accounts(username, password, userappserver_host, options.keyname, clear_datastore=False) app_exists = userappclient.does_app_exist(app_id) app_admin = userappclient.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)) userappclient.reserve_app_id(username, app_id, app_language) 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 serving_host, serving_port = userappclient.get_serving_info( app_id, options.keyname) RemoteHelper.sleep_until_port_is_open(serving_host, serving_port, options.verbose) AppScaleLogger.success( "Your app can be reached at the following URL: " + "http://{0}:{1}".format(serving_host, serving_port)) if created_dir: shutil.rmtree(file_location) return (serving_host, serving_port)
def run_instances(cls, options): """Starts a new AppScale deployment with the parameters given. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. Raises: AppControllerException: If the AppController on the head node crashes. When this occurs, the message in the exception contains the reason why the AppController crashed. BadConfigurationException: If the user passes in options that are not sufficient to start an AppScale deployment (e.g., running on EC2 but not specifying the AMI to use), or if the user provides us contradictory options (e.g., running on EC2 but not specifying EC2 credentials). """ LocalState.make_appscale_directory() LocalState.ensure_appscale_isnt_running(options.keyname, options.force) if options.infrastructure: if not options.disks and not options.test and not options.force: LocalState.ensure_user_wants_to_run_without_disks() AppScaleLogger.log( "Starting AppScale " + APPSCALE_VERSION + " over the " + options.infrastructure + " cloud." ) else: AppScaleLogger.log("Starting AppScale " + APPSCALE_VERSION + " over a virtualized cluster.") my_id = str(uuid.uuid4()) AppScaleLogger.remote_log_tools_state(options, my_id, "started", APPSCALE_VERSION) node_layout = NodeLayout(options) if not node_layout.is_valid(): raise BadConfigurationException( "There were errors with your " + "placement strategy:\n{0}".format(str(node_layout.errors())) ) public_ip, instance_id = RemoteHelper.start_head_node(options, my_id, node_layout) AppScaleLogger.log( "\nPlease wait for AppScale to prepare your machines " + "for use. This can take few minutes." ) # Write our metadata as soon as possible to let users SSH into those # machines via 'appscale ssh'. LocalState.update_local_metadata(options, node_layout, public_ip, instance_id) RemoteHelper.copy_local_metadata(public_ip, options.keyname, options.verbose) acc = AppControllerClient(public_ip, LocalState.get_secret_key(options.keyname)) # Let's now wait till the server is initialized. while not acc.is_initialized(): AppScaleLogger.log("Waiting for head node to initialize...") # This can take some time in particular the first time around, since # we will have to initialize the database. time.sleep(cls.SLEEP_TIME * 3) # Now let's make sure the UserAppServer is fully initialized. uaserver_client = UserAppClient(public_ip, LocalState.get_secret_key(options.keyname)) try: # We don't need to have any exception information here: we do expect # some anyway while the UserAppServer is coming up. uaserver_client.does_user_exist("non-existent-user", True) except Exception as exception: AppScaleLogger.log("UserAppServer not ready yet. Retrying ...") time.sleep(cls.SLEEP_TIME) # Update our metadata again so that users can SSH into other boxes that # may have been started. LocalState.update_local_metadata(options, node_layout, public_ip, instance_id) RemoteHelper.copy_local_metadata(public_ip, options.keyname, options.verbose) if options.admin_user and options.admin_pass: AppScaleLogger.log("Using the provided admin username/password") username, password = options.admin_user, options.admin_pass elif options.test: AppScaleLogger.log("Using default admin username/password") username, password = LocalState.DEFAULT_USER, LocalState.DEFAULT_PASSWORD else: username, password = LocalState.get_credentials() RemoteHelper.create_user_accounts(username, password, public_ip, options.keyname, options.clear_datastore) uaserver_client.set_admin_role(username) RemoteHelper.wait_for_machines_to_finish_loading(public_ip, options.keyname) # Finally, update our metadata once we know that all of the machines are # up and have started all their API services. LocalState.update_local_metadata(options, node_layout, public_ip, instance_id) RemoteHelper.copy_local_metadata(public_ip, options.keyname, options.verbose) RemoteHelper.sleep_until_port_is_open( LocalState.get_login_host(options.keyname), RemoteHelper.APP_DASHBOARD_PORT, options.verbose ) AppScaleLogger.success("AppScale successfully started!") AppScaleLogger.success( "View status information about your AppScale " + "deployment at http://{0}:{1}/status".format( LocalState.get_login_host(options.keyname), RemoteHelper.APP_DASHBOARD_PORT ) ) AppScaleLogger.remote_log_tools_state(options, my_id, "finished", APPSCALE_VERSION)
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) ) try: all_ips = acc.get_all_public_ips() except socket.error: # Occurs when the AppController has failed. AppScaleLogger.warn( "Couldn't get an up-to-date listing of the " + "machines in this AppScale deployment. Using our locally cached " + "info instead." ) all_ips = LocalState.get_all_public_ips(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) # The log paths that we collect logs from. log_paths = [ "/var/log/appscale", "/var/log/kern.log*", "/var/log/monit.log*", "/var/log/nginx", "/var/log/syslog*", "/var/log/zookeeper", ] failures = False for ip in all_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) for log_path in log_paths: try: RemoteHelper.scp_remote_to_local(ip, options.keyname, log_path, local_dir, options.verbose) except ShellException as shell_exception: failures = True AppScaleLogger.warn("Unable to collect logs from '{}' for host '{}'".format(log_path, ip)) AppScaleLogger.verbose("Encountered exception: {}".format(str(shell_exception)), options.verbose) if failures: AppScaleLogger.log( "Done copying to {0}. There were " "failures while collecting AppScale logs.".format(options.location) ) else: AppScaleLogger.success("Successfully collected all AppScale logs into " "{0}".format(options.location))
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)) try: all_ips = acc.get_all_public_ips() except socket.error: # Occurs when the AppController has failed. AppScaleLogger.warn("Couldn't get an up-to-date listing of the " + \ "machines in this AppScale deployment. Using our locally cached " + \ "info instead.") all_ips = LocalState.get_all_public_ips(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) # The log paths that we collect logs from. log_paths = [ '/var/log/appscale', '/var/log/kern.log*', '/var/log/monit.log*', '/var/log/nginx', '/var/log/syslog*', '/var/log/zookeeper' ] failures = False for ip in all_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) for log_path in log_paths: try: RemoteHelper.scp_remote_to_local(ip, options.keyname, log_path, local_dir, options.verbose) except ShellException as shell_exception: failures = True AppScaleLogger.warn( "Unable to collect logs from '{}' for host '{}'". format(log_path, ip)) AppScaleLogger.verbose( "Encountered exception: {}".format( str(shell_exception)), options.verbose) if failures: AppScaleLogger.log( "Done copying to {0}. There were " "failures while collecting AppScale logs.".format( options.location)) else: AppScaleLogger.success( "Successfully collected all AppScale logs into " "{0}".format(options.location))
def down(self, clean=False, terminate=False): """ 'down' provides a nicer experience for users than the appscale-terminate-instances command, by using the configuration options present in the AppScalefile found in the current working directory. Args: clean: A boolean to indicate if the deployment data and metadata needs to be clean. This will clear the datastore. terminate: A boolean to indicate if instances needs to be terminated (valid only if we spawn instances at start). Raises: AppScalefileException: If there is no AppScalefile in the current working directory. """ contents = self.read_appscalefile() # Construct a terminate-instances command from the file's contents command = [] contents_as_yaml = yaml.safe_load(contents) if 'verbose' in contents_as_yaml and contents_as_yaml[ 'verbose'] == True: is_verbose = contents_as_yaml['verbose'] command.append("--verbose") else: is_verbose = False if 'keyname' in contents_as_yaml: keyname = contents_as_yaml['keyname'] command.append("--keyname") command.append(contents_as_yaml['keyname']) else: keyname = 'appscale' if "EC2_ACCESS_KEY" in contents_as_yaml: os.environ["EC2_ACCESS_KEY"] = contents_as_yaml["EC2_ACCESS_KEY"] if "EC2_SECRET_KEY" in contents_as_yaml: os.environ["EC2_SECRET_KEY"] = contents_as_yaml["EC2_SECRET_KEY"] if "EC2_URL" in contents_as_yaml: os.environ["EC2_URL"] = contents_as_yaml["EC2_URL"] if clean: if 'test' not in contents_as_yaml or contents_as_yaml[ 'test'] != True: LocalState.confirm_or_abort( "Clean will delete every data in the deployment.") all_ips = LocalState.get_all_public_ips(keyname) for ip in all_ips: RemoteHelper.ssh(ip, keyname, self.TERMINATE, is_verbose) AppScaleLogger.success( "Successfully cleaned your AppScale deployment.") if terminate: infrastructure = LocalState.get_infrastructure(keyname) if infrastructure != "xen" and not LocalState.are_disks_used( keyname) and 'test' not in contents_as_yaml: LocalState.confirm_or_abort( "Terminate will delete instances and the data on them.") command.append("--terminate") if 'test' in contents_as_yaml and contents_as_yaml['test'] == True: command.append("--test") # Finally, exec the command. Don't worry about validating it - # appscale-terminate-instances will do that for us. options = ParseArgs(command, "appscale-terminate-instances").args AppScaleTools.terminate_instances(options) LocalState.cleanup_appscale_files(keyname, terminate) AppScaleLogger.success( "Successfully shut down your AppScale deployment.")
def run_instances(cls, options): """Starts a new AppScale deployment with the parameters given. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. Raises: AppControllerException: If the AppController on the head node crashes. When this occurs, the message in the exception contains the reason why the AppController crashed. BadConfigurationException: If the user passes in options that are not sufficient to start an AppScale deployment (e.g., running on EC2 but not specifying the AMI to use), or if the user provides us contradictory options (e.g., running on EC2 but not specifying EC2 credentials). """ LocalState.make_appscale_directory() LocalState.ensure_appscale_isnt_running(options.keyname, options.force) if options.infrastructure: if not options.disks and not options.test and not options.force: LocalState.ensure_user_wants_to_run_without_disks() AppScaleLogger.log("Starting AppScale " + APPSCALE_VERSION + " over the " + options.infrastructure + " cloud.") else: AppScaleLogger.log("Starting AppScale " + APPSCALE_VERSION + " over a virtualized cluster.") my_id = str(uuid.uuid4()) AppScaleLogger.remote_log_tools_state(options, my_id, "started", APPSCALE_VERSION) node_layout = NodeLayout(options) if not node_layout.is_valid(): raise BadConfigurationException("There were errors with your " + \ "placement strategy:\n{0}".format(str(node_layout.errors()))) public_ip, instance_id = RemoteHelper.start_head_node( options, my_id, node_layout) AppScaleLogger.log( "\nPlease wait for AppScale to prepare your machines " + "for use. This can take few minutes.") # Write our metadata as soon as possible to let users SSH into those # machines via 'appscale ssh'. LocalState.update_local_metadata(options, node_layout, public_ip, instance_id) RemoteHelper.copy_local_metadata(public_ip, options.keyname, options.verbose) acc = AppControllerClient(public_ip, LocalState.get_secret_key(options.keyname)) # Let's now wait till the server is initialized. while not acc.is_initialized(): AppScaleLogger.log('Waiting for head node to initialize...') # This can take some time in particular the first time around, since # we will have to initialize the database. time.sleep(cls.SLEEP_TIME * 3) try: # We don't need to have any exception information here: we do expect # some anyway while the UserAppServer is coming up. acc.does_user_exist("non-existent-user", True) except Exception as exception: AppScaleLogger.log('UserAppServer not ready yet. Retrying ...') time.sleep(cls.SLEEP_TIME) # Update our metadata again so that users can SSH into other boxes that # may have been started. LocalState.update_local_metadata(options, node_layout, public_ip, instance_id) RemoteHelper.copy_local_metadata(public_ip, options.keyname, options.verbose) if options.admin_user and options.admin_pass: AppScaleLogger.log("Using the provided admin username/password") username, password = options.admin_user, options.admin_pass elif options.test: AppScaleLogger.log("Using default admin username/password") username, password = LocalState.DEFAULT_USER, LocalState.DEFAULT_PASSWORD else: username, password = LocalState.get_credentials() RemoteHelper.create_user_accounts(username, password, public_ip, options.keyname, options.clear_datastore) acc.set_admin_role(username, 'true', cls.ADMIN_CAPABILITIES) RemoteHelper.wait_for_machines_to_finish_loading( public_ip, options.keyname) # Finally, update our metadata once we know that all of the machines are # up and have started all their API services. LocalState.update_local_metadata(options, node_layout, public_ip, instance_id) RemoteHelper.copy_local_metadata(public_ip, options.keyname, options.verbose) RemoteHelper.sleep_until_port_is_open( LocalState.get_login_host(options.keyname), RemoteHelper.APP_DASHBOARD_PORT, options.verbose) AppScaleLogger.success("AppScale successfully started!") AppScaleLogger.success("View status information about your AppScale " + \ "deployment at http://{0}:{1}/status".format(LocalState.get_login_host( options.keyname), RemoteHelper.APP_DASHBOARD_PORT)) AppScaleLogger.remote_log_tools_state(options, my_id, "finished", APPSCALE_VERSION)
def down(self, clean=False, terminate=False): """ 'down' provides a nicer experience for users than the appscale-terminate-instances command, by using the configuration options present in the AppScalefile found in the current working directory. Args: clean: A boolean to indicate if the deployment data and metadata needs to be clean. This will clear the datastore. terminate: A boolean to indicate if instances needs to be terminated (valid only if we spawn instances at start). Raises: AppScalefileException: If there is no AppScalefile in the current working directory. """ contents = self.read_appscalefile() # Construct a terminate-instances command from the file's contents command = [] contents_as_yaml = yaml.safe_load(contents) if 'verbose' in contents_as_yaml and contents_as_yaml['verbose'] == True: is_verbose = contents_as_yaml['verbose'] command.append("--verbose") else: is_verbose = False if 'keyname' in contents_as_yaml: keyname = contents_as_yaml['keyname'] command.append("--keyname") command.append(contents_as_yaml['keyname']) else: keyname = 'appscale' if "EC2_ACCESS_KEY" in contents_as_yaml: os.environ["EC2_ACCESS_KEY"] = contents_as_yaml["EC2_ACCESS_KEY"] if "EC2_SECRET_KEY" in contents_as_yaml: os.environ["EC2_SECRET_KEY"] = contents_as_yaml["EC2_SECRET_KEY"] if "EC2_URL" in contents_as_yaml: os.environ["EC2_URL"] = contents_as_yaml["EC2_URL"] if clean: if 'test' not in contents_as_yaml or contents_as_yaml['test'] != True: LocalState.confirm_or_abort("Clean will delete every data in the deployment.") all_ips = LocalState.get_all_public_ips(keyname) for ip in all_ips: RemoteHelper.ssh(ip, keyname, self.TERMINATE, is_verbose) AppScaleLogger.success("Successfully cleaned your AppScale deployment.") if terminate: infrastructure = LocalState.get_infrastructure(keyname) if infrastructure != "xen" and not LocalState.are_disks_used( keyname) and 'test' not in contents_as_yaml: LocalState.confirm_or_abort("Terminate will delete instances and the data on them.") command.append("--terminate") if 'test' in contents_as_yaml and contents_as_yaml['test'] == True: command.append("--test") # Finally, exec the command. Don't worry about validating it - # appscale-terminate-instances will do that for us. options = ParseArgs(command, "appscale-terminate-instances").args AppScaleTools.terminate_instances(options) LocalState.cleanup_appscale_files(keyname, terminate) AppScaleLogger.success("Successfully shut down your AppScale deployment.")
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)
def run_instances(cls, options): """Starts a new AppScale deployment with the parameters given. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. Raises: AppControllerException: If the AppController on the head node crashes. When this occurs, the message in the exception contains the reason why the AppController crashed. BadConfigurationException: If the user passes in options that are not sufficient to start an AppScale deployment (e.g., running on EC2 but not specifying the AMI to use), or if the user provides us contradictory options (e.g., running on EC2 but not specifying EC2 credentials). """ LocalState.make_appscale_directory() LocalState.ensure_appscale_isnt_running(options.keyname, options.force) if options.infrastructure: if not options.disks and not options.test and not options.force: LocalState.ensure_user_wants_to_run_without_disks() reduced_version = '.'.join(x for x in APPSCALE_VERSION.split('.')[:2]) AppScaleLogger.log("Starting AppScale " + reduced_version) my_id = str(uuid.uuid4()) AppScaleLogger.remote_log_tools_state(options, my_id, "started", APPSCALE_VERSION) node_layout = NodeLayout(options) if not node_layout.is_valid(): raise BadConfigurationException("There were errors with your " + \ "placement strategy:\n{0}".format(str(node_layout.errors()))) head_node = node_layout.head_node() # Start VMs in cloud via cloud agent. if options.infrastructure: instance_ids, public_ips, private_ips = RemoteHelper.start_all_nodes( options, len(node_layout.nodes)) AppScaleLogger.log("\nPlease wait for AppScale to prepare your machines " "for use. This can take few minutes.") # Set newly obtained node layout info for this deployment. for i, _ in enumerate(instance_ids): node_layout.nodes[i].public_ip = public_ips[i] node_layout.nodes[i].private_ip = private_ips[i] node_layout.nodes[i].instance_id = instance_ids[i] # Enables root logins and SSH access on the head node. RemoteHelper.enable_root_ssh(options, head_node.public_ip) AppScaleLogger.verbose("Node Layout: {}".format(node_layout.to_list()), options.verbose) # Ensure all nodes are compatible. RemoteHelper.ensure_machine_is_compatible( head_node.public_ip, options.keyname, options.verbose) # Use rsync to move custom code into the deployment. if options.scp: AppScaleLogger.log("Copying over local copy of AppScale from {0}". format(options.scp)) RemoteHelper.rsync_files(head_node.public_ip, options.keyname, options.scp, options.verbose) # Start services on head node. RemoteHelper.start_head_node(options, my_id, node_layout) # Write deployment metadata to disk (facilitates SSH operations, etc.) db_master = node_layout.db_master().private_ip head_node = node_layout.head_node().public_ip LocalState.update_local_metadata(options, db_master, head_node) # Copy the locations.json to the head node RemoteHelper.copy_local_metadata(node_layout.head_node().public_ip, options.keyname, options.verbose) # Wait for services on head node to start. secret_key = LocalState.get_secret_key(options.keyname) acc = AppControllerClient(head_node, secret_key) try: while not acc.is_initialized(): AppScaleLogger.log('Waiting for head node to initialize...') # This can take some time in particular the first time around, since # we will have to initialize the database. time.sleep(cls.SLEEP_TIME*3) except socket.error as socket_error: AppScaleLogger.warn('Unable to initialize AppController: {}'. format(socket_error.message)) message = RemoteHelper.collect_appcontroller_crashlog( head_node, options.keyname, options.verbose) raise AppControllerException(message) # Set up admin account. try: # We don't need to have any exception information here: we do expect # some anyway while the UserAppServer is coming up. acc.does_user_exist("non-existent-user", True) except Exception: AppScaleLogger.log('UserAppServer not ready yet. Retrying ...') time.sleep(cls.SLEEP_TIME) if options.admin_user and options.admin_pass: AppScaleLogger.log("Using the provided admin username/password") username, password = options.admin_user, options.admin_pass elif options.test: AppScaleLogger.log("Using default admin username/password") username, password = LocalState.DEFAULT_USER, LocalState.DEFAULT_PASSWORD else: username, password = LocalState.get_credentials() RemoteHelper.create_user_accounts(username, password, head_node, options.keyname) acc.set_admin_role(username, 'true', cls.ADMIN_CAPABILITIES) # Wait for machines to finish loading and AppScale Dashboard to be deployed. RemoteHelper.wait_for_machines_to_finish_loading(head_node, options.keyname) RemoteHelper.sleep_until_port_is_open(LocalState.get_login_host( options.keyname), RemoteHelper.APP_DASHBOARD_PORT, options.verbose) AppScaleLogger.success("AppScale successfully started!") AppScaleLogger.success("View status information about your AppScale " + \ "deployment at http://{0}:{1}".format(LocalState.get_login_host( options.keyname), RemoteHelper.APP_DASHBOARD_PORT)) AppScaleLogger.remote_log_tools_state(options, my_id, "finished", APPSCALE_VERSION)