]>
Commit | Line | Data |
---|---|---|
b3b6e05e | 1 | from collections import OrderedDict |
f67539c2 | 2 | import errno |
f6b5b4d7 | 3 | try: |
b3b6e05e | 4 | from typing import Optional, List, Any, Dict |
f6b5b4d7 TL |
5 | except ImportError: |
6 | pass # just for type checking | |
7 | ||
8 | ||
f67539c2 TL |
9 | class SpecValidationError(Exception): |
10 | """ | |
11 | Defining an exception here is a bit problematic, cause you cannot properly catch it, | |
12 | if it was raised in a different mgr module. | |
13 | """ | |
14 | def __init__(self, | |
15 | msg: str, | |
16 | errno: int = -errno.EINVAL): | |
17 | super(SpecValidationError, self).__init__(msg) | |
18 | self.errno = errno | |
19 | ||
20 | ||
f6b5b4d7 TL |
21 | class HostSpec(object): |
22 | """ | |
23 | Information about hosts. Like e.g. ``kubectl get nodes`` | |
24 | """ | |
25 | def __init__(self, | |
b3b6e05e TL |
26 | hostname: str, |
27 | addr: Optional[str] = None, | |
28 | labels: Optional[List[str]] = None, | |
29 | status: Optional[str] = None, | |
30 | location: Optional[Dict[str, str]] = None, | |
f6b5b4d7 TL |
31 | ): |
32 | self.service_type = 'host' | |
33 | ||
34 | #: the bare hostname on the host. Not the FQDN. | |
35 | self.hostname = hostname # type: str | |
36 | ||
37 | #: DNS name or IP address to reach it | |
38 | self.addr = addr or hostname # type: str | |
39 | ||
40 | #: label(s), if any | |
41 | self.labels = labels or [] # type: List[str] | |
42 | ||
43 | #: human readable status | |
44 | self.status = status or '' # type: str | |
45 | ||
b3b6e05e TL |
46 | self.location = location |
47 | ||
48 | def to_json(self) -> Dict[str, Any]: | |
49 | r: Dict[str, Any] = { | |
f6b5b4d7 TL |
50 | 'hostname': self.hostname, |
51 | 'addr': self.addr, | |
b3b6e05e | 52 | 'labels': list(OrderedDict.fromkeys((self.labels))), |
f6b5b4d7 TL |
53 | 'status': self.status, |
54 | } | |
b3b6e05e TL |
55 | if self.location: |
56 | r['location'] = self.location | |
57 | return r | |
f6b5b4d7 TL |
58 | |
59 | @classmethod | |
f67539c2 TL |
60 | def from_json(cls, host_spec: dict) -> 'HostSpec': |
61 | host_spec = cls.normalize_json(host_spec) | |
b3b6e05e TL |
62 | _cls = cls( |
63 | host_spec['hostname'], | |
64 | host_spec['addr'] if 'addr' in host_spec else None, | |
65 | list(OrderedDict.fromkeys( | |
66 | host_spec['labels'])) if 'labels' in host_spec else None, | |
67 | host_spec['status'] if 'status' in host_spec else None, | |
68 | host_spec.get('location'), | |
69 | ) | |
f6b5b4d7 TL |
70 | return _cls |
71 | ||
f67539c2 TL |
72 | @staticmethod |
73 | def normalize_json(host_spec: dict) -> dict: | |
74 | labels = host_spec.get('labels') | |
b3b6e05e TL |
75 | if labels is not None: |
76 | if isinstance(labels, str): | |
77 | host_spec['labels'] = [labels] | |
78 | elif ( | |
79 | not isinstance(labels, list) | |
80 | or any(not isinstance(v, str) for v in labels) | |
81 | ): | |
82 | raise SpecValidationError( | |
83 | f'Labels ({labels}) must be a string or list of strings' | |
84 | ) | |
85 | ||
86 | loc = host_spec.get('location') | |
87 | if loc is not None: | |
88 | if ( | |
89 | not isinstance(loc, dict) | |
90 | or any(not isinstance(k, str) for k in loc.keys()) | |
91 | or any(not isinstance(v, str) for v in loc.values()) | |
92 | ): | |
93 | raise SpecValidationError( | |
94 | f'Location ({loc}) must be a dictionary of strings to strings' | |
95 | ) | |
96 | ||
f67539c2 TL |
97 | return host_spec |
98 | ||
99 | def __repr__(self) -> str: | |
f6b5b4d7 TL |
100 | args = [self.hostname] # type: List[Any] |
101 | if self.addr is not None: | |
102 | args.append(self.addr) | |
103 | if self.labels: | |
104 | args.append(self.labels) | |
105 | if self.status: | |
106 | args.append(self.status) | |
b3b6e05e TL |
107 | if self.location: |
108 | args.append(self.location) | |
f6b5b4d7 TL |
109 | |
110 | return "HostSpec({})".format(', '.join(map(repr, args))) | |
111 | ||
f67539c2 | 112 | def __str__(self) -> str: |
f6b5b4d7 TL |
113 | if self.hostname != self.addr: |
114 | return f'{self.hostname} ({self.addr})' | |
115 | return self.hostname | |
116 | ||
f67539c2 | 117 | def __eq__(self, other: Any) -> bool: |
f6b5b4d7 TL |
118 | # Let's omit `status` for the moment, as it is still the very same host. |
119 | return self.hostname == other.hostname and \ | |
120 | self.addr == other.addr and \ | |
b3b6e05e TL |
121 | sorted(self.labels) == sorted(other.labels) and \ |
122 | self.location == other.location |