migrate 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. #!/usr/bin/python3
  2. import json
  3. import os
  4. import sys
  5. import subprocess
  6. from pathlib import Path
  7. from middlewared.client import Client
  8. from middlewared.service import ValidationErrors, CallError
  9. def path_in_locked_datasets(path: str) -> bool:
  10. with Client() as c:
  11. return c.call('pool.dataset.path_in_locked_datasets', path)
  12. def get_configured_user_group(path: str) -> dict:
  13. with Client() as c:
  14. return c.call('filesystem.stat', path)
  15. def get_host_path_attachments(path: str) -> set:
  16. with Client() as c:
  17. return {
  18. attachment['type']
  19. for attachment in c.call('pool.dataset.attachments_with_path', path)
  20. if attachment['type'].lower() not in ['kubernetes', 'chart releases']
  21. }
  22. def get_kubernetes_config() -> dict:
  23. with Client() as c:
  24. return c.call('kubernetes.config')
  25. def validate_host_path(path: str, schema_name: str, verrors: ValidationErrors) -> None:
  26. """
  27. These validations are taken from `FilesystemService._common_perm_path_validate`.
  28. Including an additional validation that makes sure all the children under
  29. a path are on same device.
  30. """
  31. schema_name += ".migration.chown"
  32. p = Path(path)
  33. if not p.is_absolute():
  34. verrors.add(schema_name, f"Must be an absolute path: {path}")
  35. if p.is_file():
  36. verrors.add(schema_name, f"Recursive operations on a file are invalid: {path}")
  37. if not p.absolute().as_posix().startswith("/mnt/"):
  38. verrors.add(
  39. schema_name,
  40. f"Changes to permissions on paths that are not beneath the directory /mnt are not permitted: {path}"
  41. )
  42. elif len(p.resolve().parents) == 2:
  43. verrors.add(schema_name, f"The specified path is a ZFS pool mountpoint: {path}")
  44. # Make sure that dataset is not locked
  45. if path_in_locked_datasets(path):
  46. verrors.add(schema_name, f"Dataset is locked at path: {path}.")
  47. # Validate attachments
  48. if attachments := get_host_path_attachments(path):
  49. verrors.add(schema_name, f"The path '{path}' is already attached to service(s): {', '.join(attachments)}.")
  50. # Make sure all the minio's data directory children are on same device.
  51. device_id = os.stat(path).st_dev
  52. for root, dirs, files in os.walk(path):
  53. for child in dirs + files:
  54. abs_path = os.path.join(root, child)
  55. if os.stat(abs_path).st_dev != device_id:
  56. verrors.add(
  57. schema_name,
  58. (f"All the children of MinIO data directory should be on "
  59. f"same device as root: path={abs_path} device={os.stat(abs_path).st_dev}")
  60. )
  61. break
  62. def migrate(values: dict) -> dict:
  63. # minio user / group ID
  64. uid = gid = 473
  65. verrors = ValidationErrors()
  66. k8s_config = get_kubernetes_config()
  67. if values["appVolumeMounts"]["export"]["hostPathEnabled"]:
  68. host_path = values["appVolumeMounts"]["export"]["hostPath"]
  69. else:
  70. app_dataset = values["appVolumeMounts"]["export"]["datasetName"]
  71. host_path = os.path.join(
  72. "/mnt", k8s_config['dataset'], "releases", values["release_name"], "volumes/ix_volumes", app_dataset
  73. )
  74. current_config = get_configured_user_group(host_path)
  75. if current_config["uid"] == uid and current_config["gid"] == gid:
  76. return values
  77. validate_host_path(host_path, values['release_name'], verrors)
  78. verrors.check()
  79. # chown the host path
  80. acltool = subprocess.run([
  81. "/usr/bin/nfs4xdr_winacl",
  82. "-a", "chown",
  83. "-O", str(uid), "-G", str(gid),
  84. "-r",
  85. "-c", host_path,
  86. "-p", host_path], check=False, capture_output=True
  87. )
  88. if acltool.returncode != 0:
  89. raise CallError(f"acltool [chown] on path {host_path} failed with error: [{acltool.stderr.decode().strip()}]")
  90. return values
  91. if __name__ == "__main__":
  92. if len(sys.argv) != 2:
  93. exit(1)
  94. if os.path.exists(sys.argv[1]):
  95. with open(sys.argv[1], "r") as f:
  96. print(json.dumps(migrate(json.loads(f.read()))))