通過heat創建stack的代碼流程分析heat stack-create

Heat-api發送RPC請求
Heat/api/openstack/v1/stacks.py
    @util.policy_enforce                                                                                                                                                                               
    def create(self, req, body):                                                                                                                                                                       
        """Create a new stack."""                                                                                                                                                                      
        data = InstantiationData(body)                                                                                                                                                                 
                                                                                                                                                                                                       
        args = self.prepare_args(data)                                                                                                                                                                 
        result = self.rpc_client.create_stack(                                                                                                                                                         
            req.context,                                                                                                                                                                               
            data.stack_name(),                                                                                                                                                                         
            data.template(),                                                                                                                                                                           
            data.environment(),                                                                                                                                                                        
            data.files(),                                                                                                                                                                              
            args,                                                                                                                                                                                      
            environment_files=data.environment_files())                                                                                                                                                
                                                                                                                                                                                                       
        formatted_stack = stacks_view.format_stack(                                                                                                                                                    
            req,                                                                                                                                                                                       
            {rpc_api.STACK_ID: result}                                                                                                                                                                 
        )                                                                                                                                                                                              
        return {'stack': formatted_stack}

Heat/rpc/client.py
    def create_stack(self, ctxt, stack_name, template, params, files,                                                                                                                                  
                     args, environment_files=None):                                                                                                                                                    
        """Creates a new stack using the template provided.                                                                                                                                            
                                                                                                                                                                                                       
        Note that at this stage the template has already been fetched from the                                                                                                                         
        heat-api process if using a template-url.                                                                                                                                                      
                                                                                                                                                                                                       
        :param ctxt: RPC context.                                                                                                                                                                      
        :param stack_name: Name of the stack you want to create.                                                                                                                                       
        :param template: Template of stack you want to create.                                                                                                                                         
        :param params: Stack Input Params/Environment                                                                                                                                                  
        :param files: files referenced from the environment.                                                                                                                                           
        :param args: Request parameters/args passed from API                                                                                                                                           
        :param environment_files: optional ordered list of environment file                                                                                                                            
               names included in the files dict                                                                                                                                                        
        :type  environment_files: list or None                                                                                                                                                         
        """                                                                                                                                                                                            
        return self._create_stack(ctxt, stack_name, template, params, files,                                                                                                                           
                                  args, environment_files=environment_files) 

    def _create_stack(self, ctxt, stack_name, template, params, files,                                                                                                                                 
                      args, environment_files=None,                                                                                                                                                    
                      owner_id=None, nested_depth=0, user_creds_id=None,                                                                                                                               
                      stack_user_project_id=None, parent_resource_name=None):                                                                                                                          
        """Internal interface for engine-to-engine communication via RPC.                                                                                                                              
                                                                                                                                                                                                       
        Allows some additional options which should not be exposed to users via                                                                                                                        
        the API:                                                                                                                                                                                       
                                                                                                                                                                                                       
        :param owner_id: parent stack ID for nested stacks                                                                                                                                             
        :param nested_depth: nested depth for nested stacks                                                                                                                                            
        :param user_creds_id: user_creds record for nested stack
        :param stack_user_project_id: stack user project for nested stack
        :param parent_resource_name: the parent resource name
        """
        return self.call(
            ctxt, self.make_msg('create_stack', stack_name=stack_name,
                                template=template,
                                params=params, files=files,
                                environment_files=environment_files,
                                args=args, owner_id=owner_id,
                                nested_depth=nested_depth,
                                user_creds_id=user_creds_id,
                                stack_user_project_id=stack_user_project_id,
                                parent_resource_name=parent_resource_name),
            version='1.23')

