async def _prepare_bootstrap(self) -> None: assert self._state if self._state.stage == NodeStageEnum.BOOTSTRAPPING: raise NodeCantBootstrapError("node bootstrapping") elif self._state.stage > NodeStageEnum.NONE: raise NodeCantBootstrapError("node can't be bootstrapped") elif self._init_stage < NodeInitStage.PRESTART: raise NodeNotStartedError()
async def deploy(self, params: DeployParamsModel) -> None: assert self._state if self._init_stage < NodeInitStage.AVAILABLE: raise NodeNotStartedError() if self.deployment_state.error: raise NodeCantDeployError("node is in error state") logger.debug("deploy > unsubscribe inventory updates") if self._inventory_sub: self.gstate.inventory.unsubscribe(self._inventory_sub) # check parameters if not params.ntpaddr or len(params.ntpaddr) == 0: raise NodeCantDeployError("missing ntp server address") if not params.hostname or len(params.hostname) == 0: raise NodeCantDeployError("missing hostname parameter") disk_solution = Disks.gen_solution(self.gstate) if not disk_solution.possible: raise NodeCantDeployError("no possible deployment solution found") assert disk_solution.systemdisk is not None disks = DeploymentDisksConfig(system=disk_solution.systemdisk.path) disks.storage = [ d.path for d in disk_solution.storage if d.path is not None ] logger.debug(f"mgr > deploy > disks: {disks}") # set hostname in memory; we'll write it later self._state.hostname = params.hostname self._token = self._generate_token() assert self._state.address logger.info("deploy node") await self._deployment.deploy( DeploymentConfig( hostname=params.hostname, address=self._state.address, token=self._token, ntp_addr=params.ntpaddr, disks=disks, ), self._post_bootstrap_finisher, self._finish_deployment, ) await self._save_token() await self._save_ntp_addr(params.ntpaddr) admin_user = UserModel(username="******", password="******") admin_user.hash_password() user_mgr = UserMgr(self.gstate.store) await user_mgr.put(admin_user)
async def join( self, leader_address: str, token: str, params: JoinParamsModel ) -> bool: logger.debug(f"join > with leader {leader_address}, token: {token}") if self._init_stage < NodeInitStage.AVAILABLE: raise NodeNotStartedError() elif self._init_stage > NodeInitStage.AVAILABLE: raise NodeCantJoinError() if not params.hostname or len(params.hostname) == 0: raise NodeError("Hostname parameter not provided.") assert self._state assert self._state.address # set in-memory hostname state, write it later self._state.hostname = params.hostname disk_solution = Disks.gen_solution(self.gstate) if not disk_solution.possible: raise NodeCantJoinError("No disk deployment solution found.") assert disk_solution.systemdisk is not None disks = DeploymentDisksConfig(system=disk_solution.systemdisk.path) disks.storage = [ d.path for d in disk_solution.storage if d.path is not None ] try: res: bool = await self._deployment.join( leader_address, token, self._state.uuid, params.hostname, self._state.address, disks, ) if not res: return False except Exception as e: # propagate exceptions raise e self._token = token await self._save_state() await self._node_start() return True
def connmgr(self) -> ConnMgr: if not self._connmgr: raise NodeNotStartedError() elif self._shutting_down: raise NodeShuttingDownError() return self._connmgr
def uuid(self) -> UUID: if self._state is None: raise NodeNotStartedError() return self._state.uuid
async def join(self, leader_address: str, token: str) -> bool: logger.debug(f"join > with leader {leader_address}, token: {token}") if self._init_stage == NodeInitStage.NONE: raise NodeNotStartedError() elif self._init_stage > NodeInitStage.PRESTART: raise NodeCantJoinError() assert self._state assert self._state.hostname assert self._state.address if self._state.stage == NodeStageEnum.BOOTSTRAPPING: raise NodeBootstrappingError() elif self._state.stage == NodeStageEnum.BOOTSTRAPPED: raise NodeHasBeenDeployedError() elif self._state.stage == NodeStageEnum.JOINING: raise NodeAlreadyJoiningError() elif self._state.stage == NodeStageEnum.READY: raise NodeHasJoinedError() assert self._state.stage == NodeStageEnum.NONE assert self._state.role == NodeRoleEnum.NONE uri: str = f"ws://{leader_address}/api/nodes/ws" conn = await self._connmgr.connect(uri) logger.debug(f"join > conn: {conn}") joinmsg = JoinMessageModel(uuid=self._state.uuid, hostname=self._state.hostname, address=self._state.address, token=token) msg = MessageModel(type=MessageTypeEnum.JOIN, data=joinmsg.dict()) await conn.send(msg) self._state.stage = NodeStageEnum.JOINING self._save_state() reply: MessageModel = await conn.receive() logger.debug(f"join > recv: {reply}") if reply.type == MessageTypeEnum.ERROR: errmsg = ErrorMessageModel.parse_obj(reply.data) logger.error(f"join > error: {errmsg.what}") await conn.close() self._state.stage = NodeStageEnum.NONE self._save_state() return False assert reply.type == MessageTypeEnum.WELCOME welcome = WelcomeMessageModel.parse_obj(reply.data) assert welcome.pubkey authorized_keys: Path = Path("/root/.ssh/authorized_keys") if not authorized_keys.parent.exists(): authorized_keys.parent.mkdir(0o700) with authorized_keys.open("a") as fd: fd.writelines([welcome.pubkey]) logger.debug(f"join > wrote pubkey to {authorized_keys}") readymsg = ReadyToAddMessageModel() await conn.send( MessageModel(type=MessageTypeEnum.READY_TO_ADD, data=readymsg)) await conn.close() self._state.stage = NodeStageEnum.READY self._state.role = NodeRoleEnum.FOLLOWER self._save_state() self._token = token self._save_token(should_exist=False) self._node_start() return True