PowerCLI اسڪرپٽ لاءِ راڪيٽ بوسٹر ڪيئن ٺاهجي 

جلدي يا بعد ۾، ڪنهن به VMware سسٽم ايڊمنسٽريٽر کي خودڪار طريقي سان معمولي ڪم ڪرڻ لاء اچي ٿو. اهو سڀ ڪمانڊ لائن سان شروع ٿئي ٿو، پوء اچي ٿو PowerShell يا VMware PowerCLI.

اچو ته چئو ته توهان ISE لانچ ڪرڻ ۽ ماڊلز مان معياري cmdlets استعمال ڪرڻ کان ٿورو اڳتي PowerShell ۾ مهارت حاصل ڪئي آهي جيڪي ”ڪجهه قسم جي جادو“ جي ڪري ڪم ڪن ٿا. جڏهن توهان ورچوئل مشينن کي سوين ۾ ڳڻڻ شروع ڪندا، توهان کي معلوم ٿيندو ته اهي اسڪرپٽ جيڪي ننڍي پيماني تي مدد ڪن ٿيون وڏي پيماني تي واضح طور تي سست هلن ٿيون. 

هن صورتحال ۾، 2 اوزار مدد ڪندو:

  • PowerShell Runspaces - هڪ طريقو جيڪو توهان کي اجازت ڏئي ٿو ته عمل جي عمل کي الڳ الڳ موضوعن ۾؛ 
  • حاصل- ڏيک - هڪ بنيادي PowerCLI فنڪشن، ونڊوز ۾ Get-WMIObject جو هڪ اينالاگ. هي cmdlet شين کي گڏ نه ٿو ڪري، پر معلومات حاصل ڪري ٿي هڪ سادي اعتراض جي صورت ۾ سادو ڊيٽا جي قسمن سان. ڪيترن ئي ڪيسن ۾ اهو تيزيء سان نڪرندو آهي.

اڳيون، آئون مختصر طور تي هر اوزار بابت ڳالهائيندس ۽ استعمال جا مثال ڏيکاريندس. اچو ته مخصوص اسڪرپٽ جو تجزيو ڪريون ۽ ڏسون جڏهن هڪ ٻئي کان بهتر ڪم ڪري ٿي. وڃ!

PowerCLI اسڪرپٽ لاءِ راڪيٽ بوسٹر ڪيئن ٺاهجي

پهريون مرحلو: Runspace

تنهن ڪري، Runspace بنيادي ماڊل کان ٻاهر ڪمن جي متوازي پروسيسنگ لاء ٺهيل آهي. يقينا، توهان هڪ ٻيو عمل شروع ڪري سگهو ٿا جيڪو ڪجهه ميموري، پروسيسر وغيره کائي ڇڏيندو. جيڪڏهن توهان جو اسڪرپٽ چند منٽن ۾ هلندو آهي ۽ هڪ گيگا بائيٽ ميموري استعمال ڪندو آهي، گهڻو ڪري توهان کي رن اسپيس جي ضرورت نه هوندي. پر لکين شين جي لکت لاءِ ان جي ضرورت پوندي آهي.

توهان هتي سکڻ شروع ڪري سگهو ٿا: 
PowerShell Runspaces جو استعمال جي شروعات: حصو 1

Runspace استعمال ڪندي ڇا ڏئي ٿو:

  • تيز رفتار تي عملدرآمد حڪمن جي لسٽ کي محدود ڪندي،
  • ڪمن جي متوازي عملدرآمد،
  • حفاظت.

هتي انٽرنيٽ مان هڪ مثال آهي جڏهن Runspace مدد ڪري ٿي:

