View Javadoc

1   package org.jscsi.target;
2   
3   
4   import java.io.BufferedReader;
5   import java.io.File;
6   import java.io.IOException;
7   import java.io.InputStreamReader;
8   import java.net.InetAddress;
9   import java.net.InetSocketAddress;
10  import java.net.NetworkInterface;
11  import java.nio.channels.ServerSocketChannel;
12  import java.nio.channels.SocketChannel;
13  import java.security.DigestException;
14  import java.util.ArrayList;
15  import java.util.Collection;
16  import java.util.Enumeration;
17  import java.util.HashMap;
18  import java.util.List;
19  import java.util.Vector;
20  import java.util.concurrent.Callable;
21  import java.util.concurrent.atomic.AtomicInteger;
22  
23  import org.jscsi.exception.InternetSCSIException;
24  import org.jscsi.parser.OperationCode;
25  import org.jscsi.parser.ProtocolDataUnit;
26  import org.jscsi.parser.login.ISID;
27  import org.jscsi.parser.login.LoginRequestParser;
28  import org.jscsi.target.connection.Connection;
29  import org.jscsi.target.connection.Connection.TargetConnection;
30  import org.jscsi.target.connection.TargetSession;
31  import org.jscsi.target.scsi.inquiry.DeviceIdentificationVpdPage;
32  import org.jscsi.target.settings.SettingsException;
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  
36  
37  /**
38   * The central class of the jSCSI Target, which keeps track of all active {@link TargetSession}s, stores target-wide
39   * parameters and variables, and which contains the {@link #main(String[])} method for starting the program.
40   * 
41   * @author Andreas Ergenzinger, University of Konstanz
42   * @author Sebastian Graf, University of Konstanz
43   */
44  public final class TargetServer implements Callable<Void> {
45  
46      private static final Logger LOGGER = LoggerFactory.getLogger(TargetServer.class);
47  
48      /**
49       * A {@link SocketChannel} used for listening to incoming connections.
50       */
51      private ServerSocketChannel serverSocketChannel;
52  
53      /**
54       * Contains all active {@link TargetSession}s.
55       */
56      private Collection<TargetSession> sessions = new Vector<TargetSession>();
57  
58      /**
59       * The jSCSI Target's global parameters.
60       */
61      private Configuration config;
62  
63      /**
64       * 
65       */
66      private DeviceIdentificationVpdPage deviceIdentificationVpdPage;
67  
68      /**
69       * The table of targets
70       */
71      private HashMap<String , Target> targets = new HashMap<String , Target>();
72  
73      /**
74       * A target-wide counter used for providing the value of sent {@link ProtocolDataUnit}s'
75       * <code>Target Transfer Tag</code> field, unless that field is reserved.
76       */
77      private static final AtomicInteger nextTargetTransferTag = new AtomicInteger();
78  
79      /**
80       * The connection the target server is using.
81       */
82      private Connection connection;
83      
84      /**
85       * while this value is true, the target is active.
86       */
87      private boolean running = true;
88  
89      public TargetServer (final Configuration conf) {
90          this.config = conf;
91  
92          LOGGER.debug("Starting jSCSI-target: ");
93  
94          // read target settings from configuration file
95  
96          LOGGER.debug("   port:           " + getConfig().getPort());
97          LOGGER.debug("   loading targets.");
98          // open the storage medium
99          List<Target> targetInfo = getConfig().getTargets();
100         for (Target curTargetInfo : targetInfo) {
101 
102             targets.put(curTargetInfo.getTargetName(), curTargetInfo);
103             // print configuration and medium details
104             LOGGER.debug("   target name:    " + curTargetInfo.getTargetName() + " loaded.");
105         }
106 
107         this.deviceIdentificationVpdPage = new DeviceIdentificationVpdPage(this);
108     }
109 
110     /**
111      * Gets and increments the value to use in the next unreserved <code>Target Transfer Tag</code> field of the next
112      * PDU to be sent by the jSCSI Target.
113      * 
114      * @see #nextTargetTransferTag
115      * @return the value to use in the next unreserved <code>Target Transfer Tag
116      * </code> field
117      */
118     public static int getNextTargetTransferTag () {
119         // value 0xffffffff is reserved
120         int tag;
121         do {
122             tag = nextTargetTransferTag.getAndIncrement();
123         } while (tag == -1);
124         return tag;
125     }
126 
127     /**
128      * Starts the jSCSI target.
129      * 
130      * @param args all command line arguments are ignored
131      * @throws IOException
132      */
133     public static void main (String[] args) throws Exception {
134         TargetServer target;
135 
136         System.out.println("This system provides more than one IP Address to advertise.\n");
137 
138         Enumeration<NetworkInterface> interfaceEnum = NetworkInterface.getNetworkInterfaces();
139         NetworkInterface i;
140         int addressCounter = 0;
141         List<InetAddress> addresses = new ArrayList<InetAddress>();
142         while (interfaceEnum.hasMoreElements()) {
143             i = interfaceEnum.nextElement();
144             Enumeration<InetAddress> addressEnum = i.getInetAddresses();
145             InetAddress address;
146 
147             while (addressEnum.hasMoreElements()) {
148                 address = addressEnum.nextElement();
149                 System.out.println("[" + addressCounter + "] " + address.getHostAddress());
150                 addresses.add(address);
151                 addressCounter++;
152             }
153         }
154 
155         /*
156          * Getting the desired address from the command line. You can't automatically make sure to always use the
157          * correct host address.
158          */
159         System.out.print("\nWhich one should be used?\nType in the number: ");
160         Integer chosenIndex = null;
161 
162         while (chosenIndex == null) {
163             BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
164             String line = br.readLine();
165             try {
166                 chosenIndex = Integer.parseInt(line);
167             } catch (NumberFormatException nfe) {
168                 chosenIndex = null;
169             }
170         }
171 
172         String targetAddress = addresses.get(chosenIndex).getHostAddress();
173         System.out.println("Using ip address " + addresses.get(chosenIndex).getHostAddress());
174         
175         
176         switch (args.length) {
177             case 0 :
178                 target = new TargetServer(Configuration.create(targetAddress));
179                 break;
180             case 1 :
181 
182                 // Checking if the schema file is at the default location
183                 target = new TargetServer(Configuration.create(Configuration.CONFIGURATION_SCHEMA_FILE.exists() ? Configuration.CONFIGURATION_SCHEMA_FILE : new File("classpath://jscsi-target.xsd"), new File(args[0]), targetAddress));
184                 break;
185             case 2 :
186                 target = new TargetServer(Configuration.create(new File(args[0]), new File(args[1]), targetAddress));
187                 break;
188             default :
189                 throw new IllegalArgumentException("Only zero or one Parameter (Path to Configuration-File) allowed!");
190         }
191 
192         target.call();
193     }
194 
195     public Void call () throws Exception {
196 
197         // Create a blocking server socket and check for connections
198         try {
199             // Create a blocking server socket channel on the specified/default
200             // port
201             serverSocketChannel = ServerSocketChannel.open();
202             serverSocketChannel.configureBlocking(true);
203 
204             // Making sure the socket is bound to the address used in the config.
205             serverSocketChannel.socket().bind(new InetSocketAddress(getConfig().getTargetAddress(), getConfig().getPort()));
206 
207             while (running) {
208                 // Accept the connection request.
209                 // If serverSocketChannel is blocking, this method blocks.
210                 // The returned channel is in blocking mode.
211                 final SocketChannel socketChannel = serverSocketChannel.accept();
212 
213                 // deactivate Nagle algorithm
214                 socketChannel.socket().setTcpNoDelay(true);
215 
216                 connection = new TargetConnection(socketChannel, true);
217                 try {
218                     final ProtocolDataUnit pdu = connection.receivePdu();
219                     // confirm OpCode-
220                     if (pdu.getBasicHeaderSegment().getOpCode() != OperationCode.LOGIN_REQUEST) throw new InternetSCSIException();
221                     // get initiatorSessionID
222                     
223                     LoginRequestParser parser = (LoginRequestParser) pdu.getBasicHeaderSegment().getParser();
224                     ISID initiatorSessionID = parser.getInitiatorSessionID();
225 
226                     /*
227                      * TODO get (new or existing) session based on TSIH But since we don't do session reinstatement and
228                      * MaxConnections=1, we can just create a new one.
229                      */
230                     TargetSession session = new TargetSession(this, connection, initiatorSessionID, parser.getCommandSequenceNumber(),// set
231                                                                                                                                       // ExpCmdSN
232                                                                                                                                       // (PDU
233                                                                                                                                       // is
234                                                                                                                                       // immediate,
235                                                                                                                                       // hence
236                                                                                                                                       // no
237                                                                                                                                       // ++)
238                     parser.getExpectedStatusSequenceNumber());
239 
240                     sessions.add(session);
241                     // threadPool.submit(connection);// ignore returned Future
242                     connection.call();
243                 } catch (DigestException | InternetSCSIException | SettingsException e) {
244                     LOGGER.info("Throws Exception", e);
245                     continue;
246                 }
247             }
248         } catch (IOException e) {
249             // this block is entered if the desired port is already in use
250             LOGGER.error("Throws Exception", e);
251         }
252 
253         System.out.println("Closing socket channel.");
254         serverSocketChannel.close();
255         for(TargetSession session: sessions){
256             System.out.println("Commiting uncommited changes.");
257             session.getStorageModule().close();
258         }
259         return null;
260     }
261 
262     public Configuration getConfig () {
263         return config;
264     }
265 
266     public DeviceIdentificationVpdPage getDeviceIdentificationVpdPage () {
267         return deviceIdentificationVpdPage;
268     }
269 
270     public Target getTarget (String targetName) {
271         synchronized (targets) {
272             return targets.get(targetName);
273         }
274     }
275 
276     /**
277      * Removes a session from the jSCSI Target's list of active sessions.
278      * 
279      * @param session the session to remove from the list of active sessions
280      */
281     public synchronized void removeTargetSession (TargetSession session) {
282         sessions.remove(session);
283     }
284 
285     public String[] getTargetNames () {
286         String[] returnNames = new String[targets.size()];
287         returnNames = targets.keySet().toArray(returnNames);
288         return returnNames;
289     }
290 
291     /**
292      * Checks to see if this target name is valid.
293      * 
294      * @param checkTargetName
295      * @return true if the the target name is configured
296      */
297     public boolean isValidTargetName (String checkTargetName) {
298         return targets.containsKey(checkTargetName);
299     }
300 
301     /**
302      * Using this connection mainly for test pruposes.
303      * 
304      * @return the connection the target server established.
305      */
306     public Connection getConnection () {
307         return this.connection;
308     }
309     
310     /**
311      * Stop this target server
312      */
313     public void stop(){
314         this.running = false;
315         for(TargetSession session : sessions){
316             if(!session.getConnection().stop()){
317                 this.running = true;
318                 LOGGER.error("Unable to stop session for " + session.getTargetName());
319             }
320         }
321     }
322 
323 }