1"""
2Managing software RAID with mdadm
3==================================
4
5:depends:    mdadm
6
7A state module for creating or destroying software RAID devices.
8
9.. code-block:: yaml
10
11    /dev/md0:
12      raid.present:
13        - level: 5
14        - devices:
15          - /dev/xvdd
16          - /dev/xvde
17          - /dev/xvdf
18        - chunk: 256
19        - run: True
20"""
21
22
23import logging
24
25import salt.utils.path
26
27# Set up logger
28log = logging.getLogger(__name__)
29
30# Define the module's virtual name
31__virtualname__ = "raid"
32
33
34def __virtual__():
35    """
36    mdadm provides raid functions for Linux
37    """
38    if __grains__["kernel"] != "Linux":
39        return (False, "Only supported on Linux")
40    if not salt.utils.path.which("mdadm"):
41        return (False, "Unable to locate command: mdadm")
42    return __virtualname__
43
44
45def present(name, level, devices, **kwargs):
46    """
47    Verify that the raid is present
48
49    .. versionchanged:: 2014.7.0
50
51    name
52        The name of raid device to be created
53
54    level
55                The RAID level to use when creating the raid.
56
57    devices
58        A list of devices used to build the array.
59
60    kwargs
61        Optional arguments to be passed to mdadm.
62
63    Example:
64
65    .. code-block:: yaml
66
67        /dev/md0:
68          raid.present:
69            - level: 5
70            - devices:
71              - /dev/xvdd
72              - /dev/xvde
73              - /dev/xvdf
74            - chunk: 256
75            - run: True
76    """
77    ret = {"changes": {}, "comment": "", "name": name, "result": True}
78
79    # Device exists
80    raids = __salt__["raid.list"]()
81    present = raids.get(name)
82
83    # Decide whether to create or assemble
84    missing = []
85    uuid_dict = {}
86    new_devices = []
87
88    for dev in devices:
89        if dev == "missing" or not __salt__["file.access"](dev, "f"):
90            missing.append(dev)
91            continue
92        superblock = __salt__["raid.examine"](dev, quiet=True)
93
94        if "MD_UUID" in superblock:
95            uuid = superblock["MD_UUID"]
96            if uuid not in uuid_dict:
97                uuid_dict[uuid] = []
98            uuid_dict[uuid].append(dev)
99        else:
100            new_devices.append(dev)
101
102    if len(uuid_dict) > 1:
103        ret[
104            "comment"
105        ] = "Devices are a mix of RAID constituents with multiple MD_UUIDs: {}.".format(
106            sorted(uuid_dict)
107        )
108        ret["result"] = False
109        return ret
110    elif len(uuid_dict) == 1:
111        uuid = next(iter(uuid_dict))
112        if present and present["uuid"] != uuid:
113            ret[
114                "comment"
115            ] = "Devices MD_UUIDs: {} differs from present RAID uuid {}.".format(
116                uuid, present["uuid"]
117            )
118            ret["result"] = False
119            return ret
120
121        devices_with_superblock = uuid_dict[uuid]
122    else:
123        devices_with_superblock = []
124
125    if present:
126        do_assemble = False
127        do_create = False
128    elif len(devices_with_superblock) > 0:
129        do_assemble = True
130        do_create = False
131        verb = "assembled"
132    else:
133        if len(new_devices) == 0:
134            ret["comment"] = "All devices are missing: {}.".format(missing)
135            ret["result"] = False
136            return ret
137        do_assemble = False
138        do_create = True
139        verb = "created"
140
141    # If running with test use the test_mode with create or assemble
142    if __opts__["test"]:
143        if do_assemble:
144            res = __salt__["raid.assemble"](
145                name, devices_with_superblock, test_mode=True, **kwargs
146            )
147        elif do_create:
148            res = __salt__["raid.create"](
149                name,
150                level,
151                new_devices + ["missing"] * len(missing),
152                test_mode=True,
153                **kwargs
154            )
155
156        if present:
157            ret["comment"] = "Raid {} already present.".format(name)
158
159        if do_assemble or do_create:
160            ret["comment"] = "Raid will be {} with: {}".format(verb, res)
161            ret["result"] = None
162
163        if (do_assemble or present) and len(new_devices) > 0:
164            ret["comment"] += " New devices will be added: {}".format(new_devices)
165            ret["result"] = None
166
167        if len(missing) > 0:
168            ret["comment"] += " Missing devices: {}".format(missing)
169
170        return ret
171
172    # Attempt to create or assemble the array
173    if do_assemble:
174        __salt__["raid.assemble"](name, devices_with_superblock, **kwargs)
175    elif do_create:
176        __salt__["raid.create"](
177            name, level, new_devices + ["missing"] * len(missing), **kwargs
178        )
179
180    if not present:
181        raids = __salt__["raid.list"]()
182        changes = raids.get(name)
183        if changes:
184            ret["comment"] = "Raid {} {}.".format(name, verb)
185            ret["changes"] = changes
186            # Saving config
187            __salt__["raid.save_config"]()
188        else:
189            ret["comment"] = "Raid {} failed to be {}.".format(name, verb)
190            ret["result"] = False
191    else:
192        ret["comment"] = "Raid {} already present.".format(name)
193
194    if (do_assemble or present) and len(new_devices) > 0 and ret["result"]:
195        for d in new_devices:
196            res = __salt__["raid.add"](name, d)
197            if not res:
198                ret["comment"] += " Unable to add {} to {}.\n".format(d, name)
199                ret["result"] = False
200            else:
201                ret["comment"] += " Added new device {} to {}.\n".format(d, name)
202        if ret["result"]:
203            ret["changes"]["added"] = new_devices
204
205    if len(missing) > 0:
206        ret["comment"] += " Missing devices: {}".format(missing)
207
208    return ret
209
210
211def absent(name):
212    """
213    Verify that the raid is absent
214
215    name
216        The name of raid device to be destroyed
217
218    .. code-block:: yaml
219
220        /dev/md0:
221          raid:
222            - absent
223    """
224    ret = {"changes": {}, "comment": "", "name": name, "result": True}
225
226    # Raid does not exist
227    if name not in __salt__["raid.list"]():
228        ret["comment"] = "Raid {} already absent".format(name)
229        return ret
230    elif __opts__["test"]:
231        ret["comment"] = "Raid {} is set to be destroyed".format(name)
232        ret["result"] = None
233        return ret
234    else:
235        # Attempt to destroy raid
236        ret["result"] = __salt__["raid.destroy"](name)
237
238        if ret["result"]:
239            ret["comment"] = "Raid {} has been destroyed".format(name)
240        else:
241            ret["comment"] = "Raid {} failed to be destroyed".format(name)
242        return ret
243