Initial attempt at python3 conversion
[time-slider.git] / usr / share / time-slider / lib / plugin / zfssend / zfssend.py
1 #!/usr/bin/python2
2 #
3 # CDDL HEADER START
4 #
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.
8 #
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.
13 #
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]
19 #
20 # CDDL HEADER END
21 #
22
23 import os
24 import sys
25 import subprocess
26 import syslog
27 from bisect import insort
28
29 import time_slider.util
30 import time_slider.smf
31 import time_slider.zfs
32
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?"))
37
38 def main(argv):
39
40     # Check appropriate environment variables habe been supplied
41     # by time-slider
42     #
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
50     # this command.
51     pluginfmri = os.getenv("PLUGIN_FMRI")
52
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")
57         sys.exit(-1)
58     syslog.openlog(pluginfmri, 0, syslog.LOG_DAEMON)
59
60     cmd = [smf.SVCPROPCMD, "-p", verboseprop, pluginfmri]
61     outdata,errdata = util.run_command(cmd)
62     if outdata.rstrip() == "true":
63         verbose = True
64     else:
65         verbose = False
66
67     if snaplabel == None:
68         log_error(syslog.LOG_ERR,
69                   "No snapshot label defined. Exiting")
70         sys.exit(-1)
71     if snapfmri == None:
72         log_error(syslog.LOG_ERR,
73                   "No auto-snapshot SMF instance FMRI defined. Exiting")
74         sys.exit(-1)
75
76     schedule = snapfmri.rsplit(':', 1)[1]
77     plugininstance = pluginfmri.rsplit(':', 1)[1]
78
79     # The user property/tag used when tagging and holding zfs datasets
80     propname = "%s:%s" % (propbasename, plugininstance)
81
82     # Identifying snapshots is a two stage process.
83     #
84     # First: identify all snapshots matching the AUTOSNAP_LABEL
85     # value passed in by the time-slider daemon.
86     #
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>)
90     #
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.
97
98     datasets = zfs.Datasets()
99     candidates = datasets.list_snapshots(snaplabel)
100     originsets = datasets.list_auto_snapshot_sets(schedule)
101     snappeddatasets = []
102     snapnames = [name for [name,ctime] in candidates \
103                  if name.split('@',1)[0] in originsets]
104
105
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()
112         try:
113             holds.index(propname)
114         except ValueError:
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)
122
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()
128
129     # Check to see if the receive command is accessible and executable
130     try:
131         statinfo = os.stat(recvcmd[0])
132         other_x = (statinfo.st_mode & 0o1)
133         if other_x == 0:
134             log_error(syslog.LOG_ERR,
135                       "Plugin: %s: Configured receive/command is not " \
136                       "executable: %s" \
137                       % (pluginfmri, outdata))
138             maintenance(pluginfmri)
139             sys.exit(-1)
140     except OSError:
141         log_error(syslog.LOG_ERR,
142                   "Plugin: %s: Can not access the configured " \
143                   "receive/command: %s" \
144                   % (pluginfmri, outdata)) 
145         maintenance(pluginfmri)   
146         sys.exit(-1)
147
148     for dataset in snappeddatasets:
149         sendcmd = None
150         prevsnapname = None
151         ds = zfs.ReadableDataset(dataset)
152         prevlabel = ds.get_user_property(propname)
153
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)
159         else:
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]
167             else:
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
172                 # prematurely.
173                 log_error(syslog.LOG_ERR,
174                           "Previously sent snapshot no longer exists: %s" \
175                           % prevsnapname)
176                 maintenance(pluginfmri)
177                 sys.exit(-1)
178         
179         
180         try:
181             sendP = subprocess.Popen(sendcmd,
182                                      stdout=subprocess.PIPE,
183                                      stderr=subprocess.PIPE,
184                                      close_fds=True)
185             recvP = subprocess.Popen(recvcmd,
186                                      stdin=sendP.stdout,
187                                      stderr=subprocess.PIPE,
188                                      close_fds=True)
189
190             recvout,recverr = recvP.communicate()
191             recverrno = recvP.wait()
192             sendout,senderr = sendP.communicate()
193             senderrno = sendP.wait()
194
195             if senderrno != 0:
196                 raise RuntimeError("Send command: %s failed with exit code" \
197                                     "%d. Error message: \n%s" \
198                                     % (str(sendcmd), senderrno, senderr))
199             if recverrno != 0:
200                 raise RuntimeError("Receive command %s failed with exit " \
201                                     "code %d. Error message: \n%s" \
202                                     % (str(recvcmd), recverrno, recverr))
203
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" \
208                       % (prevsnapname),
209                       verbose)
210                 snapshot.release(propname)
211         except Exception as message:
212             log_error(syslog.LOG_ERR,
213                       "Error during snapshot send/receive operation: %s" \
214                       % (message))
215
216             maintenance(pluginfmri)
217             sys.exit(-1)            
218
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" \
223               % (snaplabel),
224               verbose)
225
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)
231
232 def log_error(loglevel, message):
233     syslog.syslog(loglevel, message + '\n')
234     sys.stderr.write(message + '\n')
235