"اسٽوريج جي تڪرار vSphere ۾ ٽريڪ ڪرڻ لاء سڀ کان سخت ميٽرڪ مان هڪ آهي. اندر vCenter، توهان صرف نه ٿا وڃو ۽ ڏسو ته ڪهڙو VM وڌيڪ اسٽوريج وسيلن کي استعمال ڪري رهيو آهي. خوشقسمتيءَ سان، توهان هن ڊيٽا کي منٽن ۾ گڏ ڪري سگهو ٿا PowerShell جي مهرباني.
مان هڪ اسڪرپٽ شيئر ڪندس جيڪو VMware سسٽم جي منتظمين کي اجازت ڏيندو ته جلدي ڳولا ڪري سگھن سڄي vCenter ۽ VMs جي فهرست حاصل ڪري انهن جي سراسري استعمال تي ڊيٽا سان.  
اسڪرپٽ PowerShell runspaces استعمال ڪري ٿي ته جيئن هر ESXi ميزبان کي پنهنجي VMs کان استعمال جي معلومات گڏ ڪرڻ جي اجازت ڏئي هڪ الڳ رن اسپيس ۾ ۽ فوري طور تي مڪمل ٿيڻ جي رپورٽ ڪري. هي پاور شيل کي فوري طور تي نوڪريون بند ڪرڻ جي اجازت ڏئي ٿو، بجاءِ ميزبانن جي ذريعي ٻيهر ڪرڻ ۽ هر هڪ جي درخواست مڪمل ڪرڻ جو انتظار ڪرڻ جي.

جو ذريعو: ESXi ڊيش بورڊ تي ورچوئل مشين I/O ڪيئن ڏيکاريو

هيٺ ڏنل صورت ۾، Runspace هاڻي مفيد نه آهي:

”مان ڪوشش ڪري رهيو آهيان هڪ اسڪرپٽ لکڻ جي جيڪا VM مان تمام گهڻو ڊيٽا گڏ ڪري ۽ ضروري هجي ته نئين ڊيٽا لکي. مسئلو اهو آهي ته اتي ڪافي VMs آهن، ۽ هڪ مشين تي 5-8 سيڪنڊ خرچ ڪيا ويا آهن. 

جو ذريعو: Multithreading PowerCLI RunspacePool سان

هتي توهان کي حاصل ڪرڻ جي ضرورت پوندي، اچو ته ان ڏانهن وڃو. 

ٻيو مرحلو: حاصل ڪريو ڏسو

سمجھڻ لاءِ ڇو Get-View ڪارائتو آهي، اهو ياد رکڻ لائق آهي ته cmdlets عام طور تي ڪيئن ڪم ڪن ٿا. 

Cmdlets جي ضرورت آهي آسانيءَ سان معلومات حاصل ڪرڻ لاءِ بغير API ريفرنس ڪتابن جو مطالعو ڪرڻ ۽ ايندڙ ڦيٿي کي نئين سر ترتيب ڏيڻ جي. ڇا پراڻن ڏينهن ۾ ڪوڊ جون هڪ سو يا ٻه لائينون ورتيون، پاور شيل توهان کي هڪ حڪم سان ڪرڻ جي اجازت ڏئي ٿو. اسان رفتار سان هن سهولت لاءِ ادا ڪريون ٿا. cmdlets جي اندر ڪو به جادو نه آهي: ساڳي رسم الخط، پر هيٺين سطح تي، سني هندستان جي هڪ ماسٽر جي مهارت رکندڙ هٿن سان لکيل آهي.

ھاڻي، Get-View سان مقابلي لاءِ، اچو ته وٺو وٺو Get-VM cmdlet: اھو ورچوئل مشين تائين پھچندو آھي ۽ ھڪڙي جامع شئي موٽائي ٿو، يعني اھو ان سان لاڳاپيل ٻيون شيون ڳنڍي ٿو: VMHost، Datastore، وغيره.  

Get-View ان جي جاءِ تي واپس ڪيل اعتراض ۾ ڪا به غير ضروري شيءِ شامل نه ڪندو آهي. ان کان علاوه، اهو اسان کي سختي سان وضاحت ڪرڻ جي اجازت ڏئي ٿو ته اسان کي ڪهڙي معلومات جي ضرورت آهي، جيڪا آئوٽ آئوٽ اعتراض کي آسان بڻائي سگهندي. ونڊوز سرور ۾ عام طور تي ۽ هائپر-وي ۾ خاص طور تي، Get-WMIObject cmdlet هڪ سڌو اينالاگ آهي - خيال بلڪل ساڳيو آهي.

Get-View نقطي شين تي معمولي عملن لاءِ تڪليف ڏيندڙ آهي. پر جڏهن اهو اچي ٿو هزارين ۽ هزارين شيون، ان جي ڪا قيمت ناهي.

توهان VMware بلاگ تي وڌيڪ پڙهي سگهو ٿا: Get-View جو تعارف

ھاڻي مان توھان کي ڏيکاريندس سڀ ڪجھ حقيقي ڪيس استعمال ڪندي. 

