< Summary

Class:Microsoft.Azure.Batch.Conventions.Files.StoragePath
Assembly:Microsoft.Azure.Batch.Conventions.Files
File(s):C:\Git\azure-sdk-for-net\sdk\batch\Microsoft.Azure.Batch.Conventions.Files\src\StoragePath.cs
Covered lines:31
Uncovered lines:42
Coverable lines:73
Total lines:245
Line coverage:42.4% (31 of 73)
Covered branches:11
Total branches:24
Branch coverage:45.8% (11 of 24)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-80%50%
SaveAsync()-33.33%50%
SaveAsync()-50%100%
SaveTextAsync()-0%0%
SaveTrackedAsync()-25%25%
SaveTrackedAsync()-44.44%100%
List(...)-40%50%
GetOutputAsync()-50%100%
BlobName(...)-0%100%
GetDestinationBlobPath(...)-0%0%
.ctor(...)-100%100%
BlobNamePrefix(...)-100%100%
BlobNamePrefixImpl(...)-100%100%
.ctor(...)-100%100%
BlobNamePrefix(...)-100%100%
BlobNamePrefixImpl(...)-100%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\batch\Microsoft.Azure.Batch.Conventions.Files\src\StoragePath.cs

#LineLine coverage
 1// Copyright (c) Microsoft and contributors.  All rights reserved.
 2//
 3// Licensed under the Apache License, Version 2.0 (the "License");
 4// you may not use this file except in compliance with the License.
 5// You may obtain a copy of the License at
 6// http://www.apache.org/licenses/LICENSE-2.0
 7//
 8// Unless required by applicable law or agreed to in writing, software
 9// distributed under the License is distributed on an "AS IS" BASIS,
 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 11//
 12// See the License for the specific language governing permissions and
 13// limitations under the License.
 14
 15using Microsoft.Azure.Batch.Conventions.Files.Utilities;
 16using Microsoft.WindowsAzure.Storage.Blob;
 17using System;
 18using System.Collections.Generic;
 19using System.Diagnostics;
 20using System.IO;
 21using System.Linq;
 22using System.Text;
 23using System.Threading;
 24using System.Threading.Tasks;
 25
 26namespace Microsoft.Azure.Batch.Conventions.Files
 27{
 28    // Represents a storage path within Azure Storage - that is, either job storage
 29    // ({container}) or task storage ({container}/{taskId}). Private derived classes
 30    // represent the two kinds of path.
 31    internal abstract class StoragePath
 32    {
 33        private readonly CloudBlobContainer _jobOutputContainer;
 34
 3535        protected StoragePath(CloudBlobContainer jobOutputContainer)
 36        {
 3537            if (jobOutputContainer == null)
 38            {
 039                throw new ArgumentNullException(nameof(jobOutputContainer));
 40            }
 41
 3542            _jobOutputContainer = jobOutputContainer;
 3543        }
 44
 45        // Uploads a file to blob storage.
 46        public async Task SaveAsync(
 47            IOutputKind kind,
 48            DirectoryInfo baseFolder,
 49            string relativePath,
 50            CancellationToken cancellationToken = default(CancellationToken)
 51        )
 52        {
 53            Debug.Assert(baseFolder != null);
 54
 655            if (kind == null)
 56            {
 257                throw new ArgumentNullException(nameof(kind));
 58            }
 59
 460            Validate.IsNotNullOrEmpty(relativePath, nameof(relativePath));
 61
 062            if (Path.IsPathRooted(relativePath))
 63            {
 064                throw new ArgumentException($"{nameof(relativePath)} must be a relative path", nameof(relativePath));
 65            }
 66
 067            string sourcePath = Path.Combine(baseFolder.FullName, relativePath);
 068            string destinationPath = GetDestinationBlobPath(relativePath);
 69
 070            await SaveAsync(kind, sourcePath, destinationPath, cancellationToken).ConfigureAwait(false);
 071        }
 72
 73        // Uploads a file to blob storage.
 74        public async Task SaveAsync(
 75            IOutputKind kind,
 76            string sourcePath,
 77            string destinationRelativePath,
 78            CancellationToken cancellationToken = default(CancellationToken)
 79        )
 80        {
 1081            if (kind == null)
 82            {
 283                throw new ArgumentNullException(nameof(kind));
 84            }
 85
 886            Validate.IsNotNullOrEmpty(sourcePath, nameof(sourcePath));
 487            Validate.IsNotNullOrEmpty(destinationRelativePath, nameof(destinationRelativePath));
 88
 089            var blobName = BlobName(kind, destinationRelativePath);
 090            var blob = _jobOutputContainer.GetBlockBlobReference(blobName);
 091            await blob.UploadFromFileAsync(sourcePath, null, null, null, cancellationToken).ConfigureAwait(false);
 092        }
 93
 94        // Uploads text to blob storage.
 95        public async Task SaveTextAsync(
 96            IOutputKind kind,
 97            string text,
 98            string destinationRelativePath,
 99            CancellationToken cancellationToken = default(CancellationToken)
 100        )
 101        {
 0102            if (kind == null)
 103            {
 0104                throw new ArgumentNullException(nameof(kind));
 105            }
 0106            if (text == null)
 107            {
 0108                throw new ArgumentNullException(nameof(text));
 109            }
 110
 0111            Validate.IsNotNullOrEmpty(destinationRelativePath, nameof(destinationRelativePath));
 112
 0113            var blobName = BlobName(kind, destinationRelativePath);
 0114            var blob = _jobOutputContainer.GetBlockBlobReference(blobName);
 0115            await blob.UploadTextAsync(text, null, null, null, null, cancellationToken).ConfigureAwait(false);
 0116        }
 117
 118        // Uploads a file and tracks appends to that file. The implementation creates an append blob to
 119        // contain the file contents, then creates a file tracking object which runs a background operation
 120        // to upload the file to the append blob, then track appends to the file and append them to the blob.
 121        public async Task<TrackedFile> SaveTrackedAsync(IOutputKind kind, string relativePath, TimeSpan flushInterval)
 122        {
 2123            if (kind == null)
 124            {
 0125                throw new ArgumentNullException(nameof(kind));
 126            }
 127
 2128            Validate.IsNotNullOrEmpty(relativePath, nameof(relativePath));
 129
 0130            if (Path.IsPathRooted(relativePath))
 131            {
 0132                throw new ArgumentException($"{nameof(relativePath)} must be a relative path", nameof(relativePath));
 133            }
 134
 0135            var destinationPath = GetDestinationBlobPath(relativePath);
 0136            return await SaveTrackedAsync(kind, relativePath, destinationPath, flushInterval).ConfigureAwait(false);
 0137        }
 138
 139        // Uploads a file and tracks appends to that file. The implementation creates an append blob to
 140        // contain the file contents, then creates a file tracking object which runs a background operation
 141        // to upload the file to the append blob, then track appends to the file and append them to the blob.
 142        public async Task<TrackedFile> SaveTrackedAsync(IOutputKind kind, string sourcePath, string destinationRelativeP
 143        {
 5144            if (kind == null)
 145            {
 1146                throw new ArgumentNullException(nameof(kind));
 147            }
 148
 4149            Validate.IsNotNullOrEmpty(sourcePath, nameof(sourcePath));
 2150            Validate.IsNotNullOrEmpty(destinationRelativePath, nameof(destinationRelativePath));
 151
 0152            var blobName = BlobName(kind, destinationRelativePath);
 0153            var blob = _jobOutputContainer.GetAppendBlobReference(blobName);
 0154            await blob.EnsureExistsAsync().ConfigureAwait(false);
 0155            return new TrackedFile(sourcePath, blob, flushInterval);
 0156        }
 157
 158        public IEnumerable<OutputFileReference> List(IOutputKind kind)
 159        {
 2160            if (kind == null)
 161            {
 2162                throw new ArgumentNullException(nameof(kind));
 163            }
 164
 0165            return _jobOutputContainer.ListBlobs(BlobNamePrefix(kind), useFlatBlobListing: true)
 0166                                      .OfType<ICloudBlob>()
 0167                                      .Select(b => new OutputFileReference(b));
 168        }
 169
 170        public async Task<OutputFileReference> GetOutputAsync(IOutputKind kind, string filePath, CancellationToken cance
 171        {
 6172            if (kind == null)
 173            {
 2174                throw new ArgumentNullException(nameof(kind));
 175            }
 176
 4177            Validate.IsNotNullOrEmpty(filePath, nameof(filePath));
 178
 0179            var blob = await _jobOutputContainer.GetBlobReferenceFromServerAsync(BlobName(kind, filePath), null, null, n
 180
 0181            return new OutputFileReference(blob);
 0182        }
 183
 184        // Gets the string that should be prefixed to a blob name to locate it correctly
 185        // in the container. This is the equivalent of a file system directory path - for
 186        // example a prefix might be "$JobOutput/" or "MyTask/$TaskOutput" - but Azure
 187        // Storage does not have the notion of directories under a container, only of prefixes.
 188        internal abstract string BlobNamePrefix(IOutputKind kind);
 189
 190        internal string BlobName(IOutputKind kind, string relativePath)
 0191            => $"{BlobNamePrefix(kind)}/{relativePath}";
 192
 193        private static string GetDestinationBlobPath(string relativeSourcePath)
 194        {
 195            const string up = "../";
 196
 0197            var destinationPath = relativeSourcePath.Replace('\\', '/');
 198
 199            // If we are given a path that traverses up from the working directory,
 200            // treat it as though it were rooted at the working directory for blob naming
 201            // purposes. This is intended to support files such as ..\stdout.txt, which
 202            // is stored above the task working directory.
 203            //
 204            // A user can intentionally try to defeat this simple flattening by using a path
 205            // such as "temp\..\..\stdout.txt" - this may result in the file being
 206            // stored in the 'wrong' part of the job container, but they can't write
 207            // outside the job container this way, so the only damage they can do is
 208            // to themselves.
 0209            while (destinationPath.StartsWith(up))
 210            {
 0211                destinationPath = relativeSourcePath.Substring(up.Length);
 212            }
 213
 0214            return destinationPath;
 215        }
 216
 217        internal sealed class JobStoragePath : StoragePath
 218        {
 219            internal JobStoragePath(CloudBlobContainer jobOutputContainer)
 14220                : base(jobOutputContainer)
 221            {
 14222            }
 223
 3224            internal override string BlobNamePrefix(IOutputKind kind) => BlobNamePrefixImpl(kind);
 225
 6226            internal static string BlobNamePrefixImpl(IOutputKind kind) => $"${kind.Text}";
 227        }
 228
 229        internal sealed class TaskStoragePath : StoragePath
 230        {
 231            private readonly string _taskId;
 232
 233            internal TaskStoragePath(CloudBlobContainer jobOutputContainer, string taskId)
 21234                : base(jobOutputContainer)
 235            {
 236                Debug.Assert(taskId != null);
 21237                _taskId = taskId;
 21238            }
 239
 4240            internal override string BlobNamePrefix(IOutputKind kind) => BlobNamePrefixImpl(kind, _taskId);
 241
 8242            internal static string BlobNamePrefixImpl(IOutputKind kind, string taskId) => $"{taskId}/${kind.Text}";
 243        }
 244    }
 245}