最終RPC請求由heat-engine接收,真正有來創建stack的操作是由stack.create()來完成的。
Heat/engine/service.py
    def create_stack(self, cnxt, stack_name, template, params, files,
                     args, environment_files=None,
                     owner_id=None, nested_depth=0, user_creds_id=None,
                     stack_user_project_id=None, parent_resource_name=None):
        """Create a new stack using the template provided.

        Note that at this stage the template has already been fetched from the
        heat-api process if using a template-url.

        :param cnxt: RPC context.
        :param stack_name: Name of the stack you want to create.
        :param template: Template of stack you want to create.
        :param params: Stack Input Params
        :param files: Files referenced from the template
        :param args: Request parameters/args passed from API
        :param environment_files: optional ordered list of environment file
               names included in the files dict
        :type  environment_files: list or None
        :param owner_id: parent stack ID for nested stacks, only expected when
                         called from another heat-engine (not a user option)
        :param nested_depth: the nested depth for nested stacks, only expected
                         when called from another heat-engine
        :param user_creds_id: the parent user_creds record for nested stacks
        :param stack_user_project_id: the parent stack_user_project_id for
                         nested stacks
        :param parent_resource_name: the parent resource name
        """
        LOG.info(_LI('Creating stack %s'), stack_name)

	      # 創建一個新的porject,之所以會新建一個獨立的project,
	   #而不是直接用發起創建stack操作的project
	   #應該是從權限控制角度考慮,通過新的project只能訪問相關software_config資源.
	   #通過分析stack.create_stack_user_project_id,其實際上是就是調用了keystone 
	   #client的相關接口創建了一個新project.
        def _create_stack_user(stack):
            if not stack.stack_user_project_id:
                try:
                    stack.create_stack_user_project_id()
                except exception.AuthorizationFailure as ex:
                    stack.state_set(stack.action, stack.FAILED,
                                    six.text_type(ex))

        def _stack_create(stack):
            # Create/Adopt a stack, and create the periodic task if successful
            if stack.adopt_stack_data:
                stack.adopt()
            elif stack.status != stack.FAILED:
                                    # 真正做事情的地方,些處調用的是heat/engine/stack.py中的create函數。
                stack.create() 

            if (stack.action in (stack.CREATE, stack.ADOPT)
                    and stack.status == stack.COMPLETE):
                if self.stack_watch:
                    # Schedule a periodic watcher task for this stack
                    self.stack_watch.start_watch_task(stack.id, cnxt)
            else:
                LOG.info(_LI("Stack create failed, status %s"), stack.status)

        convergence = cfg.CONF.convergence_engine

	       # 對heat 模板進行驗證。
        stack = self._parse_template_and_validate_stack(
            cnxt, stack_name, template, params, files, environment_files,
            args, owner_id, nested_depth, user_creds_id,
            stack_user_project_id, convergence, parent_resource_name)

        self.resource_enforcer.enforce_stack(stack)
	      #把stack的信息寫入數據庫。
        stack_id = stack.store()
        if cfg.CONF.reauthentication_auth_method == 'trusts':
            stack = parser.Stack.load(
                cnxt, stack_id=stack_id, use_stored_context=True)
	       # 創建一個新的user.
        _create_stack_user(stack)
        if convergence:
            action = stack.CREATE
            if stack.adopt_stack_data:
                action = stack.ADOPT
            stack.thread_group_mgr = self.thread_group_mgr
            stack.converge_stack(template=stack.t, action=action)
        else:
            self.thread_group_mgr.start_with_lock(cnxt, stack, self.engine_id,
                                                  _stack_create, stack)

        return dict(stack.identifier())