VM کي لوڊ ڪرڻ لاءِ اسڪرپٽ لکڻ

هڪ ڏينهن منهنجي ساٿي مون کي هن جي اسڪرپٽ کي بهتر ڪرڻ لاءِ چيو. ڪم هڪ عام معمول آهي: سڀني VMs کي هڪ نقل سان ڳولهيو cloud.uuid پيٽرول (ها، اهو ممڪن آهي جڏهن vCloud ڊائريڪٽر ۾ هڪ VM ڪلوننگ). 

واضح حل جيڪو ذهن ۾ اچي ٿو اهو آهي:

  1. سڀني VMs جي فهرست حاصل ڪريو.
  2. ڪنهن به طرح لسٽ کي پارس ڪريو.

اصل نسخو هي سادو اسڪرپٽ هو:

function Get-CloudUUID1 {
   # Получаем список всех ВМ
   $vms = Get-VM
   $report = @()

   # Обрабатываем каждый объект, получая из него только 2 свойства: Имя ВМ и Cloud UUID.
   # Заносим данные в новый PS-объект с полями VM и UUID
   foreach ($vm in $vms)
   {
       $table = "" | select VM,UUID

       $table.VM = $vm.name
       $table.UUID = ($vm | Get-AdvancedSetting -Name cloud.uuid).Value
          
       $report += $table
   }
# Возвращаем все объекты
   $report
}
# Далее РУКАМИ парсим полученный результат

هر شيء بلڪل سادي ۽ صاف آهي. ڪافي جي وقفي سان چند منٽن ۾ لکي سگهجي ٿو. فلٽريشن تي ڇڪيو ۽ اهو ٿي چڪو آهي.

پر اچو ته وقت جو اندازو لڳايو:

PowerCLI اسڪرپٽ لاءِ راڪيٽ بوسٹر ڪيئن ٺاهجي

PowerCLI اسڪرپٽ لاءِ راڪيٽ بوسٹر ڪيئن ٺاهجي

2 منٽ 47 سيڪنڊ جڏهن پروسيسنگ لڳ ڀڳ 10k VMs. هڪ بونس فلٽر جي غير موجودگي آهي ۽ نتيجن کي دستي طور تي ترتيب ڏيڻ جي ضرورت آهي. ظاهر آهي، اسڪرپٽ کي اصلاح جي ضرورت آهي.

رن اسپيسز پھريون آھن جيڪي بچاءَ لاءِ اچن ٿيون جڏھن توھان کي ھڪ ئي وقت vCenter کان ھوسٽ ميٽرڪس حاصل ڪرڻ جي ضرورت آھي يا ڏھ ھزارن شين کي پروسيس ڪرڻ جي ضرورت آھي. اچو ته ڏسو ته هي طريقو ڇا آڻيندو.

پهرين رفتار کي چالو ڪريو: PowerShell Runspaces

پهرين شيء جيڪا هن اسڪرپٽ لاء ذهن ۾ اچي ٿي اها آهي لوپ کي ترتيب ڏيڻ سان نه، پر متوازي موضوعن ۾، سڀني ڊيٽا کي هڪ اعتراض ۾ گڏ ڪريو ۽ ان کي فلٽر ڪريو. 

پر اتي هڪ مسئلو آهي: PowerCLI اسان کي اجازت نه ڏيندو ته ڪيترن ئي آزاد سيشن کي vCenter ڏانهن کولڻ ۽ هڪ عجيب غلطي اڇلائي ڇڏيندو:

You have modified the global:DefaultVIServer and global:DefaultVIServers system variables. This is not allowed. Please reset them to $null and reconnect to the vSphere server.

ھن کي حل ڪرڻ لاءِ، توھان کي پھريائين پاس ڪرڻ گھرجي سيشن جي معلومات اسٽريم اندر. اچو ته ياد رکون ته PowerShell انهن شين سان ڪم ڪري ٿو جيڪي هڪ پيراميٽر جي طور تي يا ته فنڪشن يا اسڪرپٽ بلاڪ ڏانهن منتقل ڪري سگهجن ٿيون. اچو ته سيشن کي اهڙي شئي جي صورت ۾ پاس ڪريون، $global:DefaultVIServers (Connect-VIServer -NotDefault ڪيئي سان):

