Exemple #1
0
        def sequencers(mv: MaintenanceView, cv: ClusterView) -> Optional[str]:
            if not mv.affects_sequencers:
                return None

            headers = [
                "NODE INDEX",
                "NODE NAME",
                "LOCATION",
                "TARGET STATE",
                "CURRENT STATE",
                "MAINTENANCE STATUS",
                "LAST UPDATED",
            ]
            tbl = []
            for n in mv.sequencer_nodes:
                nv = cv.get_node_view(node_index=n.node_index)
                node_index = nv.node_index
                node_name = nv.node_name
                location = nv.location
                # pyre-ignore
                target_state = mv.sequencer_target_state.name
                mnt_status = mv.get_sequencer_maintenance_status(n)
                current_state = colored(
                    # pyre-ignore
                    nv.sequencer_state.state.name,
                    _color(mnt_status),
                )
                maintenance_status = colored(mnt_status.name,
                                             _color(mnt_status))

                last_updated_at = mv.get_sequencer_last_updated_at(n)
                if last_updated_at:
                    last_updated = f"{last_updated_at} ({naturaltime(last_updated_at)})"
                else:
                    last_updated = "-"

                tbl.append([
                    node_index,
                    node_name,
                    location,
                    target_state,
                    current_state,
                    maintenance_status,
                    last_updated,
                ])
            return "Sequencer Maintenances:\n{}".format(
                indent(tabulate(tbl, headers=headers, tablefmt="plain"),
                       prefix="  "))
Exemple #2
0
                    def shards_table(mv: MaintenanceView, cv: ClusterView,
                                     ni: int) -> str:
                        headers = [
                            "SHARD INDEX",
                            "CURRENT STATE",
                            "TARGET STATE",
                            "MAINTENANCE STATUS",
                            "LAST UPDATED",
                        ]
                        tbl = []
                        for shard in mv.get_shards_by_node_index(ni):
                            target_state = mv.shard_target_state
                            shard_state = mv.get_shard_state(shard)
                            cur_op_state = shard_state.current_operational_state
                            current_state = colored(
                                cur_op_state.name,
                                _color_shard_op_state(
                                    cur_op_state,
                                    # pyre-ignore
                                    mv.shard_target_state,
                                ),
                            )

                            mnt_status = mv.get_shard_maintenance_status(shard)
                            maintenance_status = colored(
                                mnt_status.name, _color(mnt_status))
                            last_updated_at = mv.get_shard_last_updated_at(
                                shard)
                            if last_updated_at:
                                last_updated = (
                                    f"{last_updated_at} "
                                    f"({naturaltime(last_updated_at)})")
                            else:
                                last_updated = "-"

                            tbl.append([
                                shard.shard_index,
                                current_state,
                                # pyre-ignore
                                target_state.name,
                                maintenance_status,
                                last_updated,
                            ])
                        return tabulate(tbl, headers=headers, tablefmt="plain")
Exemple #3
0
 def _maintenance_id_to_maintenance_view(self) -> Dict[str, MaintenanceView]:
     if self._maintenance_id_to_maintenance_view_dict is None:
         self._maintenance_id_to_maintenance_view_dict = {
             mnt_id: MaintenanceView(
                 maintenance=self._maintenance_id_to_maintenance[mnt_id],
                 node_index_to_node_view={
                     ni: self._node_index_to_node_view[ni]
                     for ni in self._maintenance_id_to_node_indexes[mnt_id]
                 },
             )
             for mnt_id in self._maintenance_ids
         }
     return self._maintenance_id_to_maintenance_view_dict
Exemple #4
0
 def _maintenance_id_to_maintenance_view(self) -> Dict[str, MaintenanceView]:
     if self._maintenance_id_to_maintenance_view_dict is None:
         self._maintenance_id_to_maintenance_view_dict = {
             mnt_id: MaintenanceView(
                 maintenance=self._maintenance_id_to_maintenance[mnt_id],
                 node_index_to_node_view={
                     ni: self._node_index_to_node_view[ni]
                     for ni in self._maintenance_id_to_node_indexes[mnt_id]
                 },
             )
             for mnt_id in self._maintenance_ids
         }
     # pyre-fixme[7]: Expected `Dict[str, MaintenanceView]` but got
     #  `Optional[Dict[str, MaintenanceView]]`.
     return self._maintenance_id_to_maintenance_view_dict
