Zkušenosti s přechodu na TFS a agilní techniky – záznam a slides [WUG Praha 1/2015]

Slides z přednášky 15.1.2015 pro Windows User Group Praha (WUG), kde jsme s kolegou Jirkou Kandou povídali i našich zkušenostech z přechodu HAVITu na TFS a agilní techniky (v období 08/2012 až 12/2014):

Z přednášky jsem pořizoval záznam, který najdete na našem HAVIT YouTube Channelu:

Omluvte sníženou kvalitu zvuku záznamu, nahrávali jsme tentokrát na dva různé mikrofony a výsledný záznam je sestřihán ze dvou různých zvukových stop (jedna z mikrofonů navíc nebyl zřejmě úplně ideálně umístěn a chytal nežádoucí ruchy).

Konverze časové zóny ve webové službě

Datový typ DateTime má v sobě položku DateTimeKind, která může nabývat hodnot Local, Utc, Unspecified. Podle nastavení této hodnoty je po-té provedena konverze časové zóny při volání webové služby.

V případě kindu unspecified se konverze časové zóny neprovádí. V případě kindu local se převede časová zóna volající strany do časové zóny příjemce. V případě kindu UTC se čas také nepřevádí.

Kind se při přenosu nemění.

Zde jsou příklady, jak která operace v C# nastavuje DateTimeKind:

DateTime dt1 = DateTime.Now; // DateTimeKind.Local
DateTime dt2 = DateTime.UtcNow; // DateTimeKind.Utc
DateTime dt3 = new DateTime(2014, 1, 1); // DateTimeKind.Unspecified
DateTime dt4 = DateTime.Now.ToLocalTime(); // DateTimeKind.Local
DateTime dt5 = DateTime.Now.ToUniversalTime(); // DateTimeKind.Utc
DateTime dt6 = DateTime.SpecifyKind(DateTime.Now.ToUniversalTime(), DateTimeKind.Unspecified); // DateTimeKind.Unspecified, pouze změna kindu bez konverze času

Při přenosu času jsme nakonec použili toto řešení:

Struktura:
...
public class WorkLogItemDTO
{
  /// <summary>
  /// Datum pracovního záznamu.
  /// </summary>
  public DateTime Datum { get; set; }

  /// <summary>
  /// Identifikátor klientské časové zóny.
  /// </summary>
  public string TimeZoneID { get; set; }
...
 
Klient (při volání serveru):
...
dto.Datum = item.Datum;
dto.TimeZoneID = TimeZoneInfo.Local.Id;
...

Server:
...
if (item.Datum.Kind != DateTimeKind.Unspecified)
{
  if (string.IsNullOrEmpty(item.TimeZoneID))
  {
    throw new Exception("Casova zona neni vyplnena (WorkLogItemDTO->IdTimeZone).");
  }
  TimeZoneInfo clientTimeZone = TimeZoneInfo.FindSystemTimeZoneById(item.TimeZoneID);
  item.Datum = TimeZoneInfo.ConvertTime(item.Datum, clientTimeZone);
}
...

 

Azure WebJob se občas spouští dvakrát

Problém

Máme v Azure nasazeno několik Azure Website spolu s naplanovanými (scheduled) web job. Řešíme situaci, kdy se webjob spustí dvakrát, zhruba v rozmezí jedné minuty. (V našem konkrétním případě posíláme notifikační emaily a tyto nám přijdou dva.)

Analýzou historie zjišťuji, že scheduler se webjob pokusil úlohu spustit dokonce třikrát.

webjob-history

Přičemž log jednotlivých pokusů vypadá takto:

  1. Http Action – Request to host ‚(…).scm.azurewebsites.net‘ failed: The timeout (30 seconds) was reached.
  2. Http Action – Response from host ‚(…).scm.azurewebsites.net': ‚Conflict‘ Response

    Body: Cannot start a new run since job is already running.
  3. Http Action – Response from host ‚(…).scm.azurewebsites.net': ‚Accepted‘ Response

Položky logu interpretuji takto:

  • Bod 1 úspěšně spustit webjob, ale nebyl schopen v intervalu 30 sekund toto zjistit.
  • Bod 2 se pokusil spustit webjob, ale ten již běžel, takže byl oznámen konflikt.
  • Bod 3 se opět pokusil spustit webjob, ten již doběhl, takže byl webjob úspěšně (ve skutečnosti podruhé) spuštěn.

Řešení

Po přečtení

se pokouším interpretovat (snad) klíčovou větu „If your site runs continuous web jobs, you should enable Always On, or the web jobs may not run reliably“ tak, že Always On zapínám i pro náš scheduled Web Job. Pomáhá to.

Volba Always On se nastavuje v Azure portálu, v nastavení Azure Website, se přepínačem Always On na záložce Configure.

Přístup k Team Foundation Serveru z Azure Website

Problém

V jedné z aplikací přistupujeme pomocí API jako klient do Team Foundation Serveru 2013.

ICredentials credentials = new NetworkCredential(...);
TfsTeamProjectCollection collection = new TfsTeamProjectCollection(new Uri(...), credentials);
collection.EnsureAuthenticated();

Po migraci aplikace z vlastních serverů do Azure Website nám připojení přestalo fungovat, konkrétně začalo docházet k této výjimce:

System.IO.IOException: The specified registry key does not exist.
   at Microsoft.Win32.RegistryKey.Win32Error(Int32 errorCode, String str)
   at Microsoft.Win32.RegistryKey.CreateSubKeyInternal(String subkey, RegistryKeyPermissionCheck permissionCheck, Object registrySecurityObj, RegistryOptions registryOptions)
   at Microsoft.Win32.RegistryKey.CreateSubKey(String subkey, RegistryKeyPermissionCheck permissionCheck, RegistryOptions options)
   at Microsoft.VisualStudio.Services.Common.TokenStorage.RegistryTokenStorageHelper.GetRootKey(String subkeyName)
   at Microsoft.VisualStudio.Services.Common.TokenStorage.RegistryTokenStorage.RetrieveToken(VssTokenKey tokenKey)
   at Microsoft.VisualStudio.Services.Common.TokenStorage.VssTokenStorage.Retrieve(VssTokenKey tokenKey)
   at Microsoft.TeamFoundation.Client.TfsClientCredentialStorage.RetrieveToken(Uri serverUrl, VssCredentialsType credentialType)
   at Microsoft.TeamFoundation.Client.CookieCredential.OnCreateTokenProvider(Uri serverUrl, HttpWebResponse response)
   at Microsoft.TeamFoundation.Client.IssuedTokenCredential.CreateTokenProvider(Uri serverUrl, HttpWebResponse response, IssuedToken failedToken)
   at Microsoft.TeamFoundation.Client.TfsClientCredentials.TryGetTokenProvider(Uri serverUrl, IssuedTokenProvider&amp; provider)
   at Microsoft.TeamFoundation.Client.Channels.TfsHttpRequestHelpers.PrepareWebRequest(HttpWebRequest webRequest, Guid sessionId, String operationName, CultureInfo cultureInfo, TfsRequestSettings settings, TfsClientCredentials credentials, IdentityDescriptor impersonate, IssuedToken&amp; currentToken, IssuedTokenProvider&amp; tokenProvider)
   at Microsoft.TeamFoundation.Client.Channels.TfsHttpRequestHelpers.CreateSoapRequest(Uri requestUri, Guid sessionId, String soapAction, String operationName, CultureInfo cultureInfo, TfsRequestSettings settings, TfsClientCredentials credentials, IdentityDescriptor impersonate, IssuedToken&amp; currentToken, IssuedTokenProvider&amp; tokenProvider)
   at Microsoft.TeamFoundation.Client.Channels.TfsHttpWebRequest.CreateWebRequest()
   at Microsoft.TeamFoundation.Client.Channels.TfsHttpWebRequest.SendRequest()
   at Microsoft.TeamFoundation.Client.Channels.TfsHttpRequestChannel.Request(TfsMessage message, TimeSpan timeout)
   at Microsoft.TeamFoundation.Client.Channels.TfsHttpClientBase.Invoke(TfsClientOperation operation, Object[] parameters, TimeSpan timeout, Object[]&amp; outputs)
   at Microsoft.TeamFoundation.Framework.Client.Registration.GetRegistrationEntries(String toolId)
   at Microsoft.TeamFoundation.Framework.Client.RegistrationProxy.GetRegistrationEntries(String toolId)
   at Microsoft.TeamFoundation.Framework.Client.RegistrationService.RefreshMemoryCache()
   at Microsoft.TeamFoundation.Framework.Client.RegistrationService.RefreshCachesIfNeeded(Boolean direct)
   at Microsoft.TeamFoundation.Framework.Client.RegistrationService.GetRegistrationEntries(String toolId)
   at Microsoft.TeamFoundation.Framework.Client.PreFrameworkServerDataProvider.FindServiceLocation(String serviceType, String toolId)
   at Microsoft.TeamFoundation.Framework.Client.PreFrameworkServerDataProvider.LocationForCurrentConnection(String serviceType, Guid serviceIdentifier)
   at Microsoft.TeamFoundation.Client.TfsConnection.EnsureProviderConnected()
   at Microsoft.TeamFoundation.Client.TfsConnection.EnsureAuthenticated()

TFS klient v průběhu přihlašování šahá do registrů. Po dlouhém pátrání pomocí .NET Reflectoru jsem dospěl k názoru, že v nich hledá dříve uložený autentizační token. To pro nás není podstatné, důležité je, že se bez toho můžeme obejít.

Řešení

Řešením je použití třídy TfsClientCredentials. Tu vyrobíme pomocí WindowCredentials a tu s pomocí dvou tříd: NetworkCredentials a SimpleWebTokenCredential, přičemž v NetworkCredentials máme údaje k autorizaci a SimpleWebTokenCredential nemusí nést žádné hodnoty, pouze zajišťuje workaround popsaného problému.

NetworkCredential networkCredentials = new NetworkCredential(...)
TfsClientCredentials tfsCredentials = new TfsClientCredentials(new WindowsCredential(networkCredentials), new SimpleWebTokenCredential(null, null), allowInteractive: false);
TfsTeamProjectCollection collection = new TfsTeamProjectCollection(new Uri(...), tfsCredentials);
collection.EnsureAuthenticated();

iTunes (+Match) – nelze změnit Media Kind (např. na Audiobook)

Pro hudbu a audioknihy používám iTunes. Protože mám iTunes Match, cpu do toho všechny CD, co se nám doma objeví. iTunes si je grabnou do M4A a uloží do Library. Ještě do „nedávna“ šlo měnit po nacucnutí CD pomocí Get Info volbu Media Kind (sekce Options), přičemž po přepnutí na Audiobook se album přesunulo ze sekce Music do sekce Audiobooks.

Dneska jsem zjistil, že volba Media Kind mi nabízí jedinou možnost: Music. Naimportované CD s pohádkami se mi nedaří dostat mezi Audiobooks.

Nakonec se ukazuje, že to je jakési nové chování iTunes, které se projevuje ve spojení s aktivním iTunes Match, kdy nelze Media Kind změnit.

Workaround: Stačí na chvilku vypnout iTunes Match (menu: Store / Turn Off iTunes Match), potom lze Media Kind změnit. Následně iTunes Match reaktivovat. To mi funguje.

Někdo další radí založit druhou Library bez napojení na iTunes Match, do ní si grabovat, nastavit Media Kind a další tagy a následně odtud hrnout do hlavní Library.

Jak vypnout automatické stmívání LCD panelu notebooku při tmavých obrazech

Už pěknou dobu mě štve, že můj notebook při tmavých obrazech (například Visual Studio bez otevřeného souboru) automaticky stahuje intenzitu podsvícení, což zhoršuje čitelnost a po změně obrazu na světlý mu několik vteřin trvá, než se zadaptuje zpět.

Je mi jasné, že to je hlavně kvůli úspoře baterie, ale proč to dělá i na AC adaptéru, to nevím. Každopádně mě to štve natolik, že jsem se to snažil od počátku nějak vypnout. Celkem rychle jsem se (chybně) dogooglil k nastavení Intel HD Graphics, které by to mělo být:

image

Po opakovaných neúspěšných pokusech s tímto nastavením jsem si to pro sebe uzavřel s tím, že mám holt smůlu a vypnout se mi to nepodaří. (Nakonec se ukázalo, že musí být vypnuté i toto, resp. alespoň v poloze „max. jeden dílek od Maximální kvality“).

Dnes mě to po delší době zase rozběsnilo, tak jsem pátral znovu. Nakonec jsem se dopátral k volbě, která zabírá, a kterou se to dá opravdu vypnout:

image

Nakonec se ukazuje, že vypnout je potřeba obojí. Volba přes Windows Power Options je výrazně agresivnější, nicméně i s jejím vypnutím se stále trochu obraz stmívá (není už to poznat ve Visual Studiu, ale třeba SnagIt, který je celý černý, tak nakonec intenzitu stáhne). Teprve po vypnutí na obou místech přestane stmívání úplně. Různou kombinací voleb lze určitě dosáhnout i přijatelného kompromisu mezi spotřebou a koukatelností.

Azure Website: Pokus o stažení souboru *.woff souboru končí chybou 404, přestože existuje

Abych byl spravedlivý, nejde jen o Azure Website, ale i o Windows Servery 2008. Důvodem je nedefinovaný Content-Type (resp. MimeType) pro daný typ.

Ten můžeme přidat do web.configu:

<system.webServer>
<staticContent>
<mimeMap fileExtension=".woff" mimeType="application/x-woff" />
</staticContent>
</system.webServer>

Pokud však takovouto konfiguraci nasadíme na server, kde je již Content-Type (resp. MimeType) pro danou příponu nastaven, dojde k pádu aplikace. Zobrazená chyba nám oznámí, že se pokoušíme nastavit MimeType pro typ, který je již definován.

Pokud chceme mít jednu konfiguraci pro všechny serveru, ať již mají nebo nemají definovaný MimeType pro příponu woff, můžeme použít trik s remove:

<system.webServer>
<staticContent>
<remove fileExtension=".woff" />
<mimeMap fileExtension=".woff" mimeType="application/x-woff" />
</staticContent>
</system.webServer>