]> git.proxmox.com Git - ceph.git/blame - ceph/src/python-common/ceph/deployment/hostspec.py
bump version to 19.2.0-pve1
[ceph.git] / ceph / src / python-common / ceph / deployment / hostspec.py
CommitLineData
b3b6e05e 1from collections import OrderedDict
f67539c2 2import errno
20effc67
TL
3import re
4from typing import Optional, List, Any, Dict
5
6
7def assert_valid_host(name: str) -> None:
8 p = re.compile('^[a-zA-Z0-9-]+$')
9 try:
10 assert len(name) <= 250, 'name is too long (max 250 chars)'
11 for part in name.split('.'):
12 assert len(part) > 0, '.-delimited name component must not be empty'
13 assert len(part) <= 63, '.-delimited name component must not be more than 63 chars'
14 assert p.match(part), 'name component must include only a-z, 0-9, and -'
15 except AssertionError as e:
2a845540 16 raise SpecValidationError(str(e) + f'. Got "{name}"')
f6b5b4d7
TL
17
18
f38dd50b
TL
19def assert_valid_oob(oob: Dict[str, str]) -> None:
20 fields = ['username', 'password']
21 try:
22 for field in fields:
23 assert field in oob.keys()
24 except AssertionError as e:
25 raise SpecValidationError(str(e))
26
27
f67539c2
TL
28class SpecValidationError(Exception):
29 """
30 Defining an exception here is a bit problematic, cause you cannot properly catch it,
31 if it was raised in a different mgr module.
32 """
33 def __init__(self,
34 msg: str,
35 errno: int = -errno.EINVAL):
36 super(SpecValidationError, self).__init__(msg)
37 self.errno = errno
38
39
f6b5b4d7
TL
40class HostSpec(object):
41 """
42 Information about hosts. Like e.g. ``kubectl get nodes``
43 """
44 def __init__(self,
b3b6e05e
TL
45 hostname: str,
46 addr: Optional[str] = None,
47 labels: Optional[List[str]] = None,
48 status: Optional[str] = None,
49 location: Optional[Dict[str, str]] = None,
f38dd50b 50 oob: Optional[Dict[str, str]] = None,
f6b5b4d7
TL
51 ):
52 self.service_type = 'host'
53
54 #: the bare hostname on the host. Not the FQDN.
55 self.hostname = hostname # type: str
56
57 #: DNS name or IP address to reach it
58 self.addr = addr or hostname # type: str
59
60 #: label(s), if any
61 self.labels = labels or [] # type: List[str]
62
63 #: human readable status
64 self.status = status or '' # type: str
65
b3b6e05e
TL
66 self.location = location
67
f38dd50b
TL
68 #: oob details, if provided
69 self.oob = oob
70
20effc67
TL
71 def validate(self) -> None:
72 assert_valid_host(self.hostname)
f38dd50b
TL
73 if self.oob:
74 assert_valid_oob(self.oob)
20effc67 75
b3b6e05e
TL
76 def to_json(self) -> Dict[str, Any]:
77 r: Dict[str, Any] = {
f6b5b4d7
TL
78 'hostname': self.hostname,
79 'addr': self.addr,
b3b6e05e 80 'labels': list(OrderedDict.fromkeys((self.labels))),
f6b5b4d7
TL
81 'status': self.status,
82 }
b3b6e05e
TL
83 if self.location:
84 r['location'] = self.location
f38dd50b
TL
85 if self.oob:
86 r['oob'] = self.oob
b3b6e05e 87 return r
f6b5b4d7
TL
88
89 @classmethod
f67539c2
TL
90 def from_json(cls, host_spec: dict) -> 'HostSpec':
91 host_spec = cls.normalize_json(host_spec)
b3b6e05e
TL
92 _cls = cls(
93 host_spec['hostname'],
94 host_spec['addr'] if 'addr' in host_spec else None,
95 list(OrderedDict.fromkeys(
96 host_spec['labels'])) if 'labels' in host_spec else None,
97 host_spec['status'] if 'status' in host_spec else None,
98 host_spec.get('location'),
f38dd50b 99 host_spec['oob'] if 'oob' in host_spec else None,
b3b6e05e 100 )
f6b5b4d7
TL
101 return _cls
102
f67539c2
TL
103 @staticmethod
104 def normalize_json(host_spec: dict) -> dict:
105 labels = host_spec.get('labels')
b3b6e05e
TL
106 if labels is not None:
107 if isinstance(labels, str):
108 host_spec['labels'] = [labels]
109 elif (
110 not isinstance(labels, list)
111 or any(not isinstance(v, str) for v in labels)
112 ):
113 raise SpecValidationError(
114 f'Labels ({labels}) must be a string or list of strings'
115 )
116
117 loc = host_spec.get('location')
118 if loc is not None:
119 if (
120 not isinstance(loc, dict)
121 or any(not isinstance(k, str) for k in loc.keys())
122 or any(not isinstance(v, str) for v in loc.values())
123 ):
124 raise SpecValidationError(
125 f'Location ({loc}) must be a dictionary of strings to strings'
126 )
127
f67539c2
TL
128 return host_spec
129
130 def __repr__(self) -> str:
f6b5b4d7
TL
131 args = [self.hostname] # type: List[Any]
132 if self.addr is not None:
133 args.append(self.addr)
134 if self.labels:
135 args.append(self.labels)
136 if self.status:
137 args.append(self.status)
b3b6e05e
TL
138 if self.location:
139 args.append(self.location)
f6b5b4d7
TL
140
141 return "HostSpec({})".format(', '.join(map(repr, args)))
142
f67539c2 143 def __str__(self) -> str:
f6b5b4d7
TL
144 if self.hostname != self.addr:
145 return f'{self.hostname} ({self.addr})'
146 return self.hostname
147
f67539c2 148 def __eq__(self, other: Any) -> bool:
f6b5b4d7 149 # Let's omit `status` for the moment, as it is still the very same host.
39ae355f
TL
150 if not isinstance(other, HostSpec):
151 return NotImplemented
f6b5b4d7 152 return self.hostname == other.hostname and \
39ae355f
TL
153 self.addr == other.addr and \
154 sorted(self.labels) == sorted(other.labels) and \
155 self.location == other.location