Exemple #5
0
 def impact(mv: MaintenanceView,
            show_safety_check_results: bool) -> str:
     if (mv.overall_status == MaintenanceOverallStatus.BLOCKED
             and show_safety_check_results):
         response = mv.last_check_impact_result
         shards = []
         for ni in mv.affected_node_indexes:
             shards.extend(mv.get_shards_by_node_index(ni))
         impact_string = safety.check_impact_string(
             response=response,
             shards=shards,
             # pyre-ignore
             target_state=mv.shard_target_state,
         )
         return f"Safety Check Impact:\n\n{impact_string}"
     else:
         return ""
 async def test_no_timestamps(self):
     async with MockAdminAPI() as client:
         cv = await get_cluster_view(client)
         await apply_maintenance(
             client=client,
             sequencer_nodes=[cv.get_node_view_by_node_index(0).node_id],
             ttl=timedelta(days=2),
         )
         cv = await get_cluster_view(client)
     mnt = list(cv.get_all_maintenances())[0]
     mnt = mnt(created_on=None, expires_on=None)
     mv = MaintenanceView(
         maintenance=mnt,
         node_index_to_node_view={0: cv.get_node_view(node_index=0)})
     self.assertIsNone(mv.created_on)
     self.assertIsNone(mv.expires_on)
     self.assertIsNone(mv.expires_in)