$ConnectionString = @()
foreach ($vCenter in $vCenters)
   {
       try {
           $ConnectionString += Connect-VIServer -Server $vCenter -Credential $Credential -NotDefault -AllLinked -Force -ErrorAction stop -WarningAction SilentlyContinue -ErrorVariable er
       }
       catch {
           if ($er.Message -like "*not part of a linked mode*")
           {
               try {
                   $ConnectionString += Connect-VIServer -Server $vCenter -Credential $Credential -NotDefault -Force -ErrorAction stop -WarningAction SilentlyContinue -ErrorVariable er
               }
               catch {
                   throw $_
               }
              
           }
           else {
               throw $_
           }
       }
   }

ھاڻي اچو ته رن اسپيس پولز ذريعي ملٽي ٿريڊنگ لاڳو ڪريون.  

الگورتھم ھي as ڏنل آھي:

  1. اسان سڀني VMs جي هڪ فهرست حاصل ڪندا آهيون.
  2. متوازي اسٽريمز ۾ اسان حاصل ڪندا آهيون cloud.uuid.
  3. اسان اسٽريم مان ڊيٽا گڏ ڪندا آهيون هڪ اعتراض ۾.
  4. اسان اعتراض کي فلٽر ڪريون ٿا ان کي گروپ ڪري ان کي CloudUUID فيلڊ جي قدر سان: اهي جتي منفرد قدرن جو تعداد 1 کان وڌيڪ آهي اهي VM آهن جن کي اسان ڳولي رهيا آهيون.

نتيجي طور، اسان لکت حاصل ڪريون ٿا:


function Get-VMCloudUUID {
   param (
       [string[]]
       [ValidateNotNullOrEmpty()]
       $vCenters = @(),
       [int]$MaxThreads,
       [System.Management.Automation.PSCredential]
       [System.Management.Automation.Credential()]
       $Credential
   )

   $ConnectionString = @()

   # Создаем объект с сессионным ключом
   foreach ($vCenter in $vCenters)
   {
       try {
           $ConnectionString += Connect-VIServer -Server $vCenter -Credential $Credential -NotDefault -AllLinked -Force -ErrorAction stop -WarningAction SilentlyContinue -ErrorVariable er
       }
       catch {
           if ($er.Message -like "*not part of a linked mode*")
           {
               try {
                   $ConnectionString += Connect-VIServer -Server $vCenter -Credential $Credential -NotDefault -Force -ErrorAction stop -WarningAction SilentlyContinue -ErrorVariable er
               }
               catch {
                   throw $_
               }
              
           }
           else {
               throw $_
           }
       }
   }

   # Получаем список всех ВМ
   $Global:AllVMs = Get-VM -Server $ConnectionString

   # Поехали!
   $ISS = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
   $RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $ISS, $Host)
   $RunspacePool.ApartmentState = "MTA"
   $RunspacePool.Open()
   $Jobs = @()

# ScriptBlock с магией!)))
# Именно он будет выполняться в потоке
   $scriptblock = {
       Param (
       $ConnectionString,
       $VM
       )

       $Data = $VM | Get-AdvancedSetting -Name Cloud.uuid -Server $ConnectionString | Select-Object @{N="VMName";E={$_.Entity.Name}},@{N="CloudUUID";E={$_.Value}},@{N="PowerState";E={$_.Entity.PowerState}}

       return $Data
   }
# Генерируем потоки

   foreach($VM in $AllVMs)
   {
       $PowershellThread = [PowerShell]::Create()
# Добавляем скрипт
       $null = $PowershellThread.AddScript($scriptblock)
# И объекты, которые передадим в качестве параметров скрипту
       $null = $PowershellThread.AddArgument($ConnectionString)
       $null = $PowershellThread.AddArgument($VM)
       $PowershellThread.RunspacePool = $RunspacePool
       $Handle = $PowershellThread.BeginInvoke()
       $Job = "" | Select-Object Handle, Thread, object
       $Job.Handle = $Handle
       $Job.Thread = $PowershellThread
       $Job.Object = $VM.ToString()
       $Jobs += $Job
   }