接下來看一下stack.create() heat/engine/stack.py
注意其中的TaskRunner這個類及其回調函數self.stack_task。
在heat中,TaskRunner被廣泛使用,用創建諸如create, update, suspend等的操作。
TaskRunner的構造參數中定義了,這個Task將執行的函數,對stack所涉及的資源執行的action,即創建。
最後調度這個Task運行,運行的過程是執行self.stack_task函數
    @profiler.trace('Stack.create', hide_args=False)
    @reset_state_on_error      
    def create(self):
        """Create the stack and all of the resources."""
        def rollback():
            if not self.disable_rollback and self.state == (self.CREATE, 
                                                            self.FAILED):                   
                self.delete(action=self.ROLLBACK)

        self._store_resources()

	      # 因爲我們是調用heat stack-create,所以這裏是self.stack_task;如果我們調用的是
	      # heat stack-update,那麼此處應該是self.update_task.
	     # 對於stack的其它操作,如suspend,resume,snapshot等是self.stack_task.具體的可以
	     #看一下heat/engine/stack.py中的代碼。
        creator = scheduler.TaskRunner( 
            self.stack_task, action=self.CREATE,
            reverse=False, post_func=rollback,
            error_wait_time=cfg.CONF.error_wait_time)

	       #此處將調用TaskRunner中的__call__()方法。在此__call__()方法中,self.stack_task
	       # 將會被執行。
        creator(timeout=self.timeout_secs())

TaskRunner這個類定義在了heat/engine/scheduler.py中。
Heat engine中比較重要的幾個文件就是scheduler.py, service.py, stack.py, resource,py, stack_lock.py, update.py
	Scheduler.py中的兩個類比較重要:
		TaskRunner: 對stack的任何操作, 最開始都是以TaskRunner來封裝的。
		DependencyTaskGroup: 一個Task中可能會有許多步驟(step)來完,實現task的循環執行主要依靠這個類
			與wrappertask這個裝飾器及配合yield來共同完成的。
	Service.py中主要定義了三個類:
		EngineService:
		EngineListenerService:
		ThreadGroupManager: 實現把對stack的操作放到子線程中執行。
		
這個stack_task有三個重要的點:
scheduler.wrappertask進行了裝飾,意味這task需要處理subtask
scheduler.DependencyTaskGroup,構造該對象,即拓撲結構的圖,該圖上的每個節點都是一個資源,即資源上執行的task,即stack_task的子任務
yield action_task(),這是yield關鍵字的使用,有yield之後,函數就成爲一個對象了,那麼直接調用stack_task不會執行該函數,而是返回一個迭代器對象

下面來看stack_task(), 同時注意所用的裝飾器。
   @scheduler.wrappertask
    def stack_task(self, action, reverse=False, post_func=None,
                   error_wait_time=None,
                   aggregate_exceptions=False, pre_completion_func=None):
        """A task to perform an action on the stack.

        All of the resources are traversed in forward or reverse dependency
        order.

        :param action: action that should be executed with stack resources
        :param reverse: define if action on the resources need to be executed
         in reverse order (resources - first and then res dependencies )
        :param post_func: function that need to be executed after
        action complete on the stack
        :param error_wait_time: time to wait before cancelling all execution
        threads when an error occurred
        :param aggregate_exceptions: define if exceptions should be aggregated
        :param pre_completion_func: function that need to be executed right
        before action completion. Uses stack ,action, status and reason as
        input parameters
        """
        LOG.debug("Jeffrey: stack.stack_task")
        try:
            lifecycle_plugin_utils.do_pre_ops(self.context, self,
                                              None, action)
        except Exception as e:
            self.state_set(action, self.FAILED, e.args[0] if e.args else
                           'Failed stack pre-ops: %s' % six.text_type(e))
            if callable(post_func):
                post_func()
            return
        self.state_set(action, self.IN_PROGRESS,
                       'Stack %s started' % action)

        stack_status = self.COMPLETE
        reason = 'Stack %s completed successfully' % action

        action_method = action.lower()
        # If a local _$action_kwargs function exists, call it to get the
        # action specific argument list, otherwise an empty arg list
        handle_kwargs = getattr(self,
                                '_%s_kwargs' % action_method,
                                lambda x: {})

        @functools.wraps(getattr(resource.Resource, action_method))
        def resource_action(r):
            # Find e.g resource.create and call it
            handle = getattr(r, action_method)
            LOG.debug("Jeffrey: stack.stack_task.resource_action: handle=%s" % handle)
		     #此處實際上就是調用了資源對應的handler函數,比如handle_create()
		    # 這些handle_Xxaction()函數在定義資源的時候已經實現,具體實例可以參看:
		    # heat/engine/resources/openstack/nova/server.py
            return handle(**handle_kwargs(r))

        action_task = scheduler.DependencyTaskGroup(
            self.dependencies,
            resource_action,
            reverse,
            error_wait_time=error_wait_time,
            aggregate_exceptions=aggregate_exceptions)

        try:
            LOG.debug("Jeffrey: stack.stack_task.resource_action: action_task 1")
                           # 調用了DependcyTaskGroup的__call__(),在__call__中又調用了回調函數
	                # resource_action
            yield action_task()
            LOG.debug("Jeffrey: stack.stack_task.resource_action: action_task 1")