Exemple #7
0
            def aggregated(mv: MaintenanceView, cv: ClusterView) -> str:
                headers = [
                    "NODE INDEX",
                    "NODE NAME",
                    "LOCATION",
                    "TARGET STATE",
                    "CURRENT STATE",
                    "MAINTENANCE STATUS",
                    "LAST UPDATED",
                ]
                tbl = []
                for ni in mv.affected_storage_node_indexes:
                    nv = cv.get_node_view(node_index=ni)
                    node_index = ni
                    node_name = nv.node_name
                    location = nv.location
                    target_state = (
                        # pyre-ignore
                        f"{mv.shard_target_state.name}"
                        f"({len(mv.get_shards_by_node_index(ni))})")

                    # current_state
                    chunks = []
                    for cur_op_state, num in sorted(
                            Counter(
                                (mv.get_shard_state(
                                    shard).current_operational_state
                                 for shard in mv.get_shards_by_node_index(ni)
                                 )).items(),
                            key=lambda x: x[0].name,
                    ):
                        chunks.append(
                            colored(
                                f"{cur_op_state.name}({num})",
                                _color_shard_op_state(
                                    cur_op_state,
                                    # pyre-ignore
                                    mv.shard_target_state,
                                ),
                            ))

                    current_state = ",".join(chunks)

                    # maintenance status
                    mnt_statuses = [
                        mv.get_shard_maintenance_status(shard)
                        for shard in mv.get_shards_by_node_index(ni)
                    ]
                    chunks = []
                    for mnt_status, num in sorted(
                            Counter(mnt_statuses).items(),
                            key=lambda x: x[0].name):
                        chunks.append(
                            colored(f"{mnt_status.name}({num})",
                                    _color(mnt_status)))

                    maintenance_status = ",".join(chunks)

                    last_updated_at_time = min((
                        ShardMaintenanceProgress.from_thrift(
                            # pyre-ignore
                            ss.maintenance).last_updated_at
                        for ss in nv.shard_states if ss.maintenance))
                    last_updated_at = (
                        f"{last_updated_at_time} ({naturaltime(last_updated_at_time)})"
                    )

                    tbl.append([
                        node_index,
                        node_name,
                        location,
                        target_state,
                        current_state,
                        maintenance_status,
                        last_updated_at,
                    ])
                return tabulate(tabular_data=tbl,
                                headers=headers,
                                tablefmt="plain")
    def validate(
        self,
        maintenance_view: MaintenanceView,
        maintenance: MaintenanceDefinition,
        node_index_to_node_view: Dict[int, NodeView],
    ) -> None:
        self.assertEqual(maintenance_view.allow_passive_drains,
                         maintenance.allow_passive_drains)

        self.assertListEqual(
            sorted(ni.node_index
                   for ni in maintenance_view.affected_sequencer_node_ids),
            sorted(sn.node_index for sn in maintenance.sequencer_nodes),
        )

        self.assertTupleEqual(
            maintenance_view.affected_sequencer_node_indexes,
            tuple(sorted(sn.node_index for sn in maintenance.sequencer_nodes)),
        )

        self.assertListEqual(
            sorted(ni.node_index
                   for ni in maintenance_view.affected_storage_node_ids),
            sorted({shard.node.node_index
                    for shard in maintenance.shards}),
        )

        self.assertTupleEqual(
            maintenance_view.affected_storage_node_indexes,
            tuple(
                sorted({shard.node.node_index
                        for shard in maintenance.shards})),
        )

        self.assertListEqual(
            sorted(ni.node_index for ni in maintenance_view.affected_node_ids),
            sorted({sn.node_index
                    for sn in maintenance.sequencer_nodes}.union({
                        shard.node.node_index
                        for shard in maintenance.shards
                    })),
        )

        self.assertTupleEqual(
            maintenance_view.affected_node_indexes,
            tuple(
                sorted({sn.node_index
                        for sn in maintenance.sequencer_nodes}.union({
                            shard.node.node_index
                            for shard in maintenance.shards
                        }))),
        )

        if len(maintenance.shards) == 0:
            self.assertIsNone(maintenance_view.shard_target_state)
        else:
            self.assertEqual(maintenance_view.shard_target_state,
                             maintenance.shard_target_state)

        if len(maintenance.sequencer_nodes) == 0:
            self.assertIsNone(maintenance_view.sequencer_target_state)
        else:
            self.assertEqual(
                maintenance_view.sequencer_target_state,
                maintenance.sequencer_target_state,
            )

        if maintenance.ttl_seconds == 0:
            self.assertIsNone(maintenance_view.ttl)
        else:
            assert maintenance_view.ttl is not None
            self.assertEqual(
                maintenance.ttl_seconds,
                # pyre-fixme[16]: Optional type has no attribute `total_seconds`.
                maintenance_view.ttl.total_seconds(),
            )

        if maintenance.created_on is None:
            self.assertIsNone(maintenance_view.created_on)
        else:
            assert maintenance_view.created_on is not None
            self.assertAlmostEqual(
                # pyre-fixme[16]: Optional type has no attribute `timestamp`.
                maintenance_view.created_on.timestamp() * 1000,
                maintenance.created_on,
                1,
            )

        if maintenance.expires_on is None:
            self.assertIsNone(maintenance_view.expires_on)
        else:
            assert maintenance_view.expires_on is not None
            self.assertAlmostEqual(
                maintenance_view.expires_on.timestamp() * 1000,
                maintenance.expires_on,
                1,
            )

            assert maintenance_view.expires_in is not None
            self.assertAlmostEqual(
                maintenance_view.expires_in.total_seconds(),
                # pyre-fixme[16]: Optional type has no attribute `__sub__`.
                (maintenance_view.expires_on - datetime.now()).total_seconds(),
                1,
            )

        self.assertEqual(maintenance_view.affects_shards,
                         len(maintenance.shards) > 0)
        self.assertEqual(maintenance_view.affects_sequencers,
                         len(maintenance.sequencer_nodes) > 0)

        self.assertEqual(maintenance_view.num_shards_total,
                         len(maintenance.shards))
        self.assertEqual(maintenance_view.num_sequencers_total,
                         len(maintenance.sequencer_nodes))

        for shard in maintenance.shards:
            assert shard.node.node_index is not None
            self.assertEqual(
                # pyre-fixme[6]: Expected `int` for 1st param but got `Optional[int]`.
                node_index_to_node_view[shard.node.node_index
                                        ].shard_states[shard.shard_index],
                maintenance_view.get_shard_state(shard),
            )

        for sn in maintenance.sequencer_nodes:
            assert sn.node_index is not None
            self.assertEqual(
                # pyre-fixme[6]: Expected `int` for 1st param but got `Optional[int]`.
                node_index_to_node_view[sn.node_index].sequencer_state,
                maintenance_view.get_sequencer_state(sn),
            )

        self.assertSetEqual(set(maintenance.shards),
                            set(maintenance_view.shards))

        for node_index in {
                shard.node.node_index
                for shard in maintenance.shards
        }:
            assert node_index is not None
            shards = {
                shard
                for shard in maintenance.shards
                if shard.node.node_index == node_index
            }
            self.assertSetEqual(
                shards,
                set(maintenance_view.get_shards_by_node_index(node_index)))