# Ставим градусник, чтобы наглядно отслеживать выполнение заданий
# И здесь же прибиваем отработавшие задания
   While (@($Jobs | Where-Object {$_.Handle -ne $Null}).count -gt 0)
   {
       $Remaining = "$($($Jobs | Where-Object {$_.Handle.IsCompleted -eq $False}).object)"

       If ($Remaining.Length -gt 60) {
           $Remaining = $Remaining.Substring(0,60) + "..."
       }

       Write-Progress -Activity "Waiting for Jobs - $($MaxThreads - $($RunspacePool.GetAvailableRunspaces())) of $MaxThreads threads running" -PercentComplete (($Jobs.count - $($($Jobs | Where-Object {$_.Handle.IsCompleted -eq $False}).count)) / $Jobs.Count * 100) -Status "$(@($($Jobs | Where-Object {$_.Handle.IsCompleted -eq $False})).count) remaining - $remaining"

       ForEach ($Job in $($Jobs | Where-Object {$_.Handle.IsCompleted -eq $True})){
           $Job.Thread.EndInvoke($Job.Handle)     
           $Job.Thread.Dispose()
           $Job.Thread = $Null
           $Job.Handle = $Null
       }
   }

   $RunspacePool.Close() | Out-Null
   $RunspacePool.Dispose() | Out-Null
}


function Get-CloudUUID2
{
   [CmdletBinding()]
   param(
   [string[]]
   [ValidateNotNullOrEmpty()]
   $vCenters = @(),
   [int]$MaxThreads = 50,
   [System.Management.Automation.PSCredential]
   [System.Management.Automation.Credential()]
   $Credential)

   if(!$Credential)
   {
       $Credential = Get-Credential -Message "Please enter vCenter credentials."
   }

   # Вызов функции Get-VMCloudUUID, где мы распараллеливаем операцию
   $AllCloudVMs = Get-VMCloudUUID -vCenters $vCenters -MaxThreads $MaxThreads -Credential $Credential
   $Result = $AllCloudVMs | Sort-Object Value | Group-Object -Property CloudUUID | Where-Object -FilterScript {$_.Count -gt 1} | Select-Object -ExpandProperty Group
   $Result
}

هن اسڪرپٽ جي خوبي اها آهي ته ان کي ٻين ساڳين ڪيسن ۾ استعمال ڪري سگهجي ٿو صرف اسڪرپٽ بلاڪ کي تبديل ڪندي ۽ پيرا ميٽرز جيڪي اسٽريم ڏانهن منتقل ڪيا ويندا. ان جو استحصال ڪريو!

اسان وقت ماپ ڪريون ٿا:

PowerCLI اسڪرپٽ لاءِ راڪيٽ بوسٹر ڪيئن ٺاهجي

55 سيڪنڊ. اهو بهتر آهي، پر اهو اڃا به تيز ٿي سگهي ٿو. 

اچو ته ٻئي رفتار تي وڃو: GetView

اچو ته معلوم ڪريو ته ڇا غلط آهي.
سڀ کان پهريان ۽ سڀ کان پهريان، Get-VM cmdlet تي عمل ڪرڻ لاء هڪ ڊگهو وقت وٺندو آهي.
ٻيو، Get-AdvancedOptions cmdlet مڪمل ٿيڻ ۾ اڃا وڌيڪ وقت وٺندو آهي.
اچو ته پهرين ٻئي سان معاملو ڪريون. 

Get-AdvancedOptions انفرادي VM شين لاءِ آسان آهي، پر ڪيترن ئي شين سان ڪم ڪرڻ وقت تمام بيڪار. اسان ساڳي معلومات حاصل ڪري سگھون ٿا ورچوئل مشين اعتراض کان پاڻ (Get-VM). اهو صرف ExtensionData اعتراض ۾ دفن ٿيل آهي. فلٽرنگ سان هٿياربند، اسان ضروري ڊيٽا حاصل ڪرڻ جي عمل کي تيز ڪريون ٿا.

هٿ جي ٿوري حرڪت سان هي آهي:


VM | Get-AdvancedSetting -Name Cloud.uuid -Server $ConnectionString | Select-Object @{N="VMName";E={$_.Entity.Name}},@{N="CloudUUID";E={$_.Value}},@{N="PowerState";E={$_.Entity.PowerState}}

ان ۾ بدلجي ٿو:


