5 # The contents of this file are subject to the terms of the
6 # Common Development and Distribution License (the "License").
7 # You may not use this file except in compliance with the License.
9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 # or http://www.opensolaris.org/os/licensing.
11 # See the License for the specific language governing permissions
12 # and limitations under the License.
14 # When distributing Covered Code, include this CDDL HEADER in each
15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 # If applicable, add the following below this CDDL HEADER, with the
17 # fields enclosed by brackets "[]" replaced with your own identifying
18 # information: Portions Copyright [yyyy] [name of copyright owner]
27 from bisect import insort
29 import time_slider.util
30 import time_slider.smf
31 import time_slider.zfs
33 # Set to True if SMF property value of "plugin/command" is "true"
34 verboseprop = "plugin/verbose"
35 propbasename = "org.opensolaris:time-slider-plugin"
36 print(_("Do I work?"))
40 # Check appropriate environment variables habe been supplied
43 # The label used for the snapshot set just taken, ie. the
44 # component proceeding the "@" in the snapshot name
45 snaplabel = os.getenv("AUTOSNAP_LABEL")
46 # The SMF fmri of the auto-snapshot instance corresponding to
47 # the snapshot set just taken.
48 snapfmri = os.getenv("AUTOSNAP_FMRI")
49 # The SMF fmri of the time-slider plugin instance associated with
51 pluginfmri = os.getenv("PLUGIN_FMRI")
53 if pluginfmri == None:
54 sys.stderr.write("No time-slider plugin SMF instance FMRI defined. " \
55 "This plugin does not support command line "
56 "execution. Exiting\n")
58 syslog.openlog(pluginfmri, 0, syslog.LOG_DAEMON)
60 cmd = [smf.SVCPROPCMD, "-p", verboseprop, pluginfmri]
61 outdata,errdata = util.run_command(cmd)
62 if outdata.rstrip() == "true":
68 log_error(syslog.LOG_ERR,
69 "No snapshot label defined. Exiting")
72 log_error(syslog.LOG_ERR,
73 "No auto-snapshot SMF instance FMRI defined. Exiting")
76 schedule = snapfmri.rsplit(':', 1)[1]
77 plugininstance = pluginfmri.rsplit(':', 1)[1]
79 # The user property/tag used when tagging and holding zfs datasets
80 propname = "%s:%s" % (propbasename, plugininstance)
82 # Identifying snapshots is a two stage process.
84 # First: identify all snapshots matching the AUTOSNAP_LABEL
85 # value passed in by the time-slider daemon.
87 # Second: we need to filter the results and ensure that the
88 # filesystem/voluem corresponding to each snapshot is actually
89 # tagged with the property (com.sun:auto-snapshot<:schedule>)
91 # This is necessary to avoid confusion whereby a snapshot might
92 # have been sent|received from one zpool to another on the same
93 # system. The received snapshot will show up in the first pass
94 # results but is not actually part of the auto-snapshot set
95 # created by time-slider. It also avoids incorrectly placing
96 # zfs holds on the imported snapshots.
98 datasets = zfs.Datasets()
99 candidates = datasets.list_snapshots(snaplabel)
100 originsets = datasets.list_auto_snapshot_sets(schedule)
102 snapnames = [name for [name,ctime] in candidates \
103 if name.split('@',1)[0] in originsets]
106 # Place a hold on the the newly created snapshots so
107 # they can be backed up without fear of being destroyed
108 # before the backup gets a chance to complete.
109 for snap in snapnames:
110 snapshot = zfs.Snapshot(snap)
111 holds = snapshot.holds()
113 holds.index(propname)
115 util.debug("Placing hold on %s" % (snap), verbose)
116 snapshot.hold(propname)
117 datasetname = snapshot.fsname
118 # Insert datasetnames in alphabetically sorted order because
119 # zfs receive falls over if it receives a child before the
120 # parent if the "-F" option is not used.
121 insort(snappeddatasets, datasetname)
123 # Find out the receive command property value
124 cmd = [smf.SVCPROPCMD, "-c", "-p", "receive/command", pluginfmri]
125 outdata,errdata = util.run_command(cmd)
126 # Strip out '\' characters inserted by svcprop
127 recvcmd = outdata.strip().replace('\\', '').split()
129 # Check to see if the receive command is accessible and executable
131 statinfo = os.stat(recvcmd[0])
132 other_x = (statinfo.st_mode & 0o1)
134 log_error(syslog.LOG_ERR,
135 "Plugin: %s: Configured receive/command is not " \
137 % (pluginfmri, outdata))
138 maintenance(pluginfmri)
141 log_error(syslog.LOG_ERR,
142 "Plugin: %s: Can not access the configured " \
143 "receive/command: %s" \
144 % (pluginfmri, outdata))
145 maintenance(pluginfmri)
148 for dataset in snappeddatasets:
151 ds = zfs.ReadableDataset(dataset)
152 prevlabel = ds.get_user_property(propname)
154 snapname = "%s@%s" % (ds.name, snaplabel)
155 if (prevlabel == None or len(prevlabel) == 0):
156 # No previous backup - send a full replication stream
157 sendcmd = [zfs.ZFSCMD, "send", snapname]
158 util.debug("No previous backup registered for %s" % ds.name, verbose)
160 # A record of a previous backup exists.
161 # Check that it exists to enable send of an incremental stream.
162 prevsnapname = "%s@%s" % (ds.name, prevlabel)
163 util.debug("Previously sent snapshot: %s" % prevsnapname, verbose)
164 prevsnap = zfs.Snapshot(prevsnapname)
165 if prevsnap.exists():
166 sendcmd = [zfs.ZFSCMD, "send", "-i", prevsnapname, snapname]
168 # This should not happen under normal operation since we
169 # place a hold on the snapshot until it gets sent. So
170 # getting here suggests that something else released the
171 # hold on the snapshot, allowing it to get destroyed
173 log_error(syslog.LOG_ERR,
174 "Previously sent snapshot no longer exists: %s" \
176 maintenance(pluginfmri)
181 sendP = subprocess.Popen(sendcmd,
182 stdout=subprocess.PIPE,
183 stderr=subprocess.PIPE,
185 recvP = subprocess.Popen(recvcmd,
187 stderr=subprocess.PIPE,
190 recvout,recverr = recvP.communicate()
191 recverrno = recvP.wait()
192 sendout,senderr = sendP.communicate()
193 senderrno = sendP.wait()
196 raise RuntimeError("Send command: %s failed with exit code" \
197 "%d. Error message: \n%s" \
198 % (str(sendcmd), senderrno, senderr))
200 raise RuntimeError("Receive command %s failed with exit " \
201 "code %d. Error message: \n%s" \
202 % (str(recvcmd), recverrno, recverr))
204 if prevsnapname != None:
205 util.debug("Releasing hold on %s" % (prevsnapname), verbose)
206 snapshot = zfs.Snapshot(prevsnapname)
207 util.debug("Releasing hold on previous snapshot: %s" \
210 snapshot.release(propname)
211 except Exception as message:
212 log_error(syslog.LOG_ERR,
213 "Error during snapshot send/receive operation: %s" \
216 maintenance(pluginfmri)
219 # Finally, after success, make a record of the latest backup
220 # and release the old snapshot.
221 ds.set_user_property(propname, snaplabel)
222 util.debug("Sending of \"%s\"snapshot streams completed" \
226 def maintenance(svcfmri):
227 log_error(syslog.LOG_ERR,
228 "Placing plugin into maintenance state")
229 cmd = [smf.SVCADMCMD, "mark", "maintenance", svcfmri]
230 subprocess.Popen(cmd, close_fds=True)
232 def log_error(loglevel, message):
233 syslog.syslog(loglevel, message + '\n')
234 sys.stderr.write(message + '\n')