Exemple #9
0
def _get_shard_operational_state(mv: MaintenanceView,
                                 shard: ShardID) -> ShardOperationalState:
    shard_state = mv.get_shard_state(shard)
    if shard_state is not None:
        return shard_state.current_operational_state
    return ShardOperationalState.UNKNOWN
Exemple #10
0
    def validate(
        self,
        maintenance_view: MaintenanceView,
        maintenance: MaintenanceDefinition,
        node_index_to_node_view: Dict[int, NodeView],
    ) -> None:
        self.assertEqual(
            maintenance_view.allow_passive_drains, maintenance.allow_passive_drains
        )

        self.assertListEqual(
            sorted(
                # pyre-fixme[6]: Expected `Iterable[Variable[_LT (bound to
                #  _SupportsLessThan)]]` for 1st param but got
                #  `Generator[typing.Optional[int], None, None]`.
                ni.node_index
                for ni in maintenance_view.affected_sequencer_node_ids
            ),
            # pyre-fixme[6]: Expected `Iterable[Variable[_LT (bound to
            #  _SupportsLessThan)]]` for 1st param but got
            #  `Generator[typing.Optional[int], None, None]`.
            sorted(sn.node_index for sn in maintenance.sequencer_nodes),
        )

        self.assertTupleEqual(
            maintenance_view.affected_sequencer_node_indexes,
            # pyre-fixme[6]: Expected `Iterable[Variable[_LT (bound to
            #  _SupportsLessThan)]]` for 1st param but got
            #  `Generator[typing.Optional[int], None, None]`.
            tuple(sorted(sn.node_index for sn in maintenance.sequencer_nodes)),
        )

        self.assertListEqual(
            # pyre-fixme[6]: Expected `Iterable[Variable[_LT (bound to
            #  _SupportsLessThan)]]` for 1st param but got
            #  `Generator[typing.Optional[int], None, None]`.
            sorted(ni.node_index for ni in maintenance_view.affected_storage_node_ids),
            # pyre-fixme[6]: Expected `Iterable[Variable[_LT (bound to
            #  _SupportsLessThan)]]` for 1st param but got `Set[typing.Optional[int]]`.
            sorted({shard.node.node_index for shard in maintenance.shards}),
        )

        self.assertTupleEqual(
            maintenance_view.affected_storage_node_indexes,
            # pyre-fixme[6]: Expected `Iterable[Variable[_LT (bound to
            #  _SupportsLessThan)]]` for 1st param but got `Set[typing.Optional[int]]`.
            tuple(sorted({shard.node.node_index for shard in maintenance.shards})),
        )

        self.assertListEqual(
            # pyre-fixme[6]: Expected `Iterable[Variable[_LT (bound to
            #  _SupportsLessThan)]]` for 1st param but got
            #  `Generator[typing.Optional[int], None, None]`.
            sorted(ni.node_index for ni in maintenance_view.affected_node_ids),
            sorted(
                # pyre-fixme[6]: Expected `Iterable[Variable[_LT (bound to
                #  _SupportsLessThan)]]` for 1st param but got
                #  `Set[typing.Optional[int]]`.
                {sn.node_index for sn in maintenance.sequencer_nodes}.union(
                    {shard.node.node_index for shard in maintenance.shards}
                )
            ),
        )

        self.assertTupleEqual(
            maintenance_view.affected_node_indexes,
            tuple(
                sorted(
                    # pyre-fixme[6]: Expected `Iterable[Variable[_LT (bound to
                    #  _SupportsLessThan)]]` for 1st param but got
                    #  `Set[typing.Optional[int]]`.
                    {sn.node_index for sn in maintenance.sequencer_nodes}.union(
                        {shard.node.node_index for shard in maintenance.shards}
                    )
                )
            ),
        )

        if len(maintenance.shards) == 0:
            self.assertIsNone(maintenance_view.shard_target_state)
        else:
            self.assertEqual(
                maintenance_view.shard_target_state, maintenance.shard_target_state
            )

        if len(maintenance.sequencer_nodes) == 0:
            self.assertIsNone(maintenance_view.sequencer_target_state)
        else:
            self.assertEqual(
                maintenance_view.sequencer_target_state,
                maintenance.sequencer_target_state,
            )

        if maintenance.ttl_seconds == 0:
            self.assertIsNone(maintenance_view.ttl)
        else:
            assert maintenance_view.ttl is not None
            self.assertEqual(
                maintenance.ttl_seconds,
                # pyre-fixme[16]: Optional type has no attribute `total_seconds`.
                maintenance_view.ttl.total_seconds(),
            )

        if maintenance.created_on is None:
            self.assertIsNone(maintenance_view.created_on)
        else:
            assert maintenance_view.created_on is not None
            self.assertAlmostEqual(
                # pyre-fixme[16]: Optional type has no attribute `timestamp`.
                maintenance_view.created_on.timestamp() * 1000,
                maintenance.created_on,
                1,
            )

        if maintenance.expires_on is None:
            self.assertIsNone(maintenance_view.expires_on)
        else:
            # pull these into local vars to appease pyre
            view_expires_on = maintenance_view.expires_on
            view_expires_in = maintenance_view.expires_in

            assert view_expires_on is not None
            self.assertAlmostEqual(
                view_expires_on.timestamp() * 1000, maintenance.expires_on, 1
            )

            assert view_expires_in is not None
            self.assertAlmostEqual(
                view_expires_in.total_seconds(),
                (view_expires_on - datetime.now()).total_seconds(),
                1,
            )

        self.assertEqual(maintenance_view.affects_shards, len(maintenance.shards) > 0)
        self.assertEqual(
            maintenance_view.affects_sequencers, len(maintenance.sequencer_nodes) > 0
        )

        self.assertEqual(maintenance_view.num_shards_total, len(maintenance.shards))
        self.assertEqual(
            maintenance_view.num_sequencers_total, len(maintenance.sequencer_nodes)
        )

        for shard in maintenance.shards:
            assert shard.node.node_index is not None
            self.assertEqual(
                node_index_to_node_view[shard.node.node_index].shard_states[
                    shard.shard_index
                ],
                maintenance_view.get_shard_state(shard),
            )

        for sn in maintenance.sequencer_nodes:
            assert sn.node_index is not None
            self.assertEqual(
                node_index_to_node_view[sn.node_index].sequencer_state,
                maintenance_view.get_sequencer_state(sn),
            )

        self.assertSetEqual(set(maintenance.shards), set(maintenance_view.shards))

        for node_index in {shard.node.node_index for shard in maintenance.shards}:
            assert node_index is not None
            shards = {
                shard
                for shard in maintenance.shards
                if shard.node.node_index == node_index
            }
            self.assertSetEqual(
                shards, set(maintenance_view.get_shards_by_node_index(node_index))
            )