繼續看scheduler.py中的__call__()
    def __call__(self):
        """Return a co-routine which runs the task group."""
        LOG.debug("Jeffrey: DependencyTaskGroup.__call__ : self._ready()=%s" % self._ready())
        raised_exceptions = [] 
        while any(six.itervalues(self._runners)):
            try:
                for k, r in self._ready():      
                    LOG.debug("Jeffrey: DependencyTaskGroup.__call__.r=%s" % r)
                    r.start()  

上面的r.start調用的是scheduler.py中的start():
這個函數主要是執行傳遞進來的Task,result = self._task(*self._args, **self._kwargs)不一定是去執行這個函數,就因爲
yield關鍵字的存在導致其成爲一個Generator對象了,所以下 一步有個類型判斷,對於GeneratorType意味着需要一步步執行,
調用step函數。若沒有yield關鍵字,一切正常如以往。step函數則調用與yield配合的next函數,最後在run_to_completion函數中,
循環的調用next函數,直至yield拋出StopIteration異常.注意,此處的self._task實際上是在heat/engine/stack.py中定義的stack_task()

    def start(self, timeout=None):
        """Initialise the task and run its first step.

        If a timeout is specified, any attempt to step the task after that
        number of seconds has elapsed will result in a Timeout being
        raised inside the task.
        """
        assert self._runner is None, "Task already started"
        assert not self._done, "Task already cancelled"
        LOG.debug("Jeffrey: scheduler.TaskRunner.start")

        LOG.debug('%s starting' % six.text_type(self))

        if timeout is not None:
            self._timeout = Timeout(self, timeout)

        result = self._task(*self._args, **self._kwargs)
        if isinstance(result, types.GeneratorType):
            self._runner = result
            self.step()
        else:
            self._runner = False
            self._done = True
            LOG.debug('%s done (not resumable)' % six.text_type(self))

    def step(self):
        """Run another step of the task.

        Return True if the task is complete; False otherwise.
        """
        LOG.debug("Jeffrey: scheduler.TaskRunner.step: self.done()=%s" % self.done())
        if not self.done():
            assert self._runner is not None, "Task not started"

            if self._timeout is not None and self._timeout.expired():
                LOG.info(_LI('%s timed out'), six.text_type(self))
                self._done = True

                self._timeout.trigger(self._runner)
            else:
                LOG.debug('%s running' % six.text_type(self))

                try:
                    LOG.debug("Jeffrey: scheduler.TaskRunner.step: next(self._runner)=%s" % next(self._runner))
                    next(self._runner)
                except StopIteration:
                    self._done = True
                    LOG.debug('%s complete' % six.text_type(self))

        return self._done

    def run_to_completion(self, wait_time=1):
        """Run the task to completion.

        The task will sleep for `wait_time` seconds between steps. To avoid
        sleeping, pass `None` for `wait_time`.
        """
        while not self.step():
            self._sleep(wait_time)




發佈了91 篇原創文章 · 獲贊 37 · 訪問量 43萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章