def validate_machine_image(self): """Checks with the given cloud (if running in a cloud) to ensure that the user-specified ami/emi exists, aborting if it does not. Raises: BadConfigurationException: If the given machine image does not exist. """ if not self.args.infrastructure: return cloud_agent = InfrastructureAgentFactory.create_agent( self.args.infrastructure) params = cloud_agent.get_params_from_args(self.args) if not cloud_agent.does_image_exist(params): raise BadConfigurationException( "Couldn't find the given machine image.") if not cloud_agent.does_zone_exist(params): raise BadConfigurationException("Couldn't find the given zone.") # Make sure that if the user gives us an Elastic IP / static IP, that they # actually own it. if self.args.static_ip: if not cloud_agent.does_address_exist(params): raise BadConfigurationException( "Couldn't find the given static IP.") if not self.args.disks: return for disk in set(self.args.disks.values()): if not cloud_agent.does_disk_exist(params, disk): raise BadConfigurationException( "Couldn't find disk {0}".format(disk))
def validate_num_of_vms_flags(self): """Validates the values given to us by the user relating to the number of virtual machines we spawn in a cloud deployment. Raises: BadConfigurationException: If the values for the min or max flags are invalid. """ if self.args.ips: return # if min is not set and max is, set min == max if self.args.min is None and self.args.max: self.args.min = self.args.max if self.args.ips: if not os.path.exists(self.args.ips): raise BadConfigurationException( "The given ips.yaml file didn't exist.") elif self.args.ips_layout: self.args.ips = yaml.safe_load( base64.b64decode(self.args.ips_layout)) else: if self.args.min < 1: raise BadConfigurationException("Min cannot be less than 1.") if self.args.max < 1: raise BadConfigurationException("Max cannot be less than 1.") if self.args.min > self.args.max: raise BadConfigurationException("Min cannot exceed max.")
def validate_developer_flags(self): """Validates the flags that correspond to flags typically used only by AppScale developers, such as automatically setting administrator e-mails and passwords. Raises: BadConfigurationException: If admin_user, admin_pass, and test are all set, or if admin_user (or admin_pass) is set but the other isn't. This exception can also be thrown if user_commands is not a list. """ if self.args.user_commands: self.args.user_commands = yaml.safe_load( base64.b64decode(self.args.user_commands)) if not isinstance(self.args.user_commands, list): raise BadConfigurationException( "user_commands must be a list. " + "Please make it a list and try again.") else: self.args.user_commands = [] if self.args.admin_user and not self.args.admin_pass: raise BadConfigurationException("If admin_user is set, admin_pass " + \ "must also be set.") if self.args.admin_pass and not self.args.admin_user: raise BadConfigurationException("If admin_pass is set, admin_user " + \ "must also be set.") if self.args.admin_user and self.args.admin_pass and self.args.test: raise BadConfigurationException("Cannot set admin_user, " + \ "admin_pass, and test.")
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 rsync_files(cls, host, keyname, local_appscale_dir, is_verbose): """Copies over an AppScale source directory from this machine to the specified host. Args: host: A str representing a host that should be accessible from this machine. keyname: A str representing the name of the SSH keypair that can log into the specified machine. local_appscale_dir: A str representing the path on the local filesystem where the AppScale source to copy over can be found. is_verbose: A bool that indicates if we should print the rsync commands we exec to stdout. Raises: BadConfigurationException: If local_appscale_dir does not exist locally, or if any of the standard AppScale module folders do not exist. """ ssh_key = LocalState.get_key_path_from_name(keyname) local_path = os.path.expanduser(local_appscale_dir) if not os.path.exists(local_path): raise BadConfigurationException("The location you specified to copy " \ "from, {0}, doesn't exist.".format(local_path)) LocalState.shell("rsync -e 'ssh -i {0} {1}' -arv " "--exclude='AppDB/logs/*' " \ "--exclude='AppDB/cassandra/cassandra/*' " \ "{2}/* root@{3}:/root/appscale/".format(ssh_key, cls.SSH_OPTIONS, local_path, host), is_verbose)
def ensure_appscale_isnt_running(cls, keyname, force): """Checks the secret key file to see if AppScale is running, and aborts if it is. Args: keyname: The keypair name that is used to identify AppScale deployments. force: A bool that is used to run AppScale even if the secret key file is present. Raises: BadConfigurationException: If AppScale is already running. """ if force: return if os.path.exists(cls.get_secret_key_location(keyname)): try: login_host = cls.get_login_host(keyname) secret_key = cls.get_secret_key(keyname) except (IOError, AppScaleException, BadConfigurationException): # If we don't have the locations files, we are not running. return acc = AppControllerClient(login_host, secret_key) try: acc.get_all_public_ips() except AppControllerException: # AC is not running, so we assume appscale is not up and running. AppScaleLogger.log("AppController not running on login node.") else: raise BadConfigurationException("AppScale is already running. Terminate" + " it, set 'force: True' in your AppScalefile, or use the --force flag" + " to run anyways.")
def invalid(self, message): """ Wrapper that NodeLayout validation aspects call when the given layout is invalid. Raises: BadConfigurationException with the given message. """ raise BadConfigurationException(message)
def get_local_nodes_info(cls, keyname): """Reads the JSON-encoded metadata on disk and returns a list using the key 'node_info' that indicates which machines run each API service in this AppScale deployment. Args: keyname: A str that represents an SSH keypair name, uniquely identifying this AppScale deployment. Returns: A list of dicts, where each dict contains information on a single machine in this AppScale deployment. Raises: BadConfigurationException: If there is no JSON-encoded metadata file named after the given keyname. """ try: with open(cls.get_locations_json_location(keyname), 'r') as file_handle: file_contents = json.loads(file_handle.read()) if isinstance(file_contents, list): cls.upgrade_json_file(keyname) file_handle.seek(0) file_contents = json.loads(file_handle.read()) return file_contents.get('node_info', []) except IOError: raise BadConfigurationException("Couldn't read from locations file, " "AppScale may not be running with " "keyname {0}".format(keyname))
def clean_local_metadata(cls, keyname): """Takes the existing JSON-encoded metadata on disk and assigns all nodes besides load_balancers (because of public ips) to "open". Args: keyname: A str that represents an SSH keypair name, uniquely identifying this AppScale deployment. Raises: BadConfigurationException: If there is no JSON-encoded metadata file named after the given keyname. """ try: with open(cls.get_locations_json_location(keyname), 'r+') as file_handle: file_contents = yaml.safe_load(file_handle.read()) # Compatibility support for previous versions of locations file. if isinstance(file_contents, list): cls.upgrade_json_file(keyname) file_handle.seek(0) file_contents = json.loads(file_handle.read()) cleaned_nodes = [] for node in file_contents.get('node_info'): if 'load_balancer' not in node.get('jobs'): node['jobs'] = ['open'] cleaned_nodes.append(node) file_contents['node_info'] = cleaned_nodes # Now we write the JSON file after our changes. file_handle.seek(0) file_handle.truncate() file_handle.write(json.dumps(file_contents)) except IOError: raise BadConfigurationException("Couldn't read from locations file.")
def init(self, environment): """ Writes an AppScalefile in the local directory, that contains common configuration parameters. Args: environment: A str that indicates whether the AppScalefile to write should be tailed to a 'cloud' environment or a 'cluster' environment. Raises: AppScalefileException: If there already is an AppScalefile in the local directory. """ # first, make sure there isn't already an AppScalefile in this # directory appscalefile_location = self.get_appscalefile_location() if os.path.exists(appscalefile_location): raise AppScalefileException( "There is already an AppScalefile" + " in this directory. Please remove it and run 'appscale init'" + " again to generate a new AppScalefile.") # next, see if we're making a cloud template file or a cluster # template file if environment == 'cloud': template_file = self.TEMPLATE_CLOUD_APPSCALEFILE elif environment == 'cluster': template_file = self.TEMPLATE_CLUSTER_APPSCALEFILE else: raise BadConfigurationException( "The environment you specified " + "was invalid. Valid environments are 'cloud' and " + "'cluster'.") # finally, copy the template AppScalefile there shutil.copy(template_file, appscalefile_location)
def valid_ssh_key(self, config): """ Determines whether or not we should call appscale-add-keypair, by collecting all the IP addresses in the given IPs layout and attempting to SSH to each of them with the specified keyname. Args: config: A dictionary that includes the IPs layout (which itself is a dict mapping role names to IPs) and, optionally, the keyname to use. Returns: A bool indicating whether or not the specified keyname can be used to log into each IP address without a password. Raises: BadConfigurationException: If the IPs layout was not a dictionary. """ keyname = config["keyname"] verbose = config.get('verbose', False) if not isinstance(config["ips_layout"], dict): raise BadConfigurationException("ips_layout should be a dictionary. " \ "Please fix it and try again.") ssh_key_location = self.APPSCALE_DIRECTORY + keyname + ".key" if not os.path.exists(ssh_key_location): return False all_ips = self.get_all_ips(config["ips_layout"]) for ip in all_ips: if not self.can_ssh_to_ip(ip, keyname, verbose): return False return True
def valid_ssh_key(self, config, run_instances_opts): """ Checks if the tools can log into the head node with the current key. Args: config: A dictionary that includes the IPs layout (which itself is a dict mapping role names to IPs) and, optionally, the keyname to use. run_instances_opts: The arguments parsed from the appscale-run-instances command. Returns: A bool indicating whether or not the specified keyname can be used to log into the head node. Raises: BadConfigurationException: If the IPs layout was not a dictionary. """ keyname = config['keyname'] verbose = config.get('verbose', False) if not isinstance(config['ips_layout'], dict): raise BadConfigurationException( 'ips_layout should be a dictionary. Please fix it and try again.' ) ssh_key_location = self.APPSCALE_DIRECTORY + keyname + ".key" if not os.path.exists(ssh_key_location): return False all_ips = LocalState.get_all_public_ips(keyname) # If a login node is defined, use that to communicate with other nodes. node_layout = NodeLayout(run_instances_opts) head_node = node_layout.head_node() if head_node is not None: remote_key = '{}/ssh.key'.format(RemoteHelper.CONFIG_DIR) try: RemoteHelper.scp(head_node.public_ip, keyname, ssh_key_location, remote_key, verbose) except ShellException: return False for ip in all_ips: ssh_to_ip = 'ssh -i {key} -o StrictHostkeyChecking=no root@{ip} true'\ .format(key=remote_key, ip=ip) try: RemoteHelper.ssh(head_node.public_ip, keyname, ssh_to_ip, verbose, user='******') except ShellException: return False return True for ip in all_ips: if not self.can_ssh_to_ip(ip, keyname, verbose): return False return True
def validate_admin_flags(self): """Validates the flags that correspond to setting administrator e-mails and passwords. Raises: BadConfigurationException: If admin_user, admin_pass, and test are all set, or if admin_user (or admin_pass) is set but the other isn't. """ if self.args.admin_user and not self.args.admin_pass: raise BadConfigurationException("If admin_user is set, admin_pass " + \ "must also be set.") if self.args.admin_pass and not self.args.admin_user: raise BadConfigurationException("If admin_pass is set, admin_user " + \ "must also be set.") if self.args.admin_user and self.args.admin_pass and self.args.test: raise BadConfigurationException("Cannot set admin_user, " + \ "admin_pass, and test.")
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 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 validate_database_flags(self): """Validates the values given to us by the user for any flag relating to the database used. Raises: BadConfigurationException: If the values for any of the database flags are not valid. """ if self.args.replication is not None and self.args.replication < 1: raise BadConfigurationException("Replication factor must exceed 0.")
def upgrade_json_file(cls, keyname): """Upgrades the JSON file from the other version where it is a list by reading the JSON file, reading the YAML file, creating a dictionary in the "new" format and writing that to the JSON file, and then removing the YAML file. Args: keyname: A str that represents an SSH keypair name, uniquely identifying this AppScale deployment. Raises: BadConfigurationException: If there is no JSON-encoded metadata file, or there is no YAML-encoded metadata file, or the JSON file couldn't be written to. """ try: # Open, read, and store the JSON metadata. with open(cls.get_locations_json_location(keyname), 'r') as file_handle: role_info = json.loads(file_handle.read()) # If this method is running, there should be a YAML metadata file. yaml_locations = "{0}locations-{1}.yaml".format(cls.LOCAL_APPSCALE_PATH, keyname) # Open, read, and store the YAML metadata. with open(yaml_locations, 'r') as yaml_handle: locations_yaml_contents = yaml.safe_load(yaml_handle.read()) # Create a dictionary with the information from both the YAML and JSON # metadata. locations_json = { 'node_info': role_info, 'infrastructure_info': locations_yaml_contents } # Write the new format to the JSON metadata file. with open(cls.get_locations_json_location(keyname), 'w') as file_handle: file_handle.write(json.dumps(locations_json)) # Remove the YAML file because all information from it should be in the # JSON file now. At this point any failures would have raised the # Exception. if os.path.exists(yaml_locations): os.remove(yaml_locations) except IOError: raise BadConfigurationException("Couldn't upgrade locations json " "file, AppScale may not be running with" " keyname {0}".format(keyname))
def shell_check(self, argument): """ Checks for special characters in arguments that are part of shell commands. Args: argument: A str, the argument to be checked. Raises: BadConfigurationException if single quotes are present in argument. """ if '\'' in argument: raise BadConfigurationException("Single quotes (') are not allowed " + \ "in filenames.")
def upgrade(cls, options): """ Upgrades the deployment to the latest AppScale version. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. """ node_layout = NodeLayout(options) if not node_layout.is_valid(): raise BadConfigurationException( 'Your ips_layout is invalid:\n{}'.format(node_layout.errors())) latest_tools = APPSCALE_VERSION try: AppScaleLogger.log( 'Checking if an update is available for appscale-tools') latest_tools = latest_tools_version() except: # Prompt the user if version metadata can't be fetched. if not options.test: response = raw_input( 'Unable to check for the latest version of appscale-tools. Would ' 'you like to continue upgrading anyway? (y/N) ') if response.lower() not in ['y', 'yes']: raise AppScaleException('Cancelled AppScale upgrade.') if latest_tools > APPSCALE_VERSION: raise AppScaleException( "There is a newer version ({}) of appscale-tools available. Please " "upgrade the tools package before running 'appscale upgrade'.". format(latest_tools)) master_ip = node_layout.head_node().public_ip upgrade_version_available = cls.get_upgrade_version_available() current_version = RemoteHelper.get_host_appscale_version( master_ip, options.keyname, options.verbose) # Don't run bootstrap if current version is later that the most recent # public one. Covers cases of revoked versions/tags and ensures we won't # try to downgrade the code. if current_version >= upgrade_version_available: AppScaleLogger.log( 'AppScale is already up to date. Skipping code upgrade.') AppScaleLogger.log( 'Running upgrade script to check if any other upgrades are needed.' ) cls.shut_down_appscale_if_running(options) cls.run_upgrade_script(options, node_layout) return cls.shut_down_appscale_if_running(options) cls.upgrade_appscale(options, node_layout)
def rsync_files(cls, host, keyname, local_appscale_dir, is_verbose): """Copies over an AppScale source directory from this machine to the specified host. Args: host: A str representing a host that should be accessible from this machine. keyname: A str representing the name of the SSH keypair that can log into the specified machine. local_appscale_dir: A str representing the path on the local filesystem where the AppScale source to copy over can be found. is_verbose: A bool that indicates if we should print the rsync commands we exec to stdout. Raises: BadConfigurationException: If local_appscale_dir does not exist locally, or if any of the standard AppScale module folders do not exist. """ ssh_key = LocalState.get_key_path_from_name(keyname) appscale_dirs = [ "lib", "AppController", "AppManager", "AppServer", "AppServer_Java", "AppDashboard", "InfrastructureManager", "AppTaskQueue", "XMPPReceiver" ] for dir_name in appscale_dirs: local_path = os.path.expanduser( local_appscale_dir) + os.sep + dir_name if not os.path.exists(local_path): raise BadConfigurationException("The location you specified to copy " \ "from, {0}, doesn't contain a {1} folder.".format(local_appscale_dir, local_path)) LocalState.shell("rsync -e 'ssh -i {0} {1}' -arv {2}/* "\ "root@{3}:/root/appscale/{4}".format(ssh_key, cls.SSH_OPTIONS, local_path, host, dir_name), is_verbose) # Rsync AppDB separately, as it has a lot of paths we may need to exclude # (e.g., built database binaries). local_app_db = os.path.expanduser( local_appscale_dir) + os.sep + "AppDB/*" LocalState.shell("rsync -e 'ssh -i {0} {1}' -arv --exclude='logs/*' " \ "--exclude='hadoop-*' --exclude='hbase/hbase-*' " \ "--exclude='cassandra/cassandra/*' {2} root@{3}:/root/appscale/AppDB" \ .format(ssh_key, cls.SSH_OPTIONS, local_app_db, host), is_verbose) # And rsync the firewall configuration file separately, as it's not a # directory (which the above all are). local_firewall = os.path.expanduser(local_appscale_dir) + os.sep + \ "firewall.conf" LocalState.shell("rsync -e 'ssh -i {0} {1}' -arv {2} root@{3}:" \ "/root/appscale/firewall.conf".format(ssh_key, cls.SSH_OPTIONS, local_firewall, host), is_verbose)
def validate_environment_flags(self): """Validates flags dealing with setting environment variables. Raises: BadConfigurationException: If the user gives us either EC2_ACCESS_KEY or EC2_SECRET_KEY, but forgets to also specify the other. """ if self.args.EC2_ACCESS_KEY and not self.args.EC2_SECRET_KEY: raise BadConfigurationException("When specifying EC2_ACCESS_KEY, " + \ "EC2_SECRET_KEY must also be specified.") if self.args.EC2_SECRET_KEY and not self.args.EC2_ACCESS_KEY: raise BadConfigurationException("When specifying EC2_SECRET_KEY, " + \ "EC2_ACCESS_KEY must also be specified.") if self.args.EC2_ACCESS_KEY: os.environ['EC2_ACCESS_KEY'] = self.args.EC2_ACCESS_KEY if self.args.EC2_SECRET_KEY: os.environ['EC2_SECRET_KEY'] = self.args.EC2_SECRET_KEY if self.args.EC2_URL: os.environ['EC2_URL'] = self.args.EC2_URL
def validate_machine_image(self): """Checks with the given cloud (if running in a cloud) to ensure that the user-specified ami/emi exists, aborting if it does not. Raises: BadConfigurationException: If the given machine image does not exist. """ if not self.args.infrastructure: return cloud_agent = InfrastructureAgentFactory.create_agent( self.args.infrastructure) params = cloud_agent.get_params_from_args(self.args) if not cloud_agent.does_image_exist(params): raise BadConfigurationException("Couldn't find the given machine image.")
def get_secret_key(cls, keyname): """Retrieves the secret key, used to authenticate AppScale services. Args: keyname: A str representing the SSH keypair name used for this AppScale deployment. Returns: A str containing the secret key. Raises: BadConfigurationException: if the secret key file is not found. """ try: with open(cls.get_secret_key_location(keyname), 'r') as file_handle: return file_handle.read() except IOError: raise BadConfigurationException( "Couldn't find secret key for keyname {}.".format(keyname))
def ensure_appscale_isnt_running(cls, keyname, force): """Checks the secret key file to see if AppScale is running, and aborts if it is. Args: keyname: The keypair name that is used to identify AppScale deployments. force: A bool that is used to run AppScale even if the secret key file is present. Raises: BadConfigurationException: If AppScale is already running. """ if force: return if os.path.exists(cls.get_secret_key_location(keyname)): raise BadConfigurationException("AppScale is already running. Terminate" + " it or use the --force flag to run anyways.")
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 validate_appengine_flags(self): """Validates the values given to us by the user for any flag relating to the number of AppServers to launch per App Engine app. Raises: BadConfigurationException: If the value for the --appengine flag is invalid. """ if self.args.appengine: if self.args.appengine < 1: raise BadConfigurationException("Number of application servers " + \ "must exceed zero.") self.args.autoscale = False elif self.args.autoscale: self.args.appengine = 1 else: # neither are set self.args.appengine = 1 self.args.autoscale = True
def validate_appengine_flags(self): """Validates the values given to us by the user for any flag relating to the number of AppServers to launch per App Engine app. Raises: BadConfigurationException: If the value for the --appengine flag is invalid. """ # Check that appengine is greater then 1, and the set defaults for # min_appservers and autoscale in case they are not defined. if self.args.default_min_appservers: if self.args.default_min_appservers < 1: raise BadConfigurationException("Number of application servers " + \ "must exceed zero.") else: self.args.default_min_appservers = 1 if not self.args.autoscale: self.args.autoscale = True
def get_local_nodes_info(cls, keyname): """Reads the JSON-encoded metadata on disk and returns a list that indicates which machines run each API service in this AppScale deployment. Args: keyname: A str that represents an SSH keypair name, uniquely identifying this AppScale deployment. Returns: A list of dicts, where each dict contains information on a single machine in this AppScale deployment. Raises: BadConfigurationException: If there is no JSON-encoded metadata file named after the given keyname. """ if not os.path.exists(cls.get_locations_json_location(keyname)): raise BadConfigurationException("AppScale does not appear to be " + \ "running with keyname {0}".format(keyname)) with open(cls.get_locations_json_location(keyname), 'r') as file_handle: return json.loads(file_handle.read())
def require_ssh_commands(cls, needs_expect, is_verbose): """Checks to make sure the commands needed to set up passwordless SSH access are installed on this machine. Args: needs_expect: A bool that indicates if we should also check for the 'expect' command. is_verbose: A bool that indicates if we should print how we check for each command to stdout. Raises: BadConfigurationException: If any of the required commands aren't present on this machine. """ required_commands = ['ssh-keygen', 'ssh-copy-id'] if needs_expect: required_commands.append('expect') for command in required_commands: try: cls.shell("hash {0}".format(command), is_verbose) except ShellException: raise BadConfigurationException( "Couldn't find {0} in your PATH.".format(command))
def get_infrastructure_option(cls, tag, keyname): """Reads the JSON-encoded metadata on disk and returns the value for the key 'tag' from the dictionary retrieved using the key 'infrastructure_info'. Args: keyname: A str that indicates the name of the SSH keypair that uniquely identifies this AppScale deployment. tag: A str that indicates what we should look for in the infrastructure_info dictionary, this tag retrieves an option that was passed to AppScale at runtime. """ try: with open(cls.get_locations_json_location(keyname), 'r') as file_handle: file_contents = yaml.safe_load(file_handle.read()) if isinstance(file_contents, list): cls.upgrade_json_file(keyname) file_handle.seek(0) file_contents = yaml.safe_load(file_handle.read()) return file_contents.get('infrastructure_info', {}).get(tag) except IOError: raise BadConfigurationException("Couldn't read from locations file, " "AppScale may not be running with " "keyname {0}".format(keyname))