def test_from_directory(self): # Ensure an exception is raised if there are no configuration candidates. shortest_path_func = 'appscale.tools.admin_api.version.shortest_directory_path' with patch(shortest_path_func, side_effect=lambda fn, path: None): with self.assertRaises(AppEngineConfigException): Version.from_directory('/example/guestbook') with patch(shortest_path_func, side_effect=lambda fn, path: '/example/guestbook/app.yaml'): open_path = 'appscale.tools.admin_api.version.open' with patch(open_path, mock_open(read_data=SIMPLE_APP_YAML)): version = Version.from_yaml_file('/example/app.yaml') self.assertEqual(version.runtime, 'python27')
def test_from_directory(self): # Ensure an exception is raised if there are no configuration candidates. shortest_path_func = 'appscale.tools.admin_api.version.shortest_directory_path' with patch(shortest_path_func, side_effect=lambda fn, path: None): with self.assertRaises(AppEngineConfigException): Version.from_directory('/example/guestbook') with patch(shortest_path_func, side_effect=lambda fn, path: '/example/guestbook/app.yaml'): open_path = 'appscale.tools.admin_api.version.open' with patch(open_path, mock_open(read_data=SIMPLE_APP_YAML)): version = Version.from_yaml_file('/example/app.yaml') self.assertEqual(version.runtime, 'python27')
def update_dispatch(cls, source_location, keyname, project_id): """ Updates an application's dispatch routing rules from the configuration file. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. """ if cls.TAR_GZ_REGEX.search(source_location): fetch_function = utils.config_from_tar_gz version = Version.from_tar_gz(source_location) elif cls.ZIP_REGEX.search(source_location): fetch_function = utils.config_from_zip version = Version.from_zip(source_location) elif os.path.isdir(source_location): fetch_function = utils.config_from_dir version = Version.from_directory(source_location) elif source_location.endswith('.yaml'): fetch_function = utils.config_from_dir version = Version.from_yaml_file(source_location) source_location = os.path.dirname(source_location) else: raise BadConfigurationException( '{} must be a directory, tar.gz, or zip'.format(source_location)) if project_id: version.project_id = project_id dispatch_rules = utils.dispatch_from_yaml(source_location, fetch_function) if dispatch_rules is None: return AppScaleLogger.log('Updating dispatch for {}'.format(version.project_id)) load_balancer_ip = LocalState.get_host_with_role(keyname, 'load_balancer') secret_key = LocalState.get_secret_key(keyname) admin_client = AdminClient(load_balancer_ip, secret_key) operation_id = admin_client.update_dispatch(version.project_id, dispatch_rules) # Check on the operation. AppScaleLogger.log("Please wait for your dispatch to be updated.") deadline = time.time() + cls.MAX_OPERATION_TIME while True: if time.time() > deadline: raise AppScaleException('The operation took too long.') operation = admin_client.get_operation(version.project_id, operation_id) if not operation['done']: time.sleep(1) continue if 'error' in operation: raise AppScaleException(operation['error']['message']) dispatch_rules = operation['response']['dispatchRules'] break AppScaleLogger.verbose( "The following dispatchRules have been applied to your application's " "configuration : {}".format(dispatch_rules)) AppScaleLogger.success('Dispatch has been updated for {}'.format( version.project_id))
def update_queues(cls, source_location, keyname, project_id): """ Updates a project's queues from the configuration file. Args: source_location: A string specifying the location of the source code. keyname: A string specifying the key name. project_id: A string specifying the project ID. """ if cls.TAR_GZ_REGEX.search(source_location): fetch_function = utils.config_from_tar_gz version = Version.from_tar_gz(source_location) elif cls.ZIP_REGEX.search(source_location): fetch_function = utils.config_from_zip version = Version.from_zip(source_location) elif os.path.isdir(source_location): fetch_function = utils.config_from_dir version = Version.from_directory(source_location) elif source_location.endswith('.yaml'): fetch_function = utils.config_from_dir version = Version.from_yaml_file(source_location) source_location = os.path.dirname(source_location) else: raise BadConfigurationException( '{} must be a directory, tar.gz, or zip'.format(source_location)) if project_id: version.project_id = project_id queue_config = fetch_function('queue.yaml', source_location) if queue_config is None: queue_config = fetch_function('queue.xml', source_location) # If the source does not have a queue configuration file, do nothing. if queue_config is None: return queues = utils.queues_from_xml(queue_config) else: queues = yaml.safe_load(queue_config) AppScaleLogger.log('Updating queues') for queue in queues.get('queue', []): if 'bucket_size' in queue or 'max_concurrent_requests' in queue: AppScaleLogger.warn('Queue configuration uses unsupported rate options' ' (bucket size or max concurrent requests)') break load_balancer_ip = LocalState.get_host_with_role(keyname, 'load_balancer') secret_key = LocalState.get_secret_key(keyname) admin_client = AdminClient(load_balancer_ip, secret_key) admin_client.update_queues(version.project_id, queues)
def update_cron(cls, source_location, keyname, project_id): """ Updates a project's cron jobs from the configuration file. Args: source_location: A string specifying the location of the source code. keyname: A string specifying the key name. project_id: A string specifying the project ID. """ if cls.TAR_GZ_REGEX.search(source_location): fetch_function = utils.config_from_tar_gz version = Version.from_tar_gz(source_location) elif cls.ZIP_REGEX.search(source_location): fetch_function = utils.config_from_zip version = Version.from_zip(source_location) elif os.path.isdir(source_location): fetch_function = utils.config_from_dir version = Version.from_directory(source_location) elif source_location.endswith('.yaml'): fetch_function = utils.config_from_dir version = Version.from_yaml_file(source_location) source_location = os.path.dirname(source_location) else: raise BadConfigurationException( '{} must be a directory, tar.gz, or zip'.format(source_location)) if project_id: version.project_id = project_id cron_config = fetch_function('cron.yaml', source_location) if cron_config is None: cron_config = fetch_function('cron.xml', source_location) # If the source does not have a cron configuration file, do nothing. if cron_config is None: return cron_jobs = utils.cron_from_xml(cron_config) else: cron_jobs = yaml.safe_load(cron_config) AppScaleLogger.log('Updating cron jobs') load_balancer_ip = LocalState.get_host_with_role(keyname, 'load_balancer') secret_key = LocalState.get_secret_key(keyname) admin_client = AdminClient(load_balancer_ip, secret_key) admin_client.update_cron(version.project_id, cron_jobs)
def upload_app(cls, options): """Uploads the given App Engine application into AppScale. Args: options: A Namespace that has fields for each parameter that can be passed in via the command-line interface. Returns: A tuple containing the host and port where the application is serving traffic from. """ custom_service_yaml = None if cls.TAR_GZ_REGEX.search(options.file): file_location = LocalState.extract_tgz_app_to_dir(options.file) created_dir = True version = Version.from_tar_gz(options.file) elif cls.ZIP_REGEX.search(options.file): file_location = LocalState.extract_zip_app_to_dir(options.file) created_dir = True version = Version.from_zip(options.file) elif os.path.isdir(options.file): file_location = options.file created_dir = False version = Version.from_directory(options.file) elif options.file.endswith('.yaml'): file_location = os.path.dirname(options.file) created_dir = False version = Version.from_yaml_file(options.file) custom_service_yaml = options.file else: raise AppEngineConfigException('{0} is not a tar.gz file, a zip file, ' \ 'or a directory. Please try uploading either a tar.gz file, a zip ' \ 'file, or a directory.'.format(options.file)) if options.project: if version.runtime == 'java': raise BadConfigurationException("AppScale doesn't support --project for" "Java yet. Please specify the application id in appengine-web.xml.") version.project_id = options.project if version.project_id is None: if version.config_type == 'app.yaml': message = 'Specify --project or define "application" in your app.yaml' else: message = 'Define "application" in your appengine-web.xml' raise AppEngineConfigException(message) # Let users know that versions are not supported yet. AppEngineHelper.warn_if_version_defined(version, options.test) AppEngineHelper.validate_app_id(version.project_id) extras = {} if version.runtime == 'go': extras = LocalState.get_extra_go_dependencies(options.file, options.test) if (version.runtime == 'java' and AppEngineHelper.is_sdk_mismatch(file_location)): AppScaleLogger.warn( 'AppScale did not find the correct SDK jar versions in your app. The ' 'current supported SDK version is ' '{}.'.format(AppEngineHelper.SUPPORTED_SDK_VERSION)) head_node_public_ip = LocalState.get_host_with_role( options.keyname, 'shadow') secret_key = LocalState.get_secret_key(options.keyname) admin_client = AdminClient(head_node_public_ip, secret_key) remote_file_path = RemoteHelper.copy_app_to_host( file_location, version.project_id, options.keyname, extras, custom_service_yaml) AppScaleLogger.log( 'Deploying service {} for {}'.format(version.service_id, version.project_id)) operation_id = admin_client.create_version(version, remote_file_path) # now that we've told the AppController to start our app, find out what port # the app is running on and wait for it to start serving AppScaleLogger.log("Please wait for your app to start serving.") deadline = time.time() + cls.MAX_OPERATION_TIME while True: if time.time() > deadline: raise AppScaleException('The deployment operation took too long.') operation = admin_client.get_operation(version.project_id, operation_id) if not operation['done']: time.sleep(1) continue if 'error' in operation: raise AppScaleException(operation['error']['message']) version_url = operation['response']['versionUrl'] break AppScaleLogger.success( 'Your app can be reached at the following URL: {}'.format(version_url)) if created_dir: shutil.rmtree(file_location) match = re.match('http://(.+):(\d+)', version_url) login_host = match.group(1) http_port = int(match.group(2)) return login_host, http_port