$VM | Where-Object {($_.ExtensionData.Config.ExtraConfig | Where-Object {$_.key -eq "cloud.uuid"}).Value -ne $null} | Select-Object @{N="VMName";E={$_.Name}},@{N="CloudUUID";E={($_.ExtensionData.Config.ExtraConfig | Where-Object {$_.key -eq "cloud.uuid"}).Value}},@{N="PowerState";E={$_.summary.runtime.powerstate}}

پيداوار ساڳي آهي Get-AdvancedOptions، پر اهو ڪم ڪري ٿو ڪيترائي ڀيرا تيز. 

ھاڻي حاصل ڪريو-VM ڏانھن. اهو تيز ناهي ڇو ته اهو پيچيده شين سان واسطو رکي ٿو. هڪ منطقي سوال پيدا ٿئي ٿو: اسان کي هن معاملي ۾ اضافي معلومات ۽ هڪ خوفناڪ PSObject جي ضرورت ڇو آهي، جڏهن اسان کي صرف VM جو نالو، ان جي حالت ۽ هڪ مشڪل وصف جي قيمت جي ضرورت آهي؟  

ان کان علاوه، Get-AdvancedOptions جي صورت ۾ رڪاوٽ کي اسڪرپٽ مان هٽايو ويو آهي. Runspace Pools استعمال ڪرڻ ھاڻي اوور ڪِل وانگر لڳي ٿو ڇو ته ھاڻي ضرورت ناھي رھي سُست ٽاسڪ کي برابر ڪرڻ جي squat ٿريڊن تي جڏھن سيشن حوالي ڪيو وڃي. اوزار سٺو آهي، پر هن معاملي لاء نه. 

اچو ته ڏسو ExtensionData جي پيداوار تي: اهو هڪ Get-View اعتراض کان وڌيڪ ڪجهه ناهي. 

اچو ته PowerShell ماسٽرز جي قديم ٽيڪنڪ کي سڏين: هڪ لڪير فلٽر استعمال ڪندي، ترتيب ڏيڻ ۽ گروپ ڪرڻ. سڀ پوئين ڊار خوبصورت طور تي ھڪڙي لڪير ۾ ختم ٿي ويو آھي ۽ ھڪڙي سيشن ۾ عمل ڪيو ويو آھي:


$AllVMs = Get-View -viewtype VirtualMachine -Property Name,Config.ExtraConfig,summary.runtime.powerstate | Where-Object {($_.Config.ExtraConfig | Where-Object {$_.key -eq "cloud.uuid"}).Value -ne $null} | Select-Object @{N="VMName";E={$_.Name}},@{N="CloudUUID";E={($_.Config.ExtraConfig | Where-Object {$_.key -eq "cloud.uuid"}).Value}},@{N="PowerState";E={$_.summary.runtime.powerstate}} | Sort-Object CloudUUID | Group-Object -Property CloudUUID | Where-Object -FilterScript {$_.Count -gt 1} | Select-Object -ExpandProperty Group

اسان وقت ماپ ڪريون ٿا:

PowerCLI اسڪرپٽ لاءِ راڪيٽ بوسٹر ڪيئن ٺاهجي

9 سيڪنڊ لڳ ڀڳ 10k شين لاءِ گهربل شرط سان فلٽر ڪرڻ سان. زبردست!

سوچيم ته هڪ ٿڪل جي

قابل قبول نتيجو سڌو سنئون اوزار جي چونڊ تي منحصر آهي. اهو اڪثر ڪري پڪ سان چوڻ ڏکيو آهي ته ان کي حاصل ڪرڻ لاء ڇا چونڊيو وڃي. اسڪرپٽ کي تيز ڪرڻ لاءِ ڏنل فهرستن مان هر هڪ طريقو ان جي قابل اطلاق حدن جي اندر سٺو آهي. مون کي اميد آهي ته هي آرٽيڪل توهان جي انفراسٽرڪچر ۾ پروسيس آٽوميشن ۽ اصلاح جي بنيادي ڳالهين کي سمجهڻ جي مشڪل ڪم ۾ مدد ڪندو.

پي ليکڪ مضمون تيار ڪرڻ ۾ مدد ۽ مدد لاء سڀني ڪميونٽي جي ميمبرن جي مهرباني. حتي جن کي پنن سان. ۽ اهي به جن کي ٽنگون نه آهن، جهڙوڪ بوا ڪنسٽرڪٽر.

جو ذريعو: www.habr.com

تبصرو شامل ڪريو