def testDestroyWithEC2EnvironmentVariables(self): # if the user wants us to use their EC2 credentials when running AppScale, # we should make sure they get set appscale = AppScale() # Mock out the actual file reading itself, and slip in a YAML-dumped # file contents = { 'infrastructure' : 'ec2', 'machine' : 'ami-ABCDEFG', 'keyname' : 'bookey', 'group' : 'boogroup', 'min' : 1, 'max' : 1, 'EC2_ACCESS_KEY' : 'access key', 'EC2_SECRET_KEY' : 'secret key' } yaml_dumped_contents = yaml.dump(contents) self.addMockForAppScalefile(appscale, yaml_dumped_contents) # finally, mock out the actual appscale-terminate-instances call flexmock(AppScaleTools) AppScaleTools.should_receive('terminate_instances') appscale.destroy() self.assertEquals('access key', os.environ['EC2_ACCESS_KEY']) self.assertEquals('secret key', os.environ['EC2_SECRET_KEY'])
def destroy(self): """'destroy' 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. 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 'keyname' in contents_as_yaml: command.append("--keyname") command.append(contents_as_yaml['keyname']) if 'verbose' in contents_as_yaml: command.append("--verbose") # 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 try: AppScaleTools.terminate_instances(options) except Exception as e: AppScaleLogger.warn(str(e))
def logs(self, location): """ 'logs' provides a cleaner experience for users than the appscale-gather-logs command, by using the configuration options present in the AppScalefile found in the current working directory. Args: location: The path on the local filesystem where logs should be copied to. Raises: AppScalefileException: If there is no AppScalefile in the current working directory. """ contents = self.read_appscalefile() contents_as_yaml = yaml.safe_load(contents) # construct the appscale-gather-logs command command = [] if 'keyname' in contents_as_yaml: command.append("--keyname") command.append(contents_as_yaml["keyname"]) command.append("--location") command.append(location) # and exec it options = ParseArgs(command, "appscale-gather-logs").args AppScaleTools.gather_logs(options)
def set(self, property_name, property_value): """ 'set' provides a cleaner experience for users than the appscale-set-property command, by using the configuration options present in the AppScalefile found in the current working directory. Args: property_name: A str naming the AppController instance variable that should be overwritten. property_value: The new value that should be used for the named property. Raises: AppScalefileException: If there is no AppScalefile in the current working directory. """ contents = self.read_appscalefile() contents_as_yaml = yaml.safe_load(contents) # construct the appscale-set-property command command = [] if 'keyname' in contents_as_yaml: command.append("--keyname") command.append(contents_as_yaml["keyname"]) command.append("--property_name") command.append(property_name) command.append("--property_value") command.append(property_value) # and exec it options = ParseArgs(command, "appscale-set-property").args AppScaleTools.set_property(options)
def upgrade(self): """ Allows users to upgrade to the latest version of AppScale.""" contents_as_yaml = yaml.safe_load(self.read_appscalefile()) # Construct the appscale-upgrade command from argv and the contents of # the AppScalefile. command = [] if 'keyname' in contents_as_yaml: command.append("--keyname") command.append(contents_as_yaml['keyname']) if 'verbose' in contents_as_yaml and contents_as_yaml[ 'verbose'] == True: command.append("--verbose") if 'ips_layout' in contents_as_yaml: command.append('--ips_layout') command.append( base64.b64encode(yaml.dump(contents_as_yaml['ips_layout']))) if 'login' in contents_as_yaml: command.extend(['--login', contents_as_yaml['login']]) if 'test' in contents_as_yaml and contents_as_yaml['test'] == True: command.append('--test') options = ParseArgs(command, 'appscale-upgrade').args options.ips = yaml.safe_load(base64.b64decode(options.ips_layout)) AppScaleTools.upgrade(options)
def testUpWithEC2EnvironmentVariables(self): # if the user wants us to use their EC2 credentials when running AppScale, # we should make sure they get set appscale = AppScale() # Mock out the actual file reading itself, and slip in a YAML-dumped # file contents = { 'infrastructure' : 'ec2', 'machine' : 'ami-ABCDEFG', 'keyname' : 'bookey', 'group' : 'boogroup', 'min' : 1, 'max' : 1, 'EC2_ACCESS_KEY' : 'access key', 'EC2_SECRET_KEY' : 'secret key' } yaml_dumped_contents = yaml.dump(contents) self.addMockForAppScalefile(appscale, yaml_dumped_contents) # finally, pretend that our ec2 image to use exists fake_ec2 = flexmock(name="fake_ec2") fake_ec2.should_receive('get_image').with_args('ami-ABCDEFG') \ .and_return() flexmock(boto) boto.should_receive('connect_ec2').with_args('access key', 'secret key') \ .and_return(fake_ec2) # finally, mock out the actual appscale-run-instances call flexmock(AppScaleTools) AppScaleTools.should_receive('run_instances') appscale.up() self.assertEquals('access key', os.environ['EC2_ACCESS_KEY']) self.assertEquals('secret key', os.environ['EC2_SECRET_KEY'])
def run_add_keypair(self): """ Sets up the add keypair arguments and attempts to add the keyname generated. Returns: True on success, False otherwise. """ self.state = self.INIT_STATE add_keypair_args = ['--keyname', self.keyname, '--ips_layout', self.ips_yaml_b64, "--root_password", self.root_pass, "--auto"] options = parse_args.ParseArgs(add_keypair_args, "appscale-add-keypair"). \ args try: AppScaleTools.add_keypair(options) logging.info("AppScale add key pair was successful") except BadConfigurationException as bad_config: self.state = self.ERROR_STATE logging.error(str(bad_config)) self.err_message = "Bad configuration. Unable to set up keypairs." return False except Exception as exception: self.state = self.ERROR_STATE logging.exception(exception) self.err_message = "Exception when running add key pair: {0}". \ format(exception) return False return True
def run_add_keypair(self): """ Sets up the add keypair arguments and attempts to add the keyname generated. Returns: True on success, False otherwise. """ self.state = self.INIT_STATE add_keypair_args = [ '--keyname', self.keyname, '--ips_layout', self.ips_yaml_b64, "--root_password", self.root_pass, "--auto" ] options = parse_args.ParseArgs(add_keypair_args, "appscale-add-keypair"). \ args try: AppScaleTools.add_keypair(options) logging.info("AppScale add key pair was successful") except BadConfigurationException as bad_config: self.state = self.ERROR_STATE logging.error(str(bad_config)) self.err_message = "Bad configuration. Unable to set up keypairs." return False except Exception as exception: self.state = self.ERROR_STATE logging.exception(exception) self.err_message = "Exception when running add key pair: {0}". \ format(exception) return False return True
def logs(self, location): """'logs' provides a cleaner experience for users than the appscale-gather-logs command, by using the configuration options present in the AppScalefile found in the current working directory. Args: location: The path on the local filesystem where logs should be copied to. Raises: AppScalefileException: If there is no AppScalefile in the current working directory. """ contents = self.read_appscalefile() contents_as_yaml = yaml.safe_load(contents) # construct the appscale-gather-logs command command = [] if 'keyname' in contents_as_yaml: command.append("--keyname") command.append(contents_as_yaml["keyname"]) command.append("--location") command.append(location) # and exec it options = ParseArgs(command, "appscale-gather-logs").args try: AppScaleTools.gather_logs(options) except Exception as e: AppScaleLogger.warn(str(e))
def undeploy(self, appid): """ 'undeploy' is a more accessible way to tell an AppScale deployment to stop hosting a Google App Engine application than 'appscale-remove-app'. It calls that command with the configuration options found in the AppScalefile in the current working directory. Args: appid: The name of the application that we should remove. Raises: AppScalefileException: If there is no AppScalefile in the current working directory. """ contents = self.read_appscalefile() # Construct an remove-app command from the file's contents command = [] contents_as_yaml = yaml.safe_load(contents) if 'keyname' in contents_as_yaml: command.append("--keyname") command.append(contents_as_yaml['keyname']) if 'verbose' in contents_as_yaml and contents_as_yaml[ 'verbose'] == True: command.append("--verbose") if 'test' in contents_as_yaml and contents_as_yaml['test'] == True: command.append('--confirm') command.append("--appname") command.append(appid) # Finally, exec the command. Don't worry about validating it - # appscale-upload-app will do that for us. options = ParseArgs(command, "appscale-remove-app").args AppScaleTools.remove_app(options)
def test_all_ok(self): # If the user wants to relocate their app to port X, and nothing else # runs on that port, this should succeed. # Assume that the AppController is running, so is our app, and that other # apps are not running on port 80. fake_appcontroller = flexmock(name='fake_appcontroller') fake_appcontroller.should_receive('get_app_info_map').with_args( 'the secret').and_return(json.dumps({ self.appid : { 'nginx' : 8080 }, 'a-different-app' : { 'nginx' : 81 } })) fake_appcontroller.should_receive('relocate_app').with_args(self.appid, 80, 443, 'the secret').and_return("OK") flexmock(SOAPpy) SOAPpy.should_receive('SOAPProxy').with_args('https://public1:17443') \ .and_return(fake_appcontroller) argv = [ '--keyname', self.keyname, '--appname', self.appid, '--http_port', '80', '--https_port', '443' ] options = ParseArgs(argv, self.function).args AppScaleTools.relocate_app(options)
def deploy(self, app): """'deploy' is a more accessible way to tell an AppScale deployment to run a Google App Engine application than 'appscale-upload-app'. It calls that command with the configuration options found in the AppScalefile in the current working directory. Args: app: The path (absolute or relative) to the Google App Engine application that should be uploaded. Raises: AppScalefileException: If there is no AppScalefile in the current working directory. """ contents = self.read_appscalefile() # Construct an upload-app command from the file's contents command = [] contents_as_yaml = yaml.safe_load(contents) if 'keyname' in contents_as_yaml: command.append("--keyname") command.append(contents_as_yaml['keyname']) if 'test' in contents_as_yaml: command.append("--test") if 'verbose' in contents_as_yaml: command.append("--verbose") command.append("--file") command.append(app) # Finally, exec the command. Don't worry about validating it - # appscale-upload-app will do that for us. options = ParseArgs(command, "appscale-upload-app").args AppScaleTools.upload_app(options)
def testUndeployWithCloudAppScalefile(self): # calling 'appscale undeploy app' with an AppScalefile in the local # directory should collect any parameters needed for the # 'appscale-remove-app' command and then exec it appscale = AppScale() # Mock out the actual file reading itself, and slip in a YAML-dumped # file contents = { 'infrastructure' : 'ec2', 'machine' : 'ami-ABCDEFG', 'keyname' : 'bookey', 'group' : 'boogroup', 'verbose' : True, 'min' : 1, 'max' : 1 } yaml_dumped_contents = yaml.dump(contents) self.addMockForAppScalefile(appscale, yaml_dumped_contents) # finally, mock out the actual appscale-run-instances call flexmock(AppScaleTools) AppScaleTools.should_receive('remove_app') app = 'barapp' appscale.undeploy(app)
def testDeployWithCloudAppScalefile(self): # calling 'appscale deploy app' with an AppScalefile in the local # directory should collect any parameters needed for the # 'appscale-upload-app' command and then exec it appscale = AppScale() # Mock out the actual file reading itself, and slip in a YAML-dumped # file contents = { 'infrastructure' : 'ec2', 'machine' : 'ami-ABCDEFG', 'keyname' : 'bookey', 'group' : 'boogroup', 'verbose' : True, 'min' : 1, 'max' : 1 } yaml_dumped_contents = yaml.dump(contents) self.addMockForAppScalefile(appscale, yaml_dumped_contents) # finally, mock out the actual appscale-run-instances call fake_port = 8080 fake_host = 'fake_host' flexmock(AppScaleTools) AppScaleTools.should_receive('upload_app').and_return( (fake_host, fake_port)) app = '/bar/app' (host, port) = appscale.deploy(app) self.assertEquals(fake_host, host) self.assertEquals(fake_port, port)
def testUpWithMalformedClusterAppScalefile(self): # if we try to use an IPs layout that isn't a dictionary, we should throw up # and die appscale = AppScale() # Mock out the actual file reading itself, and slip in a YAML-dumped # file, with an IPs layout that is a str contents = { 'ips_layout': "'master' 'ip1' 'appengine' 'ip1'", 'keyname': 'boobazblarg', 'group' : 'boobazblarg' } yaml_dumped_contents = yaml.dump(contents) self.addMockForAppScalefile(appscale, yaml_dumped_contents) flexmock(os.path) os.path.should_call('exists') os.path.should_receive('exists').with_args( '/boo/' + appscale.APPSCALEFILE).and_return(True) # finally, mock out the actual appscale tools calls. since we're running # via a cluster, this means we call add-keypair to set up SSH keys, then # run-instances to start appscale flexmock(AppScaleTools) AppScaleTools.should_receive('add_keypair') self.assertRaises(BadConfigurationException, appscale.up)
def testDeployWithCloudAppScalefileAndTestFlag(self): # same as before, but with the 'test' flag in our AppScalefile appscale = AppScale() # Mock out the actual file reading itself, and slip in a YAML-dumped # file contents = { 'infrastructure' : 'ec2', 'machine' : 'ami-ABCDEFG', 'keyname' : 'bookey', 'group' : 'boogroup', 'verbose' : True, 'min' : 1, 'max' : 1, 'test' : True } yaml_dumped_contents = yaml.dump(contents) self.addMockForAppScalefile(appscale, yaml_dumped_contents) # finally, mock out the actual appscale-run-instances call fake_port = 8080 fake_host = 'fake_host' flexmock(AppScaleTools) AppScaleTools.should_receive('upload_app').and_return( (fake_host, fake_port)) app = '/bar/app' (host, port) = appscale.deploy(app) self.assertEquals(fake_host, host) self.assertEquals(fake_port, port)
def upgrade(self): """ Allows users to upgrade to the latest version of AppScale.""" contents_as_yaml = yaml.safe_load(self.read_appscalefile()) # Construct the appscale-upgrade command from argv and the contents of # the AppScalefile. command = [] if 'keyname' in contents_as_yaml: command.append("--keyname") command.append(contents_as_yaml['keyname']) if 'verbose' in contents_as_yaml and contents_as_yaml['verbose'] == True: command.append("--verbose") if 'ips_layout' in contents_as_yaml: command.append('--ips_layout') command.append( base64.b64encode(yaml.dump(contents_as_yaml['ips_layout']))) if 'login' in contents_as_yaml: command.extend(['--login', contents_as_yaml['login']]) if 'test' in contents_as_yaml and contents_as_yaml['test'] == True: command.append('--test') options = ParseArgs(command, 'appscale-upgrade').args options.ips = yaml.safe_load(base64.b64decode(options.ips_layout)) AppScaleTools.upgrade(options)
def testUpWithCloudAppScalefile(self): # calling 'appscale up' if there is an AppScalefile present # should call appscale-run-instances with the given config # params. here, we assume that the file is intended for use # on EC2 appscale = AppScale() # Mock out the actual file reading itself, and slip in a YAML-dumped # file contents = { 'infrastructure' : 'ec2', 'machine' : 'ami-ABCDEFG', 'keyname' : 'bookey', 'group' : 'boogroup', 'min' : 1, 'max' : 1 } yaml_dumped_contents = yaml.dump(contents) self.addMockForAppScalefile(appscale, yaml_dumped_contents) # throw in some mocks for the argument parsing for credential in EC2Agent.REQUIRED_CREDENTIALS: os.environ[credential] = "baz" # finally, pretend that our ec2 image to use exists fake_ec2 = flexmock(name="fake_ec2") fake_ec2.should_receive('get_image').with_args('ami-ABCDEFG') \ .and_return() flexmock(boto) boto.should_receive('connect_ec2').with_args('baz', 'baz').and_return(fake_ec2) # finally, mock out the actual appscale-run-instances call flexmock(AppScaleTools) AppScaleTools.should_receive('run_instances') appscale.up()
def test_appscale_in_two_node_virt_deployment(self): # pretend that the place we're going to put logs into doesn't exist flexmock(os.path) os.path.should_call('exists') # set the fall-through os.path.should_receive('exists').with_args('/tmp/foobaz').and_return( False) # and mock out the mkdir operation flexmock(os) os.should_receive('mkdir').with_args('/tmp/foobaz').and_return() # next, mock out finding the login ip address os.path.should_receive('exists').with_args( LocalState.get_locations_json_location( self.keyname)).and_return(True) fake_nodes_json = flexmock(name="fake_nodes_json") fake_nodes_json.should_receive('read').and_return( json.dumps([{ "public_ip": "public1", "private_ip": "private1", "jobs": ["shadow", "login"] }])) builtins = flexmock(sys.modules['__builtin__']) builtins.should_call('open') builtins.should_receive('open').with_args( LocalState.get_locations_json_location(self.keyname), 'r') \ .and_return(fake_nodes_json) # mock out writing the secret key to ~/.appscale, as well as reading it # later secret_key_location = LocalState.get_secret_key_location(self.keyname) fake_secret = flexmock(name="fake_secret") fake_secret.should_receive('read').and_return('the secret') builtins.should_receive('open').with_args(secret_key_location, 'r') \ .and_return(fake_secret) # and slip in a fake appcontroller to report on the two IP addrs fake_appcontroller = flexmock(name='fake_appcontroller') fake_appcontroller.should_receive('get_all_public_ips').with_args( 'the secret').and_return(json.dumps(['public1', 'public2'])) flexmock(SOAPpy) SOAPpy.should_receive('SOAPProxy').with_args('https://public1:17443') \ .and_return(fake_appcontroller) # fake the creation of the log directories locally os.should_receive('mkdir').with_args( '/tmp/foobaz/public1').and_return() os.should_receive('mkdir').with_args( '/tmp/foobaz/public2').and_return() # finally, fake the copying of the log files flexmock(subprocess) subprocess.should_receive('Popen').with_args(re.compile('/var/log/appscale'), shell=True, stdout=self.fake_temp_file, stderr=subprocess.STDOUT) \ .and_return(self.success) argv = ["--keyname", self.keyname, "--location", "/tmp/foobaz"] options = ParseArgs(argv, self.function).args AppScaleTools.gather_logs(options)
def undeploy(self, appid): """ 'undeploy' is a more accessible way to tell an AppScale deployment to stop hosting a Google App Engine application than 'appscale-remove-app'. It calls that command with the configuration options found in the AppScalefile in the current working directory. Args: appid: The name of the application that we should remove. Raises: AppScalefileException: If there is no AppScalefile in the current working directory. """ contents = self.read_appscalefile() # Construct an remove-app command from the file's contents command = [] contents_as_yaml = yaml.safe_load(contents) if 'keyname' in contents_as_yaml: command.append("--keyname") command.append(contents_as_yaml['keyname']) if 'verbose' in contents_as_yaml and contents_as_yaml['verbose'] == True: command.append("--verbose") command.append("--appname") command.append(appid) # Finally, exec the command. Don't worry about validating it - # appscale-upload-app will do that for us. options = ParseArgs(command, "appscale-remove-app").args AppScaleTools.remove_app(options)
def testUpWithEC2EnvironmentVariables(self): # if the user wants us to use their EC2 credentials when running AppScale, # we should make sure they get set appscale = AppScale() # Mock out the actual file reading itself, and slip in a YAML-dumped # file contents = { 'infrastructure': 'ec2', 'machine': 'ami-ABCDEFG', 'keyname': 'bookey', 'group': 'boogroup', 'min': 1, 'max': 1, 'EC2_ACCESS_KEY': 'access key', 'EC2_SECRET_KEY': 'secret key' } yaml_dumped_contents = yaml.dump(contents) self.addMockForAppScalefile(appscale, yaml_dumped_contents) # finally, pretend that our ec2 image to use exists fake_ec2 = flexmock(name="fake_ec2") fake_ec2.should_receive('get_image').with_args('ami-ABCDEFG') \ .and_return() flexmock(boto) boto.should_receive('connect_ec2').with_args('access key', 'secret key') \ .and_return(fake_ec2) # finally, mock out the actual appscale-run-instances call flexmock(AppScaleTools) AppScaleTools.should_receive('run_instances') appscale.up() self.assertEquals('access key', os.environ['EC2_ACCESS_KEY']) self.assertEquals('secret key', os.environ['EC2_SECRET_KEY'])
def test_all_ok(self): # If the user wants to relocate their app to port X, and nothing else # runs on that port, this should succeed. # Assume that the AppController is running, so is our app, and that other # apps are not running on port 80. fake_appcontroller = flexmock(name='fake_appcontroller') fake_appcontroller.should_receive('get_app_info_map').with_args( 'the secret').and_return( json.dumps({ self.appid: { 'nginx': 8080 }, 'a-different-app': { 'nginx': 81 } })) fake_appcontroller.should_receive('relocate_app').with_args( self.appid, 80, 443, 'the secret').and_return("OK") flexmock(SOAPpy) SOAPpy.should_receive('SOAPProxy').with_args('https://public1:17443') \ .and_return(fake_appcontroller) argv = [ '--keyname', self.keyname, '--appname', self.appid, '--http_port', '80', '--https_port', '443' ] options = ParseArgs(argv, self.function).args AppScaleTools.relocate_app(options)
def up(self): """ Starts an AppScale deployment with the configuration options from the AppScalefile in the current directory. Raises: AppScalefileException: If there is no AppScalefile in the current directory. """ contents = self.read_appscalefile() # If running in a cluster environment, we first need to set up SSH keys contents_as_yaml = yaml.safe_load(contents) if not LocalState.ensure_appscalefile_is_up_to_date(): contents = self.read_appscalefile() contents_as_yaml = yaml.safe_load(contents) # Construct a run-instances command from the file's contents command = [] for key, value in contents_as_yaml.items(): if key in ["EC2_ACCESS_KEY", "EC2_SECRET_KEY", "EC2_URL"]: os.environ[key] = value continue if value is True: command.append(str("--%s" % key)) elif value is False: pass else: if key == "ips_layout": command.append("--ips_layout") command.append(base64.b64encode(yaml.dump(value))) elif key == "disks": command.append("--disks") command.append(base64.b64encode(yaml.dump(value))) elif key == "user_commands": command.append("--user_commands") command.append(base64.b64encode(yaml.dump(value))) else: command.append(str("--%s" % key)) command.append(str("%s" % value)) run_instances_opts = ParseArgs(command, "appscale-run-instances").args if 'infrastructure' not in contents_as_yaml: # Generate a new keypair if necessary. if not self.valid_ssh_key(contents_as_yaml, run_instances_opts): add_keypair_command = [] if 'keyname' in contents_as_yaml: add_keypair_command.append('--keyname') add_keypair_command.append(str(contents_as_yaml['keyname'])) add_keypair_command.append('--ips_layout') add_keypair_command.append( base64.b64encode(yaml.dump(contents_as_yaml['ips_layout']))) add_keypair_opts = ParseArgs( add_keypair_command, 'appscale-add-keypair').args AppScaleTools.add_keypair(add_keypair_opts) AppScaleTools.run_instances(run_instances_opts)
def test_appscale_in_two_node_virt_deployment(self): # pretend that the place we're going to put logs into doesn't exist flexmock(os.path) os.path.should_call('exists') # set the fall-through os.path.should_receive('exists').with_args('/tmp/foobaz').and_return(False) # and mock out the mkdir operation flexmock(os) os.should_receive('mkdir').with_args('/tmp/foobaz').and_return() # next, mock out finding the login ip address os.path.should_receive('exists').with_args( LocalState.get_locations_json_location(self.keyname)).and_return(True) fake_nodes_json = flexmock(name="fake_nodes_json") fake_nodes_json.should_receive('read').and_return(json.dumps([{ "public_ip" : "public1", "private_ip" : "private1", "jobs" : ["shadow", "login"] }])) builtins = flexmock(sys.modules['__builtin__']) builtins.should_call('open') builtins.should_receive('open').with_args( LocalState.get_locations_json_location(self.keyname), 'r') \ .and_return(fake_nodes_json) # mock out writing the secret key to ~/.appscale, as well as reading it # later secret_key_location = LocalState.get_secret_key_location(self.keyname) fake_secret = flexmock(name="fake_secret") fake_secret.should_receive('read').and_return('the secret') builtins.should_receive('open').with_args(secret_key_location, 'r') \ .and_return(fake_secret) # and slip in a fake appcontroller to report on the two IP addrs fake_appcontroller = flexmock(name='fake_appcontroller') fake_appcontroller.should_receive('get_all_public_ips').with_args( 'the secret').and_return(json.dumps(['public1', 'public2'])) flexmock(SOAPpy) SOAPpy.should_receive('SOAPProxy').with_args('https://public1:17443') \ .and_return(fake_appcontroller) # fake the creation of the log directories locally os.should_receive('mkdir').with_args('/tmp/foobaz/public1').and_return() os.should_receive('mkdir').with_args('/tmp/foobaz/public2').and_return() # finally, fake the copying of the log files flexmock(subprocess) subprocess.should_receive('Popen').with_args(re.compile('/var/log/appscale'), shell=True, stdout=self.fake_temp_file, stderr=subprocess.STDOUT) \ .and_return(self.success) argv = [ "--keyname", self.keyname, "--location", "/tmp/foobaz" ] options = ParseArgs(argv, self.function).args AppScaleTools.gather_logs(options)
def test_describe_instances_with_two_nodes(self): # mock out writing the secret key to ~/.appscale, as well as reading it # later builtins = flexmock(sys.modules['__builtin__']) builtins.should_call('open') # set the fall-through secret_key_location = LocalState.get_secret_key_location(self.keyname) fake_secret = flexmock(name="fake_secret") fake_secret.should_receive('read').and_return('the secret') fake_secret.should_receive('write').and_return() builtins.should_receive('open').with_args(secret_key_location, 'r') \ .and_return(fake_secret) # mock out the SOAP call to the AppController and assume it succeeded fake_appcontroller = flexmock(name='fake_appcontroller') fake_appcontroller.should_receive('get_all_public_ips').with_args('the secret') \ .and_return(json.dumps(['public1', 'public2'])) fake_appcontroller.should_receive('status').with_args('the secret') \ .and_return('nothing interesting here') \ .and_return('Database is at not-up-yet') \ .and_return('Database is at 1.2.3.4') flexmock(SOAPpy) SOAPpy.should_receive('SOAPProxy').with_args('https://public1:17443') \ .and_return(fake_appcontroller) SOAPpy.should_receive('SOAPProxy').with_args('https://public2:17443') \ .and_return(fake_appcontroller) # mock out reading the locations.json file, and slip in our own json flexmock(os.path) os.path.should_call('exists') # set the fall-through os.path.should_receive('exists').with_args( LocalState.get_locations_json_location( self.keyname)).and_return(True) fake_nodes_json = flexmock(name="fake_nodes_json") fake_nodes_json.should_receive('read').and_return( json.dumps([ { "public_ip": "public1", "private_ip": "private1", "jobs": ["shadow", "login"] }, { "public_ip": "public2", "private_ip": "private2", "jobs": ["appengine"] }, ])) fake_nodes_json.should_receive('write').and_return() builtins.should_receive('open').with_args( LocalState.get_locations_json_location(self.keyname), 'r') \ .and_return(fake_nodes_json) # assume that there are two machines running in our deployment argv = ["--keyname", self.keyname] options = ParseArgs(argv, self.function).args AppScaleTools.describe_instances(options)
def test_remove_app_and_app_is_running(self): # mock out reading from stdin, and assume the user says 'YES' builtins = flexmock(sys.modules['__builtin__']) builtins.should_receive('raw_input').and_return('YES') # mock out reading the secret key builtins.should_call('open') # set the fall-through secret_key_location = LocalState.get_secret_key_location(self.keyname) fake_secret = flexmock(name="fake_secret") fake_secret.should_receive('read').and_return('the secret') builtins.should_receive('open').with_args(secret_key_location, 'r') \ .and_return(fake_secret) # mock out the SOAP call to the AppController and assume it succeeded fake_appcontroller = flexmock(name='fake_appcontroller') fake_appcontroller.should_receive('status').with_args('the secret') \ .and_return('Database is at public1') fake_appcontroller.should_receive('stop_app').with_args('blargapp', 'the secret').and_return('OK') fake_appcontroller.should_receive('is_app_running').with_args('blargapp', 'the secret').and_return(True).and_return(True).and_return(False) flexmock(SOAPpy) SOAPpy.should_receive('SOAPProxy').with_args('https://public1:17443') \ .and_return(fake_appcontroller) # mock out reading the locations.json file, and slip in our own json flexmock(os.path) os.path.should_call('exists') # set the fall-through os.path.should_receive('exists').with_args( LocalState.get_locations_json_location(self.keyname)).and_return(True) fake_nodes_json = flexmock(name="fake_nodes_json") fake_nodes_json.should_receive('read').and_return(json.dumps([{ "public_ip" : "public1", "private_ip" : "private1", "jobs" : ["shadow", "login"] }])) fake_nodes_json.should_receive('write').and_return() builtins.should_receive('open').with_args( LocalState.get_locations_json_location(self.keyname), 'r') \ .and_return(fake_nodes_json) # mock out calls to the UserAppServer and presume that the app does exist fake_userappserver = flexmock(name='fake_uaserver') fake_userappserver.should_receive('get_app_data').with_args( 'blargapp', 'the secret').and_return(json.dumps({ 'hosts' : { '192.168.1.1' : { 'http' : '80', 'https' : '443' }}})) SOAPpy.should_receive('SOAPProxy').with_args('https://public1:4343') \ .and_return(fake_userappserver) argv = [ "--appname", "blargapp", "--keyname", self.keyname ] options = ParseArgs(argv, self.function).args AppScaleTools.remove_app(options)
def test_describe_instances_with_two_nodes(self): # mock out writing the secret key to ~/.appscale, as well as reading it # later builtins = flexmock(sys.modules['__builtin__']) builtins.should_call('open') # set the fall-through secret_key_location = LocalState.get_secret_key_location(self.keyname) fake_secret = flexmock(name="fake_secret") fake_secret.should_receive('read').and_return('the secret') fake_secret.should_receive('write').and_return() builtins.should_receive('open').with_args(secret_key_location, 'r') \ .and_return(fake_secret) # mock out the SOAP call to the AppController and assume it succeeded fake_appcontroller = flexmock(name='fake_appcontroller') fake_appcontroller.should_receive('get_all_public_ips').with_args('the secret') \ .and_return(json.dumps(['public1', 'public2'])) fake_appcontroller.should_receive('status').with_args('the secret') \ .and_return('nothing interesting here') \ .and_return('Database is at not-up-yet') \ .and_return('Database is at 1.2.3.4') flexmock(SOAPpy) SOAPpy.should_receive('SOAPProxy').with_args('https://public1:17443') \ .and_return(fake_appcontroller) SOAPpy.should_receive('SOAPProxy').with_args('https://public2:17443') \ .and_return(fake_appcontroller) # mock out reading the locations.json file, and slip in our own json flexmock(os.path) os.path.should_call('exists') # set the fall-through os.path.should_receive('exists').with_args( LocalState.get_locations_json_location(self.keyname)).and_return(True) fake_nodes_json = flexmock(name="fake_nodes_json") fake_nodes_json.should_receive('read').and_return(json.dumps([{ "public_ip" : "public1", "private_ip" : "private1", "jobs" : ["shadow", "login"] }, { "public_ip" : "public2", "private_ip" : "private2", "jobs" : ["appengine"] }, ])) fake_nodes_json.should_receive('write').and_return() builtins.should_receive('open').with_args( LocalState.get_locations_json_location(self.keyname), 'r') \ .and_return(fake_nodes_json) # assume that there are two machines running in our deployment argv = [ "--keyname", self.keyname ] options = ParseArgs(argv, self.function).args AppScaleTools.describe_instances(options)
def test_remove_app_and_app_is_running(self): # mock out reading from stdin, and assume the user says 'YES' builtins = flexmock(sys.modules['__builtin__']) builtins.should_receive('raw_input').and_return('YES') # mock out reading the secret key builtins.should_call('open') # set the fall-through secret_key_location = LocalState.get_secret_key_location(self.keyname) fake_secret = flexmock(name="fake_secret") fake_secret.should_receive('read').and_return('the secret') builtins.should_receive('open').with_args(secret_key_location, 'r') \ .and_return(fake_secret) # mock out the SOAP call to the AppController and assume it succeeded fake_appcontroller = flexmock(name='fake_appcontroller') fake_appcontroller.should_receive('status').with_args('the secret') \ .and_return('Database is at public1') fake_appcontroller.should_receive('stop_app').with_args( 'blargapp', 'the secret').and_return('OK') fake_appcontroller.should_receive('is_app_running').with_args( 'blargapp', 'the secret').and_return(True).and_return(True).and_return(False) flexmock(SOAPpy) SOAPpy.should_receive('SOAPProxy').with_args('https://public1:17443') \ .and_return(fake_appcontroller) # mock out reading the locations.json file, and slip in our own json flexmock(os.path) os.path.should_call('exists') # set the fall-through os.path.should_receive('exists').with_args( LocalState.get_locations_json_location( self.keyname)).and_return(True) fake_nodes_json = flexmock(name="fake_nodes_json") fake_nodes_json.should_receive('read').and_return( json.dumps([{ "public_ip": "public1", "private_ip": "private1", "jobs": ["shadow", "login"] }])) fake_nodes_json.should_receive('write').and_return() builtins.should_receive('open').with_args( LocalState.get_locations_json_location(self.keyname), 'r') \ .and_return(fake_nodes_json) # mock out calls to the UserAppServer and presume that the app does exist fake_userappserver = flexmock(name='fake_uaserver') fake_userappserver.should_receive('get_app_data').with_args( 'blargapp', 'the secret').and_return('\nnum_ports:2\n') SOAPpy.should_receive('SOAPProxy').with_args('https://public1:4343') \ .and_return(fake_userappserver) argv = ["--appname", "blargapp", "--keyname", self.keyname] options = ParseArgs(argv, self.function).args AppScaleTools.remove_app(options)
def test_reset_password_for_user_that_exists(self): # put in a mock for reading the secret file builtins = flexmock(sys.modules['__builtin__']) builtins.should_call('open') # set the fall-through secret_key_location = LocalState.get_secret_key_location(self.keyname) fake_secret = flexmock(name="fake_secret") fake_secret.should_receive('read').and_return('the secret') builtins.should_receive('open').with_args(secret_key_location, 'r') \ .and_return(fake_secret) # mock out reading the username and new password from the user builtins.should_receive('raw_input').and_return('*****@*****.**') flexmock(getpass) getpass.should_receive('getpass').and_return('the password') # mock out finding the login node's IP address from the json file flexmock(os.path) os.path.should_call('exists') # set the fall-through os.path.should_receive('exists').with_args( LocalState.get_locations_json_location( self.keyname)).and_return(True) fake_nodes_json = flexmock(name="fake_secret") fake_nodes_json.should_receive('read').and_return( json.dumps([{ 'public_ip': 'public1', 'private_ip': 'private1', 'jobs': ['login', 'db_master'] }])) builtins.should_receive('open').with_args( LocalState.get_locations_json_location(self.keyname), 'r') \ .and_return(fake_nodes_json) # mock out grabbing the userappserver ip from an appcontroller fake_appcontroller = flexmock(name='fake_appcontroller') fake_appcontroller.should_receive('status').with_args('the secret') \ .and_return('nothing interesting here') \ .and_return('Database is at not-up-yet') \ .and_return('Database is at public1') flexmock(SOAPpy) SOAPpy.should_receive('SOAPProxy').with_args('https://*****:*****@foo.goo', str, 'the secret').and_return('true') SOAPpy.should_receive('SOAPProxy').with_args('https://public1:4343') \ .and_return(fake_userappserver) argv = ["--keyname", self.keyname] options = ParseArgs(argv, self.function).args AppScaleTools.reset_password(options)
def test_reset_password_for_user_that_exists(self): # put in a mock for reading the secret file builtins = flexmock(sys.modules['__builtin__']) builtins.should_call('open') # set the fall-through secret_key_location = LocalState.get_secret_key_location(self.keyname) fake_secret = flexmock(name="fake_secret") fake_secret.should_receive('read').and_return('the secret') builtins.should_receive('open').with_args(secret_key_location, 'r') \ .and_return(fake_secret) # mock out reading the username and new password from the user builtins.should_receive('raw_input').and_return('*****@*****.**') flexmock(getpass) getpass.should_receive('getpass').and_return('the password') # mock out finding the login node's IP address from the json file flexmock(os.path) os.path.should_call('exists') # set the fall-through os.path.should_receive('exists').with_args( LocalState.get_locations_json_location(self.keyname)).and_return(True) fake_nodes_json = flexmock(name="fake_secret") fake_nodes_json.should_receive('read').and_return(json.dumps([{ 'public_ip' : 'public1', 'private_ip' : 'private1', 'jobs' : ['login', 'db_master'] }])) builtins.should_receive('open').with_args( LocalState.get_locations_json_location(self.keyname), 'r') \ .and_return(fake_nodes_json) # mock out grabbing the userappserver ip from an appcontroller fake_appcontroller = flexmock(name='fake_appcontroller') fake_appcontroller.should_receive('status').with_args('the secret') \ .and_return('nothing interesting here') \ .and_return('Database is at not-up-yet') \ .and_return('Database is at public1') flexmock(SOAPpy) SOAPpy.should_receive('SOAPProxy').with_args('https://*****:*****@foo.goo', str, 'the secret').and_return('true') SOAPpy.should_receive('SOAPProxy').with_args('https://public1:4343') \ .and_return(fake_userappserver) argv = [ "--keyname", self.keyname ] options = ParseArgs(argv, self.function).args AppScaleTools.reset_password(options)
def up(self): """Starts an AppScale deployment with the configuration options from the AppScalefile in the current directory. Raises: AppScalefileException: If there is no AppScalefile in the current directory. """ contents = self.read_appscalefile() # If running in a cluster environment, we first need to set up SSH keys contents_as_yaml = yaml.safe_load(contents) if "ips_layout" in contents_as_yaml: ips_layout = base64.b64encode( yaml.dump(contents_as_yaml["ips_layout"])) if not "infrastructure" in contents_as_yaml: # Only run add-keypair if there is no ssh key present, # or if it doesn't log into all the machines specified. if not self.valid_ssh_key(contents_as_yaml): add_keypair_command = [] if "keyname" in contents_as_yaml: add_keypair_command.append("--keyname") add_keypair_command.append(str( contents_as_yaml["keyname"])) add_keypair_command.append("--ips_layout") add_keypair_command.append(ips_layout) options = ParseArgs(add_keypair_command, "appscale-add-keypair").args AppScaleTools.add_keypair(options) # Construct a run-instances command from the file's contents command = [] for key, value in contents_as_yaml.items(): if key in ["EC2_ACCESS_KEY", "EC2_SECRET_KEY", "EC2_URL"]: os.environ[key] = value continue if value is True: command.append(str("--%s" % key)) elif value is False: pass else: if key == "ips_layout": command.append("--ips_layout") command.append(ips_layout) else: command.append(str("--%s" % key)) command.append(str("%s" % value)) # Finally, call AppScaleTools.run_instances options = ParseArgs(command, "appscale-run-instances").args AppScaleTools.run_instances(options)
def testGetLogsWithKeyname(self): # calling 'appscale logs dir' with a keyname should produce # a command to exec with the --keyname flag appscale = AppScale() contents = {"keyname": "boo"} yaml_dumped_contents = yaml.dump(contents) self.addMockForAppScalefile(appscale, yaml_dumped_contents) # mock out the actual call to appscale-gather-logs flexmock(AppScaleTools) AppScaleTools.should_receive('run_instances') self.assertRaises(BadConfigurationException, appscale.logs, '/baz')
def testGetLogsWithNoKeyname(self): # calling 'appscale logs dir' with no keyname should produce # a command to exec without the --keyname flag appscale = AppScale() contents = { } yaml_dumped_contents = yaml.dump(contents) self.addMockForAppScalefile(appscale, yaml_dumped_contents) # mock out the actual call to appscale-gather-logs flexmock(AppScaleTools) AppScaleTools.should_receive('run_instances') appscale.logs('/baz')
def up(self): """Starts an AppScale deployment with the configuration options from the AppScalefile in the current directory. Raises: AppScalefileException: If there is no AppScalefile in the current directory. """ contents = self.read_appscalefile() # If running in a cluster environment, we first need to set up SSH keys contents_as_yaml = yaml.safe_load(contents) if "ips_layout" in contents_as_yaml: ips_layout = base64.b64encode(yaml.dump(contents_as_yaml["ips_layout"])) if not "infrastructure" in contents_as_yaml: # Only run add-keypair if there is no ssh key present, # or if it doesn't log into all the machines specified. if not self.valid_ssh_key(contents_as_yaml): add_keypair_command = [] if "keyname" in contents_as_yaml: add_keypair_command.append("--keyname") add_keypair_command.append(str(contents_as_yaml["keyname"])) add_keypair_command.append("--ips_layout") add_keypair_command.append(ips_layout) options = ParseArgs(add_keypair_command, "appscale-add-keypair").args AppScaleTools.add_keypair(options) # Construct a run-instances command from the file's contents command = [] for key, value in contents_as_yaml.items(): if key in ["EC2_ACCESS_KEY", "EC2_SECRET_KEY", "EC2_URL"]: os.environ[key] = value continue if value is True: command.append(str("--%s" % key)) elif value is False: pass else: if key == "ips_layout": command.append("--ips_layout") command.append(ips_layout) else: command.append(str("--%s" % key)) command.append(str("%s" % value)) # Finally, call AppScaleTools.run_instances options = ParseArgs(command, "appscale-run-instances").args AppScaleTools.run_instances(options)
def testGetLogsWithKeyname(self): # calling 'appscale logs dir' with a keyname should produce # a command to exec with the --keyname flag appscale = AppScale() contents = { "keyname" : "boo" } yaml_dumped_contents = yaml.dump(contents) self.addMockForAppScalefile(appscale, yaml_dumped_contents) # mock out the actual call to appscale-gather-logs flexmock(AppScaleTools) AppScaleTools.should_receive('run_instances') self.assertRaises(BadConfigurationException, appscale.logs, '/baz')
def test_appscale_in_one_node_virt_deployment_with_login_override(self): # let's say that appscale isn't already running self.local_state.should_receive('ensure_appscale_isnt_running').and_return() self.local_state.should_receive('make_appscale_directory').and_return() self.local_state.should_receive('update_local_metadata').and_return() self.local_state.should_receive('get_local_nodes_info').and_return(json.loads( json.dumps([{ "public_ip" : "1.2.3.4", "private_ip" : "1.2.3.4", "jobs" : ["shadow", "login"] }]))) self.local_state.should_receive('get_secret_key').and_return("fookey") flexmock(RemoteHelper) RemoteHelper.should_receive('start_head_node')\ .and_return(('1.2.3.4','i-ABCDEFG')) RemoteHelper.should_receive('sleep_until_port_is_open').and_return() RemoteHelper.should_receive('copy_local_metadata').and_return() RemoteHelper.should_receive('create_user_accounts').and_return() RemoteHelper.should_receive('wait_for_machines_to_finish_loading')\ .and_return() acc = flexmock(AppControllerClient) acc.should_receive('is_initialized').and_return(True) uac = flexmock(UserAppClient) uac.should_receive('does_user_exist').and_return(False) flexmock(UserAppClient).should_receive('set_admin_role').and_return() # don't use a 192.168.X.Y IP here, since sometimes we set our virtual # machines to boot with those addresses (and that can mess up our tests). ips_layout = yaml.safe_load(""" master : 1.2.3.4 database: 1.2.3.4 zookeeper: 1.2.3.4 appengine: 1.2.3.4 """) argv = [ "--ips_layout", base64.b64encode(yaml.dump(ips_layout)), "--keyname", self.keyname, "--test", "--login_host", "www.booscale.com" ] options = ParseArgs(argv, self.function).args AppScaleTools.run_instances(options)
def test_appscale_in_one_node_virt_deployment_with_login_override(self): # let's say that appscale isn't already running local_state = flexmock(LocalState) local_state.should_receive('ensure_appscale_isnt_running').and_return() local_state.should_receive('make_appscale_directory').and_return() local_state.should_receive('update_local_metadata').and_return() local_state.should_receive('get_local_nodes_info').and_return(json.loads( json.dumps([{ "public_ip" : "1.2.3.4", "private_ip" : "1.2.3.4", "jobs" : ["shadow", "login"] }]))) local_state.should_receive('get_secret_key').and_return("fookey") flexmock(RemoteHelper) RemoteHelper.should_receive('start_head_node')\ .and_return(('1.2.3.4','i-ABCDEFG')) RemoteHelper.should_receive('sleep_until_port_is_open').and_return() RemoteHelper.should_receive('copy_local_metadata').and_return() RemoteHelper.should_receive('create_user_accounts').and_return() RemoteHelper.should_receive('wait_for_machines_to_finish_loading')\ .and_return() acc = flexmock(AppControllerClient) acc.should_receive('get_uaserver_host').and_return('host') flexmock(UserAppClient).should_receive('set_admin_role').and_return() # don't use a 192.168.X.Y IP here, since sometimes we set our virtual # machines to boot with those addresses (and that can mess up our tests). ips_layout = yaml.safe_load(""" master : 1.2.3.4 database: 1.2.3.4 zookeeper: 1.2.3.4 appengine: 1.2.3.4 """) argv = [ "--ips_layout", base64.b64encode(yaml.dump(ips_layout)), "--keyname", self.keyname, "--test", "--login_host", "www.booscale.com" ] options = ParseArgs(argv, self.function).args AppScaleTools.run_instances(options)
def appscale_down(self): """ Terminates a currently running deployment of AppScale. Calls on the AppScale tools by building an argument list, which varies based on the deployment type. Returns: True on success, False otherwise. """ logging.debug("Starting AppScale down.") self.state = self.TERMINATING_STATE # We capture the stdout and stderr of the tools and use it to calculate # the percentage towards completion. old_stdout = sys.stdout old_stderr = sys.stderr sys.stdout = self.std_out_capture sys.stderr = self.std_err_capture terminate_args = ['--keyname', self.keyname, "--verbose"] if self.deployment_type == CLOUD: terminate_args.extend([ "--EC2_SECRET_KEY", self.ec2_secret, "--EC2_ACCESS_KEY", self.ec2_access, "--EC2_URL", self.ec2_url, "--test" ]) try: logging.info("Starting terminate instances.") options = parse_args.ParseArgs(terminate_args, "appscale-terminate-instances").args AppScaleTools.terminate_instances(options) self.state = self.TERMINATED_STATE logging.info("AppScale terminate instances successfully ran!") except BadConfigurationException as bad_config: self.state = self.ERROR_STATE logging.exception(bad_config) self.err_message = "Bad configuration. Unable to terminate AppScale. " \ "{0}".format(bad_config) except Exception as exception: self.state = self.ERROR_STATE logging.exception(exception) self.err_message = "Exception when terminating: {0}".format( exception) finally: sys.stdout = old_stdout sys.stderr = old_stderr return self.state == self.TERMINATED_STATE
def appscale_down(self): """ Terminates a currently running deployment of AppScale. Calls on the AppScale tools by building an argument list, which varies based on the deployment type. Returns: True on success, False otherwise. """ logging.debug("Starting AppScale down.") self.state = self.TERMINATING_STATE # We capture the stdout and stderr of the tools and use it to calculate # the percentage towards completion. old_stdout = sys.stdout old_stderr = sys.stderr sys.stdout = self.std_out_capture sys.stderr = self.std_err_capture terminate_args = ['--keyname', self.keyname, "--verbose"] if self.deployment_type == CLOUD: terminate_args.extend(["--EC2_SECRET_KEY", self.ec2_secret, "--EC2_ACCESS_KEY", self.ec2_access, "--EC2_URL", self.ec2_url, "--test"]) try: logging.info("Starting terminate instances.") options = parse_args.ParseArgs(terminate_args, "appscale-terminate-instances").args AppScaleTools.terminate_instances(options) self.state = self.TERMINATED_STATE logging.info("AppScale terminate instances successfully ran!") except BadConfigurationException as bad_config: self.state = self.ERROR_STATE logging.exception(bad_config) self.err_message = "Bad configuration. Unable to terminate AppScale. " \ "{0}".format(bad_config) except Exception as exception: self.state = self.ERROR_STATE logging.exception(exception) self.err_message = "Exception when terminating: {0}".format(exception) finally: sys.stdout = old_stdout sys.stderr = old_stderr return self.state == self.TERMINATED_STATE
def get(self, property_regex): """ 'get' provides a cleaner experience for users than the appscale-get-property command, by using the configuration options present in the AppScalefile found in the current working directory. Args: property_regex: A regular expression indicating which AppController properties should be retrieved. Raises: AppScalefileException: If there is no AppScalefile in the current working directory. """ contents = self.read_appscalefile() contents_as_yaml = yaml.safe_load(contents) # construct the appscale-get-property command command = [] if 'keyname' in contents_as_yaml: command.append("--keyname") command.append(contents_as_yaml["keyname"]) command.append("--property") command.append(property_regex) # and exec it options = ParseArgs(command, "appscale-get-property").args return AppScaleTools.get_property(options)
def testUpWithEC2EnvironmentVariables(self): # if the user wants us to use their EC2 credentials when running AppScale, # we should make sure they get set appscale = AppScale() # Mock out the actual file reading itself, and slip in a YAML-dumped # file contents = { 'infrastructure' : 'ec2', 'machine' : 'ami-ABCDEFG', 'keyname' : 'bookey', 'group' : 'boogroup', 'min' : 1, 'max' : 1, 'EC2_ACCESS_KEY' : 'access key', 'EC2_SECRET_KEY' : 'secret key', 'zone' : 'my-zone-1b' } yaml_dumped_contents = yaml.dump(contents) self.addMockForAppScalefile(appscale, yaml_dumped_contents) flexmock(os.path) os.path.should_call('exists') os.path.should_receive('exists').with_args( '/boo/' + appscale.APPSCALEFILE).and_return(True) # finally, pretend that our ec2 zone/image to use exist fake_ec2 = flexmock(name="fake_ec2") fake_ec2.should_receive('get_all_instances') fake_ec2.should_receive('get_all_zones').with_args('my-zone-1b') \ .and_return('anything') fake_ec2.should_receive('get_image').with_args('ami-ABCDEFG') \ .and_return() flexmock(boto.ec2) boto.ec2.should_receive('connect_to_region').with_args('my-zone-1', aws_access_key_id='access key', aws_secret_access_key='secret key').and_return(fake_ec2) # finally, mock out the actual appscale-run-instances call flexmock(AppScaleTools) AppScaleTools.should_receive('run_instances') appscale.up() self.assertEquals('access key', os.environ['EC2_ACCESS_KEY']) self.assertEquals('secret key', os.environ['EC2_SECRET_KEY'])
def testUpWithCloudAppScalefile(self): # calling 'appscale up' if there is an AppScalefile present # should call appscale-run-instances with the given config # params. here, we assume that the file is intended for use # on EC2 appscale = AppScale() # Mock out the actual file reading itself, and slip in a YAML-dumped # file contents = { 'infrastructure' : 'ec2', 'machine' : 'ami-ABCDEFG', 'keyname' : 'bookey', 'group' : 'boogroup', 'min' : 1, 'max' : 1, 'zone' : 'my-zone-1b' } yaml_dumped_contents = yaml.dump(contents) self.addMockForAppScalefile(appscale, yaml_dumped_contents) flexmock(os.path) os.path.should_call('exists') os.path.should_receive('exists').with_args( '/boo/' + appscale.APPSCALEFILE).and_return(True) # throw in some mocks for the argument parsing for credential in EC2Agent.REQUIRED_CREDENTIALS: os.environ[credential] = "baz" # finally, pretend that our ec2 zone and image exists fake_ec2 = flexmock(name="fake_ec2") fake_ec2.should_receive('get_all_instances') fake_ec2.should_receive('get_all_zones').with_args('my-zone-1b') \ .and_return('anything') fake_ec2.should_receive('get_image').with_args('ami-ABCDEFG') \ .and_return() flexmock(boto.ec2) boto.ec2.should_receive('connect_to_region').with_args('my-zone-1', aws_access_key_id='baz', aws_secret_access_key='baz').and_return(fake_ec2) # finally, mock out the actual appscale-run-instances call flexmock(AppScaleTools) AppScaleTools.should_receive('run_instances') appscale.up()
def run_appscale(self): """ Exectutes the appscale tools once the configuration has been set. """ self.status = RUNNING_STATE options = ParseArgs(self.args, "appscale-run-instances").args old_stdout = sys.stdout old_stderr = sys.stderr sys.stdout = self.std_out_capture sys.stderr = self.std_err_capture try: AppScaleTools.run_instances(options) except Exception as e: self.status = ERROR_STATE self.err_message = str(e) finally: sys.stdout = old_stdout sys.stderr = old_stderr
def run_appscale(self): """ Executes the appscale tools with deployment specific arguments. Returns: True on success, False otherwise. """ logging.info("Tools arguments: {0}".format(str(self.args))) self.state = self.RUNNING_STATE old_stdout = sys.stdout old_stderr = sys.stderr try: options = parse_args.ParseArgs(self.args, "appscale-run-instances").args sys.stdout = self.std_out_capture sys.stderr = self.std_err_capture AppScaleTools.run_instances(options) logging.info("AppScale run instances was successful!") self.state = self.COMPLETE_STATE self.set_status_link() except BadConfigurationException as bad_config: self.state = self.ERROR_STATE logging.exception(bad_config) self.err_message = "Bad configuration. {0}".format(bad_config) except Exception as exception: self.state = self.ERROR_STATE logging.exception(exception) self.err_message = "Exception--{0}".format(exception) except SystemExit as sys_exit: self.state = self.ERROR_STATE logging.error(str(sys_exit)) self.err_message = str( "Error with given arguments caused system exit.") finally: sys.stdout = old_stdout sys.stderr = old_stderr return self.state == self.COMPLETE_STATE
def testUpWithClusterAppScalefile(self): # calling 'appscale up' if there is an AppScalefile present # should call appscale-run-instances with the given config # params. here, we assume that the file is intended for use # on a virtualized cluster appscale = AppScale() # Mock out the actual file reading itself, and slip in a YAML-dumped # file contents = { 'ips_layout': { 'master': 'ip1', 'appengine': 'ip1', 'database': 'ip2', 'zookeeper': 'ip2' }, 'keyname': 'boobazblarg' } yaml_dumped_contents = yaml.dump(contents) base64_ips_layout = base64.b64encode(yaml.dump(contents["ips_layout"])) self.addMockForAppScalefile(appscale, yaml_dumped_contents) # for this test, let's say that we don't have an SSH key already # set up for ip1 and ip2 # TODO(cgb): Add in tests where we have a key for ip1 but not ip2, # and the case where we have a key but it doesn't work flexmock(os.path) key_path = os.path.expanduser('~/.appscale/boobazblarg.key') os.path.should_call('exists') os.path.should_receive('exists').with_args(key_path).and_return( False).once() # finally, mock out the actual appscale tools calls. since we're running # via a cluster, this means we call add-keypair to set up SSH keys, then # run-instances to start appscale flexmock(AppScaleTools) AppScaleTools.should_receive('add_keypair') AppScaleTools.should_receive('run_instances') appscale.up()
def testUpWithMalformedClusterAppScalefile(self): # if we try to use an IPs layout that isn't a dictionary, we should throw up # and die appscale = AppScale() # Mock out the actual file reading itself, and slip in a YAML-dumped # file, with an IPs layout that is a str contents = { 'ips_layout': "'master' 'ip1' 'appengine' 'ip1'", 'keyname': 'boobazblarg' } yaml_dumped_contents = yaml.dump(contents) base64_ips_layout = base64.b64encode(yaml.dump(contents["ips_layout"])) self.addMockForAppScalefile(appscale, yaml_dumped_contents) # finally, mock out the actual appscale tools calls. since we're running # via a cluster, this means we call add-keypair to set up SSH keys, then # run-instances to start appscale flexmock(AppScaleTools) AppScaleTools.should_receive('add_keypair') self.assertRaises(BadConfigurationException, appscale.up)
def destroy(self): """'destroy' 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. 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 "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 'keyname' in contents_as_yaml: command.append("--keyname") command.append(contents_as_yaml['keyname']) if 'verbose' in contents_as_yaml and contents_as_yaml[ 'verbose'] == True: command.append("--verbose") 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)
def status(self): """'status' is a more accessible way to query the state of the AppScale deployment than 'appscale-describe-instances', and calls it with the parameters in the user's AppScalefile. Raises: AppScalefileException: If there is no AppScalefile in the current directory. """ contents = self.read_appscalefile() # Construct a describe-instances command from the file's contents command = [] contents_as_yaml = yaml.safe_load(contents) if 'keyname' in contents_as_yaml: command.append("--keyname") command.append(contents_as_yaml['keyname']) # Finally, exec the command. Don't worry about validating it - # appscale-describe-instances will do that for us. options = ParseArgs(command, "appscale-describe-instances").args AppScaleTools.describe_instances(options)
def status(self): """ 'status' is a more accessible way to query the state of the AppScale deployment than 'appscale-describe-instances', and calls it with the parameters in the user's AppScalefile. Raises: AppScalefileException: If there is no AppScalefile in the current directory. """ contents = self.read_appscalefile() # Construct a describe-instances command from the file's contents command = [] contents_as_yaml = yaml.safe_load(contents) if 'keyname' in contents_as_yaml: command.append("--keyname") command.append(contents_as_yaml['keyname']) # Finally, exec the command. Don't worry about validating it - # appscale-describe-instances will do that for us. options = ParseArgs(command, "appscale-describe-instances").args AppScaleTools.describe_instances(options)
def relocate(self, appid, http_port, https_port): """ 'relocate' 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: appid: A str indicating the name of the application to relocate. http_port: An int that indicates what port should serve HTTP traffic for this application. https_port: An int that indicates what port should serve HTTPS traffic for this application. Raises: AppScalefileException: If there is no AppScalefile in the current working directory. """ contents = self.read_appscalefile() contents_as_yaml = yaml.safe_load(contents) # Construct the appscale-relocate-app command from argv and the contents of # the AppScalefile. command = [] if 'keyname' in contents_as_yaml: command.append("--keyname") command.append(contents_as_yaml["keyname"]) command.append("--appname") command.append(appid) command.append("--http_port") command.append(str(http_port)) command.append("--https_port") command.append(str(https_port)) # and exec it options = ParseArgs(command, "appscale-relocate-app").args AppScaleTools.relocate_app(options)
def testSetPropertyWithAppScalefile(self): # calling 'appscale set' with an AppScalefile in the local # directory should collect any parameters needed for the # 'appscale-get-property' command and then exec it appscale = AppScale() # Mock out the actual file reading itself, and slip in a YAML-dumped # file contents = { 'infrastructure' : 'ec2', 'machine' : 'ami-ABCDEFG', 'keyname' : 'bookey', 'group' : 'boogroup', 'verbose' : True, 'min' : 1, 'max' : 1 } yaml_dumped_contents = yaml.dump(contents) self.addMockForAppScalefile(appscale, yaml_dumped_contents) # finally, mock out the actual appscale-set-property call flexmock(AppScaleTools) AppScaleTools.should_receive('set_property') appscale.set('key', 'value')