Добавить API.py
This commit is contained in:
371
API.py
Normal file
371
API.py
Normal file
@@ -0,0 +1,371 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
В таблице backups: size - это сколько реально занимают данные на носителе, а original_data_size - это сколько заимают реальные данные.
|
||||
столбец deduplicated_size или равен нулю или original_data_size.
|
||||
столбец backed_up_data_size или равен нулю или original_data_size, но значительно чаще чем deduplicated_size равен нулю.
|
||||
|
||||
В таблице archives: size - это сколько реально занимают данные на носителе, а original_data_size и data_size - это сколько заимают реальные данные (все, что прошли через бэкап! даже те что уже проэкспаирились и удалились!!!). При этом непонятно зачем две разных колонки с одинаковыми значениями.
|
||||
столбец logical_size или равен нулю или столбцу size.
|
||||
столбец compressed_data_size или равен нулю или почти (чуть меньше!!!) равен столбцу size.
|
||||
в таблице могут быть несколько строк с одинаковыми ['id' ,'name'], при этом разными будут ваульты.
|
||||
# In[1]:
|
||||
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import requests
|
||||
from getpass import getpass
|
||||
import os
|
||||
from urllib3.exceptions import InsecureRequestWarning
|
||||
from urllib.parse import quote
|
||||
import datetime as dt
|
||||
|
||||
import itables
|
||||
itables.init_notebook_mode(connected=False)
|
||||
|
||||
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
|
||||
os.environ['no_proxy']='*'
|
||||
cb_srv = 'nameserver'
|
||||
|
||||
TB = (1024**4) # Для первода в терабайты
|
||||
GB = (1024**3) # Для первода в гигабайты
|
||||
|
||||
|
||||
# In[2]:
|
||||
|
||||
|
||||
# Функция вытаскивает данные из эндпоинтов через пагинацию.
|
||||
def get_paginated_data(uri, headers, limit=100, verify_ssl=False, timeout=30, progress=False, args = None):
|
||||
all_items = []
|
||||
params = {"limit": limit}
|
||||
|
||||
if args:
|
||||
params.update(args)
|
||||
|
||||
while True:
|
||||
try:
|
||||
response = requests.get(
|
||||
uri, headers=headers, params=params, verify=verify_ssl, timeout=timeout
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(
|
||||
f"\033[91mОшибка: {response.status_code} — {response.reason}\033[0m"
|
||||
)
|
||||
break
|
||||
|
||||
data = response.json()
|
||||
items = data.get("items", [])
|
||||
all_items.extend(items)
|
||||
|
||||
after_cursor = data.get("cursors", {}).get("after") or data.get(
|
||||
"paging", {}
|
||||
).get("cursors", {}).get("after")
|
||||
|
||||
if not after_cursor:
|
||||
break
|
||||
|
||||
params['after'] = after_cursor
|
||||
|
||||
except Exception as e:
|
||||
print(f"\033[91mОшибка: {e}\033[0m")
|
||||
break
|
||||
|
||||
if progress:
|
||||
line_to_print = f"\rПолучено: {len(all_items)} элементов из {uri} "
|
||||
print(f"\r{line_to_print}")
|
||||
|
||||
return pd.json_normalize(all_items) if all_items else pd.DataFrame()
|
||||
|
||||
# Функция конвртации времени в колонках на основании шаблона '_at'
|
||||
def convert_time_many_cols(df):
|
||||
# Список колонок для конвертации
|
||||
iso_cols = [
|
||||
col
|
||||
for col in df.columns if isinstance(col,str) and col.endswith("_at")
|
||||
]
|
||||
|
||||
for col in iso_cols:
|
||||
df[col] = (
|
||||
pd.to_datetime(df[col], format="ISO8601", errors='coerce',utc=True)
|
||||
.dt.tz_convert("Europe/Moscow")
|
||||
.dt.tz_localize(None)
|
||||
)
|
||||
return df
|
||||
|
||||
|
||||
# In[4]:
|
||||
|
||||
|
||||
# Запрос пользователя/пароля
|
||||
username = input("Введите имя пользователя: ")
|
||||
password = getpass("Введите пароль: ")
|
||||
|
||||
if not username or not password:
|
||||
print("\033[91mНе введён User или Password\033[0m")
|
||||
else:
|
||||
auth_payload = {
|
||||
'username': username,
|
||||
'password': password,
|
||||
'grant_type': 'password'
|
||||
}
|
||||
|
||||
## Основная программа
|
||||
# In[5]:
|
||||
|
||||
|
||||
# Аутентификация
|
||||
auth_url = f"https://{cb_srv}:9877/idp/token"
|
||||
try:
|
||||
auth_response = requests.post(
|
||||
auth_url,
|
||||
proxies={'http': None, 'https': None},
|
||||
data=auth_payload,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
verify=False
|
||||
)
|
||||
auth_response.raise_for_status()
|
||||
auth_data = auth_response.json()
|
||||
# Проверка наличия токена
|
||||
if 'token_type' not in auth_data or 'access_token' not in auth_data:
|
||||
print("\033[91mОшибка: в ответе отсутствует токен\033[0m")
|
||||
exit()
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"\033[91mОшибка аутентификации: {e}\033[0m")
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"{auth_data['token_type']} {auth_data['access_token']}"
|
||||
}
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
# Получение списка бэкапов
|
||||
df_backups = convert_time_many_cols(
|
||||
get_paginated_data(
|
||||
uri=f"https://{cb_srv}:9877/api/v1/backups",
|
||||
headers=headers,
|
||||
args={
|
||||
"include_screenshot_validation_status": "true",
|
||||
# "embed": "archive", - 2026-06-10 убрал, т.к. мне не всё нужно из таблицы archives
|
||||
#"include_actions": "true", -непонятно что даёт
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
# Получение списка архивов
|
||||
df_archives = convert_time_many_cols(
|
||||
get_paginated_data(uri=f"https://{cb_srv}:9877/api/v1/archives",headers=headers)
|
||||
)
|
||||
dict_archives = df_archives.set_index('id')['name'].to_dict() # id в списке архивов может встречаться несколько раз (при смене ваульта у задания, наверное и при дубликациина ленты).
|
||||
dict_archives_plan = df_archives.set_index('id')['backup_plan_name'].to_dict() # id в списке архивов может встречаться несколько раз (при смене ваульта у задания, наверное и при дубликациина ленты).
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
# Получение списка ваультов (старое API)
|
||||
df_vaults = convert_time_many_cols(
|
||||
get_paginated_data(uri=f"https://{cb_srv}:9877/api/vault_manager/v1/vaults",headers=headers)
|
||||
)
|
||||
dict_vaults = df_vaults.set_index('id')['name'].to_dict()
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
# Получение списка политик
|
||||
df_policies = convert_time_many_cols(
|
||||
get_paginated_data(
|
||||
uri=f"https://{cb_srv}:9877/api/v1/policies",
|
||||
headers=headers,
|
||||
args={"include_settings": "true", "include_applied_context": "true"},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
# Получение информации о применении политик
|
||||
df_policy_applications = convert_time_many_cols(
|
||||
pd.json_normalize(
|
||||
get_paginated_data(
|
||||
uri=f"https://{cb_srv}:9877/api/v1/policy_applications",
|
||||
headers=headers,
|
||||
args={
|
||||
"include_validation_issues": "true",
|
||||
"include_deployment_errors": "true",
|
||||
"include_running_blockers": "true",
|
||||
# "direct_only": "true", # Этот параметр уменьшает количество итоговых строк до 46. вместо примерно 1500!
|
||||
},
|
||||
)[0]
|
||||
)
|
||||
)
|
||||
|
||||
## непоянтно что делают - не увидел разницы от применения:
|
||||
# include_validation_issues=true
|
||||
# include_deployment_errors=true
|
||||
# include_running_blockers=true
|
||||
# direct_only=true
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
# Получение списка устройств
|
||||
df_resources = convert_time_many_cols(
|
||||
get_paginated_data(
|
||||
uri=f"https://{cb_srv}:9877/api/v1/resources",
|
||||
headers=headers,
|
||||
#args={"include_attributes": "true"},
|
||||
)
|
||||
)
|
||||
|
||||
## Создаём словарь где значениями становится колонка name:
|
||||
dict_resources = df_resources.set_index('id')['user_defined_name'].to_dict()
|
||||
## приводим ключи в словаре в верхний регистр, т.к. в df_backups['resource_ids'] почему-то именно так:
|
||||
dict_resources = {k.upper(): v for k,v in dict_resources.items()}
|
||||
|
||||
## Создаём словарь где значениями становитя комбинация из "user_defined_name['name']" если user_defined_name не равно name, в противном случае user_defined_name:
|
||||
dict_resources_comb = df_resources.set_index('id').apply(lambda r: r['user_defined_name'] if r['user_defined_name'] == r['name'] else f"{r['user_defined_name']} [{r['name']}]", axis=1).to_dict()
|
||||
## приводим ключи в словаре в верхний регистр, т.к. в df_backups['resource_ids'] почему-то именно так:
|
||||
dict_resources_comb = {k.upper(): v for k,v in dict_resources_comb.items()}
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
# заменяем GUID-ы в колонке 'resource_ids' на соответствующие значения из dict_resource. 'resource_ids' - это всегда list!
|
||||
## Разворачиваем списки в отдельные строки, заменяем через map, и собираем обратно с \n
|
||||
df_backups['resource_ids'] = (
|
||||
df_backups['resource_ids']
|
||||
.explode() # Превращает ['GUID1', 'GUID2'] в две строки: 'GUID1' и 'GUID2'
|
||||
.pipe(lambda s: s.map(dict_resources_comb).fillna(s)) # если map вернёт NaN, то используется оригинальный GUID
|
||||
.groupby(level=0) # Группируем обратно по исходному индексу
|
||||
.apply(lambda x: '\n'.join(x)) # Склеиваем через перенос строки
|
||||
)
|
||||
|
||||
# Добавляем поле с именем плана защиты. назовём IS, так как потом из него будет сделан номер IS. Важно - это делается до замены GUID-ов в archive_id!
|
||||
df_backups['IS'] = df_backups['archive_id'].map(dict_archives_plan)
|
||||
# Заменяем GUID-ы на значения
|
||||
df_backups['archive_id'] = df_backups['archive_id'].map(dict_archives).fillna(df_backups['archive_id'])
|
||||
df_backups['vault_id'] = df_backups['vault_id'].map(dict_vaults).fillna(df_backups['vault_id'])
|
||||
|
||||
# Добавляем столбец с типом устройства хранения - disk или tape.
|
||||
set_libs_vault_ids = set(df_backups[df_backups['tapes'].notna()]['vault_id'].to_list())
|
||||
df_archives['vault_type'] = df_archives['vault.name'].apply(lambda x: 'tape' if x in set_libs_vault_ids else 'disk')
|
||||
|
||||
# удаляем ненужные столбцы
|
||||
df_backups = df_backups.drop(columns=['legacy_id','tenant_id'])
|
||||
df_archives = df_archives.drop(columns=['vault_id','tenant_id','agent_id','backup_plan_id','vault.id','resource_id'])
|
||||
|
||||
# создаём отдельные списки по типу хранения диски или ленты.
|
||||
df_backups_disks = df_backups[df_backups['tapes'].isna()]
|
||||
df_backups_libs = df_backups[df_backups['tapes'].notna()]
|
||||
|
||||
# создаём отдельные списки по типу хранения диски или ленты в разрезе IS.
|
||||
df_backups_IS = df_backups[(df_backups['IS'].notna())&(df_backups['IS'].str.startswith('IS'))].reset_index(drop=True)
|
||||
df_backups_IS['IS'] = df_backups_IS['IS'].str.split('_').str.get(0)
|
||||
df_backups_IS_disks = df_backups_IS[df_backups_IS['tapes'].isna()]
|
||||
df_backups_IS_libs = df_backups_IS[df_backups_IS['tapes'].notna()]
|
||||
|
||||
# создаём отдельные списки по типу хранения диски или ленты для списка archives.
|
||||
df_archives_disks = df_archives[df_archives['vault_type']=='disk']
|
||||
df_archives_libs = df_archives[df_archives['vault_type']=='tape']
|
||||
# тоже, но в разрезе IS. Берём только те архивы, у которых в имени зашифрована IS и добавляем столбец с IS.
|
||||
df_archives_IS = df_archives[(df_archives['backup_plan_name'].notna())&(df_archives['backup_plan_name'].str.startswith('IS'))].reset_index(drop=True)
|
||||
df_archives_IS['IS'] = df_archives_IS['backup_plan_name'].str.split('_').str.get(0)
|
||||
df_archives_IS_disks = df_archives_IS[df_archives_IS['vault_type']=='disk'].reset_index(drop=True)
|
||||
df_archives_IS_libs = df_archives_IS[df_archives_IS['vault_type']=='lib'].reset_index(drop=True)
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
print(f'\033[32mБэкапов:\033[0m {len(df_backups)}')
|
||||
print(f'\033[32mАрхивов:\033[0m {len(df_archives)}')
|
||||
print(f'\033[32mВаультов:\033[0m {len(df_vaults)}')
|
||||
print(f'\033[32mПолитик:\033[0m {len(df_policies)}')
|
||||
print(f'\033[32mПрименений политик\033[0m: {len(df_policy_applications)}')
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
# Получение списка задач
|
||||
df_tasks = convert_time_many_cols(
|
||||
get_paginated_data(uri=f"https://{cb_srv}:9877/api/v1/tasks",headers=headers)
|
||||
)
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
# Статистика задач
|
||||
print(f"\033[32mВсего задач: {len(df_tasks)}")
|
||||
print(f"Самая старая задача: {df_tasks['started_at'].min()}")
|
||||
print(f"Самая новая задача: {df_tasks['started_at'].max()}")
|
||||
nl='\n'
|
||||
print(f"Перечень колонок у которых нет никаких значений:\033[0m \n{nl.join(df_tasks.columns[df_tasks.isna().all()].tolist())}")
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
df_archives_IS_disks.groupby(['IS']).agg(backEndOnDisk_TB=('size',lambda x: x.sum()/TB))
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
df_archives_disks.groupby('resource_type').agg(backEndOnDisk_TB=('size',lambda x: x.sum()/TB),dataIn_TB=('data_size',lambda x: x.sum()/TB))
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
df_archives_disks[['size','data_size']].sum().rename('TB')/TB
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
df_archives_disks['size'].sum()/TB
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
df_backups_IS_disks.query("type=='FULL'").groupby('archive_id').agg(backEndOnDisk_once_TB=('size',lambda x: x.max()/TB),dataFront_TB=('original_data_size',lambda x: x.max()/TB)).sum().rename('TB')
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
df_backups_disks.query("type=='FULL'").groupby('archive_id').agg(backEndOnDisk_once_TB=('size',lambda x: x.max()/TB),dataFront_TB=('original_data_size',lambda x: x.max()/TB)).sum().rename('TB')
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
## Вывод в Excel
|
||||
xlsWriter = pd.ExcelWriter(f"{dt.datetime.now().strftime('%Y-%m-%d[%H-%M]')}_{cb_srv}.xlsx", engine='openpyxl', mode='w')
|
||||
df_archives.to_excel(xlsWriter, sheet_name = "archives")
|
||||
df_backups.to_excel(xlsWriter, sheet_name = "backups")
|
||||
df_resources.to_excel(xlsWriter, sheet_name = "resources")
|
||||
for worksheet in xlsWriter.sheets.values():
|
||||
for column in worksheet.columns:
|
||||
length = max(len(str(cell.value)) for cell in column)
|
||||
length = length if length <= 50 else 50
|
||||
worksheet.column_dimensions[column[0].column_letter].width = length+2
|
||||
worksheet.auto_filter.ref = worksheet.dimensions
|
||||
worksheet.freeze_panes = 'A2'
|
||||
xlsWriter.close()
|
||||
|
||||
Reference in New Issue
Block a user